diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 130f7a6..d1437cb 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_analytics","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics-12.0.0/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_temp_fork","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_temp_fork-0.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/ashwin/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_ios","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_ios-0.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_analytics","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics-12.0.0/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_temp_fork","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_temp_fork-0.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/ashwin/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_android","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_android-0.0.1+2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.10/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.16/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_analytics","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics-12.0.0/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_macos","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_macos-1.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_macos","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_macos-0.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_selector_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.3+2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_linux-1.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_linux-0.0.2/","native_build":false,"dependencies":["file_selector_linux"],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_selector_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_windows-1.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_windows-0.0.2/","native_build":false,"dependencies":["file_selector_windows"],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_analytics_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics_web-0.6.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core_web-3.0.0/","dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_temp_fork","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_temp_fork-0.1.5/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/ashwin/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/","dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_web-0.0.2/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"firebase_analytics","dependencies":["firebase_analytics_web","firebase_core"]},{"name":"firebase_analytics_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"flutter_keyboard_visibility_linux","dependencies":[]},{"name":"flutter_keyboard_visibility_macos","dependencies":[]},{"name":"flutter_keyboard_visibility_temp_fork","dependencies":["flutter_keyboard_visibility_linux","flutter_keyboard_visibility_macos","flutter_keyboard_visibility_windows"]},{"name":"flutter_keyboard_visibility_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"quill_native_bridge","dependencies":["quill_native_bridge_android","quill_native_bridge_web","quill_native_bridge_windows","quill_native_bridge_linux","quill_native_bridge_ios","quill_native_bridge_macos"]},{"name":"quill_native_bridge_android","dependencies":[]},{"name":"quill_native_bridge_ios","dependencies":[]},{"name":"quill_native_bridge_linux","dependencies":["file_selector_linux"]},{"name":"quill_native_bridge_macos","dependencies":[]},{"name":"quill_native_bridge_web","dependencies":[]},{"name":"quill_native_bridge_windows","dependencies":["file_selector_windows"]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2025-07-30 16:44:38.240412","version":"3.32.8","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_analytics","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics-12.0.0/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_temp_fork","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_temp_fork-0.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/ashwin/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/home/ashwin/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.9.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_ios","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_ios-0.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_analytics","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics-12.0.0/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_temp_fork","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_temp_fork-0.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/ashwin/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_android","path":"/home/ashwin/.pub-cache/hosted/pub.dev/google_sign_in_android-6.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_android","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_android-0.0.1+2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.10/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.16/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_analytics","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics-12.0.0/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_macos","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_macos-1.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/home/ashwin/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.9.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_macos","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_macos-0.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_selector_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.3+2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_linux-1.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_linux-0.0.2/","native_build":false,"dependencies":["file_selector_linux"],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_selector_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_core","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_windows-1.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_windows-0.0.2/","native_build":false,"dependencies":["file_selector_windows"],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"connectivity_plus","path":"/home/ashwin/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.4/","dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/ashwin/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_analytics_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_analytics_web-0.6.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/firebase_core_web-3.0.0/","dependencies":[],"dev_dependency":false},{"name":"flutter_keyboard_visibility_temp_fork","path":"/home/ashwin/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_temp_fork-0.1.5/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/ashwin/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/","dependencies":[],"dev_dependency":false},{"name":"google_sign_in_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.4+4/","dependencies":[],"dev_dependency":false},{"name":"quill_native_bridge_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/quill_native_bridge_web-0.0.2/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/home/ashwin/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"firebase_analytics","dependencies":["firebase_analytics_web","firebase_core"]},{"name":"firebase_analytics_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"flutter_keyboard_visibility_linux","dependencies":[]},{"name":"flutter_keyboard_visibility_macos","dependencies":[]},{"name":"flutter_keyboard_visibility_temp_fork","dependencies":["flutter_keyboard_visibility_linux","flutter_keyboard_visibility_macos","flutter_keyboard_visibility_windows"]},{"name":"flutter_keyboard_visibility_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"quill_native_bridge","dependencies":["quill_native_bridge_android","quill_native_bridge_web","quill_native_bridge_windows","quill_native_bridge_linux","quill_native_bridge_ios","quill_native_bridge_macos"]},{"name":"quill_native_bridge_android","dependencies":[]},{"name":"quill_native_bridge_ios","dependencies":[]},{"name":"quill_native_bridge_linux","dependencies":["file_selector_linux"]},{"name":"quill_native_bridge_macos","dependencies":[]},{"name":"quill_native_bridge_web","dependencies":[]},{"name":"quill_native_bridge_windows","dependencies":["file_selector_windows"]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2025-08-01 11:26:07.022719","version":"3.32.8","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 37b1568..3b0930e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,12 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ +migrate_working_dir/ # IntelliJ related *.iml @@ -26,15 +29,9 @@ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies -.packages .pub-cache/ .pub/ /build/ -pubspec.lock -.vscode - -# Web related -lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols @@ -46,3 +43,71 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Firebase & Security Sensitive Files +android/app/google-services.json +ios/Runner/GoogleService-Info.plist +ios/firebase_app_id_file.json + +# Environment & Configuration Files +.env +.env.local +.env.production +.env.development +config.json +secrets.json + +# API Keys & Credentials +**/api_keys.dart +**/secrets.dart +**/credentials.json +**/service-account-key.json + +# Keystore Files (Android signing) +*.keystore +*.jks +key.properties +android/key.properties + +# iOS Signing & Certificates +ios/Runner/GoogleService-Info.plist +ios/Runner.xcodeproj/project.xcworkspace/xcuserdata/ +ios/Runner.xcodeproj/xcuserdata/ +ios/Pods/ +ios/.symlinks/ +*.mobileprovision +*.p12 +*.cer + +# Local Development +local.properties +android/local.properties + +# Claude AI Assistant +.claude/ +*.claude.json + +# IDE & Editor +.vscode/settings.json +.vscode/launch.json +*.code-workspace + +# OS Generated +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Temporary Files +*.tmp +*.temp +*.bak +*.swp +*~ + +# Log Files +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f434879 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,137 @@ +# BottleCRM - Claude Context + +## Project Overview +- **Name**: BottleCRM +- **Platform**: Flutter +- **Version**: 1.0.0+1 +- **Flutter SDK**: ^3.8.1 +- **Design System**: Material Design +- **Icons**: Material Icons +- **Architecture**: Modern Flutter best practices + +## Project Structure +- `/lib/` - Main Dart source code +- `/assets/` - Application assets (images, icons, fonts) +- `/android/` - Android-specific configuration +- `/ios/` - iOS-specific configuration +- `/web/` - Web platform configuration +- `/test/` - Unit and widget tests + +## Development Guidelines + +### Design & UI/UX +- Follow Material Design 3 principles +- Implement modern UI/UX best practices +- Maintain consistency across all screens +- Use responsive design for different screen sizes + +#### Card & List Styling Guidelines +- **Flat Design**: Use flat, minimal card styling similar to Asana/Jira +- **No Shadows**: Avoid Card elevation/shadows; use Container with borders instead +- **AppBar Styling**: Keep AppBars transparent/white (no backgroundColor: inversePrimary) +- **List Items**: Use bottom borders (Colors.grey.shade200) for item separation +- **Dashboard Cards**: White background with subtle grey borders and 8px border radius +- **Task Cards**: Add left border with priority color (3px width) for visual hierarchy +- **Consistent Spacing**: Use 4px vertical margins for list items, 16px horizontal margins + +#### Color Guidelines +- **Borders**: Use Colors.grey.shade200 for subtle separators +- **Priority Colors**: Green (low), Orange (medium), Red (high), Purple (urgent) +- **Status Colors**: Blue (new), Orange (contacted), Green (qualified), Red (lost) +- **Backgrounds**: White containers with minimal border styling + +### Code Standards +- Follow Dart/Flutter naming conventions +- Use descriptive variable and function names +- Implement proper error handling +- Write clean, maintainable code +- Add appropriate comments for complex logic + +### File & Folder Structure +- Organize files by feature/module +- Use proper naming conventions (snake_case for files) +- Keep related files grouped together +- Maintain clear separation of concerns + +### Dependencies +- **Core**: Flutter SDK, Cupertino Icons +- **Development**: Flutter Test, Flutter Lints, Flutter Launcher Icons +- **Linting**: Enabled via analysis_options.yaml + +## Common Commands +```bash +# Install dependencies +flutter pub get + +# Run the app +flutter run + +# Run tests +flutter test + +# Build for release +flutter build apk --release +flutter build ios --release + +# Generate icons +flutter pub run flutter_launcher_icons:main + +# Analyze code +flutter analyze + +# Format code +dart format . +``` + +## Key Features to Implement +- CRM functionality (contacts, leads, deals) +- User authentication and authorization +- Data synchronization +- Offline capability +- Modern dashboard with analytics +- Mobile-optimized user interface + +## Testing Strategy +- Unit tests for business logic +- Widget tests for UI components +- Integration tests for user flows +- Use flutter_test framework + +## API Architecture +- **Centralized URLs**: All API endpoints defined in `lib/config/api_config.dart` +- **HTTP Service**: Unified REST client in `lib/services/api_service.dart` +- **Authentication**: Google Sign-In + JWT tokens via `lib/services/auth_service.dart` +- **Models**: Type-safe API response models in `lib/models/api_models.dart` +- **Usage Examples**: See `lib/services/example_usage.dart` for implementation patterns + +### API Usage Pattern +```dart +// Get data from API +final response = await ApiService().get(ApiConfig.contacts); +if (response.success) { + final contacts = response.data!.map((json) => Contact.fromJson(json)).toList(); +} + +// Authentication check +if (AuthService().isLoggedIn) { + // Make authenticated requests +} +``` + +## Dependencies Added +- `http: ^1.1.0` - HTTP client for API calls +- `google_sign_in: ^6.1.5` - Google authentication +- `shared_preferences: ^2.2.2` - Local storage for tokens +- `jwt_decoder: ^2.0.1` - JWT token handling + +## Notes for AI Assistant +- Always use centralized API URLs from `ApiConfig` +- All REST requests must go through `ApiService` +- Handle authentication automatically via `AuthService` +- Use type-safe models for all API responses +- Follow Material Design guidelines +- Maintain consistent code style across the project +- Implement proper state management +- Consider mobile-first design principles +- Ensure accessibility compliance +- Use Flutter best practices for performance \ No newline at end of file diff --git a/FIREBASE_SETUP.md b/FIREBASE_SETUP.md new file mode 100644 index 0000000..951391d --- /dev/null +++ b/FIREBASE_SETUP.md @@ -0,0 +1,28 @@ +# Firebase Setup + +## Initial Setup for New Developers + +1. **Google Services Configuration**: + - Copy `android/app/google-services.json.template` to `android/app/google-services.json` + - Replace all placeholder values with your actual Firebase project configuration + - Get this file from the Firebase Console > Project Settings > General > Your apps + +2. **Required Configuration Files**: + - `android/app/google-services.json` - Firebase configuration for Android + - `ios/Runner/GoogleService-Info.plist` - Firebase configuration for iOS (if applicable) + +3. **Environment Variables** (if using): + - Create `.env` file in project root + - Add your API keys and configuration + +## Security Notes + +- Never commit sensitive files like: + - `google-services.json` + - `GoogleService-Info.plist` + - `.keystore` files + - `key.properties` + - Any files containing API keys or secrets + +- These files are in `.gitignore` for security reasons +- Use template files and environment variables for team development diff --git a/README.md b/README.md index c100630..2e7d540 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,561 @@ -# BottleCRM +# BottleCRM Mobile -Free CRM for startups and enterprises. +
+![Flutter](https://img.shields.io/badge/Flutter-3.8.1+-02569B?style=for-the-badge&logo=flutter&logoColor=white) +![Dart](https://img.shields.io/badge/Dart-3.0.0+-0175C2?style=for-the-badge&logo=dart&logoColor=white) +![Android](https://img.shields.io/badge/Android-API%2031+-3DDC84?style=for-the-badge&logo=android&logoColor=white) +![iOS](https://img.shields.io/badge/iOS-11.0+-000000?style=for-the-badge&logo=ios&logoColor=white) +![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge) -# build +[![Get it on Google Play](https://img.shields.io/badge/Get%20it%20on-Google%20Play-black?style=for-the-badge&logo=google-play&logoColor=white)](https://play.google.com/store/apps/details?id=io.bottlecrm) + +
+ +A modern, feature-rich Flutter CRM application for startups and enterprises. Built with a scalable architecture, BottleCRM Mobile provides comprehensive customer relationship management capabilities with multi-tenant support, real-time synchronization, and intuitive user experience. + +## ๐Ÿš€ Overview + +BottleCRM Mobile is designed to streamline your sales and customer management processes with: +- **Multi-tenant Architecture**: Organization-based data isolation and role management +- **Offline-First Design**: Local caching with smart synchronization +- **Modern UI/UX**: Material Design 3 with adaptive layouts +- **Enterprise Security**: OAuth 2.0, JWT tokens, and secure API communication + +## โœจ Features + +### ๐Ÿ” Authentication & Security +- **Google OAuth 2.0**: Seamless single sign-on integration +- **JWT Authentication**: Secure token-based API communication +- **Multi-tenant Support**: Organization-based data isolation +- **Role-based Access**: Granular permissions and user management + +### ๐Ÿ“Š Core CRM Modules +- **Dashboard**: Real-time metrics, KPIs, and activity feeds +- **Contacts**: Complete contact management with search and filtering +- **Leads**: Lead capture, qualification, and conversion tracking +- **Opportunities**: Sales pipeline management with stages +- **Tasks**: Task assignment, tracking, and deadline management +- **Accounts**: Customer account profiles and relationship history +- **Cases**: Customer support ticket management +- **Events**: Calendar integration and meeting scheduling +- **Documents**: File management with cloud storage +- **Teams**: Collaboration tools and team management +- **Invoices**: Billing and invoice generation + +### ๐ŸŽจ User Experience +- **Material Design 3**: Modern, consistent UI patterns +- **Responsive Design**: Optimized for phones, tablets, and foldables +- **Dark/Light Theme**: System-aware theme switching +- **Offline Support**: Local data caching with smart sync +- **Pull-to-Refresh**: Intuitive data refresh patterns +- **Infinite Scrolling**: Smooth pagination for large datasets + +## ๐Ÿ“‹ Requirements + +### System Requirements +- **Flutter SDK**: 3.8.1 or later +- **Dart SDK**: 3.0.0 or later +- **Android Studio/VS Code**: Latest stable version +- **Git**: For version control + +### Platform Support +- **Android**: API level 31+ (Android 12+) +- **iOS**: iOS 11.0+ (for iOS builds) +- **Web**: Modern browsers (Chrome, Firefox, Safari, Edge) +- **Desktop**: Windows, macOS, Linux (experimental) + +### Development Tools +- **Android SDK**: For Android development +- **Xcode**: For iOS development (macOS only) +- **CocoaPods**: iOS dependency management + +## ๐Ÿš€ Getting Started + +### ๐Ÿ“ฑ For End Users + +**Download the app directly from Google Play Store:** + +
+ +[![Get it on Google Play](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=io.bottlecrm) + +*Package ID: `io.bottlecrm`* + +
+ +### ๐Ÿ‘จโ€๐Ÿ’ป For Developers + +### Quick Start + +1. **Clone the repository**: + ```bash + git clone https://github.com/your-username/bottlecrm_mobile_v2.git + cd bottlecrm_mobile_v2 + ``` + +2. **Install Flutter dependencies**: + ```bash + flutter pub get + ``` + +3. **Set up Firebase & Google Sign-In**: + ```bash + # Copy template and configure + cp android/app/google-services.json.template android/app/google-services.json + # Edit the file with your Firebase project configuration + ``` + +4. **Run the application**: + ```bash + flutter run + ``` + +### ๐Ÿ”ง Detailed Setup + +#### Firebase Configuration +1. Create a new Firebase project at [Firebase Console](https://console.firebase.google.com) +2. Enable Google Sign-In authentication +3. Download `google-services.json` for Android +4. Download `GoogleService-Info.plist` for iOS (if building for iOS) +5. Place files in their respective directories: + - Android: `android/app/google-services.json` + - iOS: `ios/Runner/GoogleService-Info.plist` + +#### Google Cloud Console Setup +1. Navigate to [Google Cloud Console](https://console.cloud.google.com) +2. Create OAuth 2.0 Client ID for Android: + ```bash + # Get your SHA-1 fingerprint + cd android + ./gradlew signingReport + ``` +3. Add the SHA-1 fingerprint to your OAuth client +4. Configure OAuth consent screen + +#### Environment Configuration +The app automatically switches between environments: +- **Debug builds**: Uses development API (ngrok URL) +- **Release builds**: Uses production API (bottlecrm.io) + +You can modify URLs in `lib/config/api_config.dart` + +## ๐Ÿ›  Development + +### Development Commands + +```bash +# Run the app with hot reload +flutter run + +# Run with specific device +flutter run -d + +# Run in debug mode with detailed logging +flutter run --debug --verbose + +# Format code +dart format . + +# Analyze code quality +flutter analyze --no-fatal-infos + +# Run tests +flutter test + +# Generate code coverage +flutter test --coverage +``` + +### Code Quality & Standards + +This project follows strict coding standards: +- **Linting**: Uses `flutter_lints` for consistent code style +- **Architecture**: Singleton services with dependency injection +- **Error Handling**: Comprehensive try-catch with user-friendly messages +- **Naming**: Dart conventions (camelCase, PascalCase, snake_case) +- **Documentation**: Comprehensive inline documentation + +### Debugging + +```bash +# Connect to Android device over WiFi +adb connect :5555 + +# View logs +flutter logs + +# Launch DevTools +flutter pub global activate devtools +flutter pub global run devtools +``` + +## ๐Ÿ“ฆ Build & Deployment + +### Android Builds + +```bash +# Clean and prepare +flutter clean +flutter pub get + +# Debug build (uses development API) +flutter build apk --debug + +# Release build (uses production API) +flutter build apk --release + +# App Bundle for Play Store +flutter build appbundle --release + +# Install APK directly +flutter install build/app/outputs/flutter-apk/app-release.apk +``` + +### iOS Builds (macOS only) + +```bash +# Build for iOS +flutter build ios --release + +# Build for iOS Simulator +flutter build ios --debug --simulator +``` + +### Build Configurations + +The app uses different API endpoints based on build type: +- **Debug builds**: Development server (ngrok tunnel) +- **Release builds**: Production API (`https://api.bottlecrm.io`) + +Build configurations are managed in: +- `lib/config/api_config.dart` - API endpoint configuration +- `android/app/build.gradle.kts` - Android build settings +- `ios/Runner.xcodeproj` - iOS build settings + +## ๐Ÿ— Architecture + +BottleCRM Mobile follows a robust, scalable architecture designed for enterprise applications: + +### Core Architecture Patterns + +- **Singleton Services**: All services use the singleton pattern for consistent state management +- **Service-First Design**: Business logic is encapsulated in service classes +- **Repository Pattern**: Data access abstraction with caching layers +- **Multi-tenant Support**: Organization-based data isolation at the API level + +### Project Structure + +``` +lib/ +โ”œโ”€โ”€ app.dart # Main app configuration & routing +โ”œโ”€โ”€ main.dart # Application entry point +โ”œโ”€โ”€ config/ +โ”‚ โ””โ”€โ”€ api_config.dart # Environment-aware API configuration +โ”œโ”€โ”€ models/ +โ”‚ โ””โ”€โ”€ api_models.dart # Type-safe data models with JSON serialization +โ”œโ”€โ”€ services/ # Business logic & API communication +โ”‚ โ”œโ”€โ”€ api_service.dart # HTTP client with auto-authentication +โ”‚ โ”œโ”€โ”€ auth_service.dart # Google OAuth & organization management +โ”‚ โ”œโ”€โ”€ contacts_service.dart +โ”‚ โ”œโ”€โ”€ dashboard_service.dart +โ”‚ โ”œโ”€โ”€ leads_service.dart +โ”‚ โ””โ”€โ”€ tasks_service.dart +โ””โ”€โ”€ screens/ # UI screens organized by feature + โ”œโ”€โ”€ dashboard_screen.dart + โ”œโ”€โ”€ login_screen.dart + โ”œโ”€โ”€ company_selection_screen.dart + โ””โ”€โ”€ ... +``` + +### Key Architecture Components + +#### 1. Authentication Flow +``` +Login โ†’ Google OAuth โ†’ JWT Token โ†’ Organization Selection โ†’ Dashboard +``` + +#### 2. Service Pattern +```dart +class SomeService { + static final SomeService _instance = SomeService._internal(); + factory SomeService() => _instance; + SomeService._internal(); + + // Service methods... +} +``` + +#### 3. API Communication +- Centralized HTTP client with automatic JWT token injection +- Organization header (`X-Organization-ID`) for multi-tenant requests +- Automatic logout on 401 responses +- Comprehensive error handling with user-friendly messages + +#### 4. State Management +- Service-based state management with singleton pattern +- Local caching using SharedPreferences +- Reactive UI updates with StatefulWidget patterns +- Navigation state preserved with IndexedStack + +### Data Flow + +1. **Authentication**: Google OAuth โ†’ JWT storage โ†’ Organization selection +2. **API Requests**: Service โ†’ ApiService โ†’ HTTP โ†’ Backend API +3. **Data Processing**: JSON โ†’ Model classes โ†’ UI widgets +4. **Local Storage**: Critical data cached in SharedPreferences +5. **Error Handling**: Service level โ†’ UI feedback โ†’ User notification + +For detailed architectural guidelines, see [CLAUDE.md](CLAUDE.md) and [.github/copilot-instructions.md](.github/copilot-instructions.md). + +## โš™๏ธ Configuration + +### API Configuration + +The application uses environment-based API URLs configured in `lib/config/api_config.dart`: + +```dart +// Development (debug builds) +static const String _developmentUrl = 'https://b2ad5166b831.ngrok-free.app'; + +// Production (release builds) +static const String _productionUrl = 'https://api.bottlecrm.io'; +``` + +**Environment Detection**: Automatic switching based on `kDebugMode` flag + +### Authentication Setup + +#### Google Sign-In Configuration + +1. **Firebase Console Setup**: + - Create Firebase project + - Enable Google Sign-In authentication provider + - Configure OAuth consent screen + +2. **Get Android SHA-1 fingerprint**: + ```bash + cd android + ./gradlew signingReport + ``` + +3. **Google Cloud Console**: + - Create OAuth 2.0 Client ID for Android + - Add SHA-1 fingerprint to OAuth client + - Download `google-services.json` + +4. **Firebase Configuration Files**: + ```bash + android/app/google-services.json # Android configuration + ios/Runner/GoogleService-Info.plist # iOS configuration (if applicable) + ``` + +#### Authentication Flow + +``` +1. User clicks "Sign in with Google" +2. Google OAuth flow โ†’ JWT token received +3. Token stored in SharedPreferences +4. User selects organization from available list +5. Organization ID added to all API requests via X-Organization-ID header +6. Dashboard and metadata loaded for selected organization +``` + +### Multi-tenancy Configuration + +- **Organization-based Data Isolation**: All API requests include organization context +- **Automatic Header Injection**: `ApiService` adds `X-Organization-ID` to requests +- **Organization Switching**: Clear cached data when switching organizations +- **Role-based Access**: Different permissions based on user role in organization + +### Storage Configuration + +- **JWT Tokens**: Stored securely in SharedPreferences +- **Organization Data**: Cached locally for offline access +- **Metadata Caching**: Lead statuses, sources, and other metadata cached per organization +- **Auto-cleanup**: Cached data cleared on logout and organization switch + +## ๐Ÿ“š Dependencies + +### Core Dependencies + +| Package | Version | Purpose | +|---------|---------|---------| +| `flutter` | SDK | Flutter framework | +| `cupertino_icons` | ^1.0.8 | iOS-style icons | +| `http` | ^1.1.0 | HTTP client for API requests | +| `google_sign_in` | ^7.1.1 | Google OAuth authentication | +| `shared_preferences` | ^2.2.2 | Local data persistence | +| `jwt_decoder` | ^2.0.1 | JWT token parsing | +| `font_awesome_flutter` | ^10.7.0 | FontAwesome icons | +| `intl` | ^0.19.0 | Internationalization support | +| `package_info_plus` | ^8.0.2 | App version and build info | + +### Development Dependencies + +| Package | Version | Purpose | +|---------|---------|---------| +| `flutter_test` | SDK | Testing framework | +| `flutter_lints` | ^6.0.0 | Dart linting rules | +| `flutter_launcher_icons` | ^0.14.4 | App icon generation | + +### Key Features by Dependency + +- **Authentication**: Google Sign-In with JWT token management +- **HTTP Communication**: Robust API client with error handling +- **Local Storage**: Secure token and metadata caching +- **UI Components**: Material Design with FontAwesome icons +- **Code Quality**: Comprehensive linting and formatting + +## ๐Ÿงช Testing + +### Test Structure +```bash +test/ +โ”œโ”€โ”€ unit/ # Unit tests for services and models +โ”œโ”€โ”€ widget/ # Widget tests for UI components +โ””โ”€โ”€ integration/ # End-to-end integration tests +``` + +### Running Tests +```bash +# Run all tests +flutter test + +# Run with coverage +flutter test --coverage + +# Run specific test file +flutter test test/unit/auth_service_test.dart +``` + +### Testing Guidelines +- **Unit Tests**: All service methods and model classes +- **Widget Tests**: Critical UI components and interactions +- **Integration Tests**: End-to-end user flows +- **Mocking**: Use mockito for external dependencies + +## ๐Ÿš€ Performance Optimization + +### App Performance +- **Lazy Loading**: Screens and data loaded on demand +- **Image Optimization**: Compressed assets and caching +- **Memory Management**: Proper disposal of controllers and streams +- **Network Optimization**: Request batching and intelligent caching + +## ๐Ÿ”ง Troubleshooting + +### Common Issues + +#### Firebase/Google Sign-In Issues +```bash +# Problem: Google Sign-In not working +# Solution: Check SHA-1 fingerprint configuration +cd android && ./gradlew signingReport + +# Problem: google-services.json not found +# Solution: Ensure file is in correct location +ls android/app/google-services.json +``` + +#### Build Issues +```bash +# Problem: Build failures after dependency updates +# Solution: Clean and rebuild flutter clean flutter pub get -flutter build appbundle -flutter build apk +flutter run + +# Problem: Android build issues +# Solution: Verify Android SDK and NDK versions +flutter doctor -v +``` + +#### API Connection Issues +```bash +# Problem: API requests failing in debug mode +# Solution: Check ngrok tunnel status and update URL in api_config.dart + +# Problem: 401 Unauthorized errors +# Solution: Clear app data and re-authenticate +flutter clean +# Uninstall app from device and reinstall +``` + +### Development Tips + +1. **Hot Reload**: Use `r` in terminal for hot reload during development +2. **Hot Restart**: Use `R` for hot restart when changing app state +3. **DevTools**: Use Flutter DevTools for debugging and performance analysis +4. **Logging**: Check console output for detailed error information + +## ๐Ÿค Contributing + +### Development Workflow + +1. **Fork the repository** +2. **Create a feature branch**: `git checkout -b feature/amazing-feature` +3. **Follow coding standards**: Use `dart format` and `flutter analyze` +4. **Write tests**: Ensure adequate test coverage +5. **Commit changes**: Use conventional commit messages +6. **Create Pull Request**: Provide detailed description + +### Code Style Guidelines + +- Follow Dart naming conventions +- Use meaningful variable and function names +- Add inline documentation for complex logic +- Implement proper error handling +- Write unit tests for new features + +### Commit Message Format +``` +type(scope): description + +feat(auth): add biometric authentication +fix(api): resolve null pointer exception +docs(readme): update installation instructions +``` + +## ๐Ÿ“„ License + +``` +MIT License + +Copyright (c) 2024 BottleCRM + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## ๐ŸŒŸ Support + +- ** Documentation**: [CLAUDE.md](CLAUDE.md) for detailed architecture information +- **๐Ÿ› Issues**: [GitHub Issues](https://github.com/your-username/bottlecrm_mobile_v2/issues) for bug reports +- **๐Ÿ’ฌ Discussions**: [GitHub Discussions](https://github.com/your-username/bottlecrm_mobile_v2/discussions) for questions +- **๐Ÿ”ฅ Firebase Setup**: [FIREBASE_SETUP.md](FIREBASE_SETUP.md) for Firebase configuration + +--- + +
+ +**BottleCRM Mobile** - Free CRM for startups and enterprises + +Made with โค๏ธ using Flutter + +
diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore index 0a741cb..be3943c 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,7 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index d3ea905..0000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" - id "com.google.gms.google-services" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -android { - namespace "io.bottlecrm" - compileSdk 35 - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = '11' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "io.bottlecrm" - minSdkVersion 23 - targetSdkVersion 35 - multiDexEnabled true - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation platform('com.google.firebase:firebase-bom:33.7.0') - implementation 'com.google.firebase:firebase-analytics' -} diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..2177e21 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,69 @@ +import java.util.Properties +import java.io.FileInputStream + +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") + id("com.google.gms.google-services") +} +dependencies { + // ... + implementation("com.google.android.material:material:1.12.0") + // ... +} + +val keystoreProperties = Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) +} + +android { + namespace = "io.bottlecrm" + compileSdk = flutter.compileSdkVersion + ndkVersion = "27.0.12077973" // Use latest available NDK + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "io.bottlecrm" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 31 // Android 12 (API 31) + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + signingConfigs { + create("release") { + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = keystoreProperties["storeFile"]?.let { file(it) } + storePassword = keystoreProperties["storePassword"] as String + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + // signingConfig = signingConfigs.getByName("debug") + signingConfig = signingConfigs.getByName("release") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/google-services.json b/android/app/google-services.json deleted file mode 100644 index 642c4df..0000000 --- a/android/app/google-services.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "project_info": { - "project_number": "74115878447", - "project_id": "bottle-crm-10074", - "storage_bucket": "bottle-crm-10074.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:74115878447:android:858fb769df26bb2c5900da", - "android_client_info": { - "package_name": "io.bottlecrm" - } - }, - "oauth_client": [ - { - "client_id": "74115878447-0r10i3hk3mma0u2vaenbsbtvvtud5sv5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD8es-uwk_IlZsuHWlEwfiEZfsrENKXRgA" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "74115878447-0r10i3hk3mma0u2vaenbsbtvvtud5sv5.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 4278496..399f698 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0003f30..49ee323 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,18 +1,14 @@ - - - - + - + - - @@ -45,4 +32,15 @@ android:name="flutterEmbedding" android:value="2" /> - \ No newline at end of file + + + + + + + + diff --git a/android/app/src/main/kotlin/io/bottlecrm/MainActivity.kt b/android/app/src/main/kotlin/io/bottlecrm/MainActivity.kt index 8994983..56a504c 100644 --- a/android/app/src/main/kotlin/io/bottlecrm/MainActivity.kt +++ b/android/app/src/main/kotlin/io/bottlecrm/MainActivity.kt @@ -2,5 +2,4 @@ package io.bottlecrm import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png index 0c1124c..51cace0 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png index 84e1269..52c7931 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png index 4881b68..a5d2ddf 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png index 3fbc6ca..45e4a7f 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png index ab8c484..0eea747 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 449a9f9..573347a 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -3,16 +3,16 @@ - diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index d74aa35..a3fb32f 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -3,16 +3,17 @@ - + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 4278496..399f698 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index e9d7fb9..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,50 +0,0 @@ -buildscript { - ext.kotlin_version = '2.1.0' - repositories { - google() - mavenCentral() - maven { url 'https://jitpack.io' } - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.7.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.gms:google-services:4.4.2' - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -subprojects { - afterEvaluate { project -> - if (project.hasProperty("android")) { - project.android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - } - } - - project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { - kotlinOptions { - jvmTarget = '11' - } - } - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.layout.buildDirectory -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..a58274b --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,31 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.google.gms:google-services:4.4.0") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties index d582b46..f018a61 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,6 +1,3 @@ -org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -android.enableR8=true -android.nonTransitiveRClass=true -android.nonFinalResIds=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index db18181..ac3b479 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 433f611..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.3" apply false - id "org.jetbrains.kotlin.android" version "2.1.0" apply false - id "com.google.gms.google-services" version "4.4.2" apply false -} - -include ":app" diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ab39a10 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/android/settings_aar.gradle b/android/settings_aar.gradle deleted file mode 100644 index e7b4def..0000000 --- a/android/settings_aar.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/assets/icon/icon.png b/assets/icon/icon.png new file mode 100644 index 0000000..fbac8d9 Binary files /dev/null and b/assets/icon/icon.png differ diff --git a/assets/images/Icon_edit_color.svg b/assets/images/Icon_edit_color.svg deleted file mode 100644 index 7113be8..0000000 --- a/assets/images/Icon_edit_color.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/accounts.svg b/assets/images/accounts.svg deleted file mode 100644 index 3edb8d7..0000000 --- a/assets/images/accounts.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/images/accounts_color.svg b/assets/images/accounts_color.svg deleted file mode 100644 index 6a09e86..0000000 --- a/assets/images/accounts_color.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/images/arrow_backward.svg b/assets/images/arrow_backward.svg deleted file mode 100644 index 51a8bfa..0000000 --- a/assets/images/arrow_backward.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/arrow_forward.svg b/assets/images/arrow_forward.svg deleted file mode 100644 index 63b0df0..0000000 --- a/assets/images/arrow_forward.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/bg-image-blue.jpg b/assets/images/bg-image-blue.jpg deleted file mode 100644 index 2adb050..0000000 Binary files a/assets/images/bg-image-blue.jpg and /dev/null differ diff --git a/assets/images/bg-image.png b/assets/images/bg-image.png deleted file mode 100644 index 49d6742..0000000 Binary files a/assets/images/bg-image.png and /dev/null differ diff --git a/assets/images/cases.svg b/assets/images/cases.svg deleted file mode 100644 index bbfe30b..0000000 --- a/assets/images/cases.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/change_password.svg b/assets/images/change_password.svg deleted file mode 100644 index 9d503c6..0000000 --- a/assets/images/change_password.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/images/contacts.svg b/assets/images/contacts.svg deleted file mode 100644 index b127ba7..0000000 --- a/assets/images/contacts.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/dashboard_icon.svg b/assets/images/dashboard_icon.svg deleted file mode 100644 index 0873b39..0000000 --- a/assets/images/dashboard_icon.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/assets/images/documents.svg b/assets/images/documents.svg deleted file mode 100644 index 112df26..0000000 --- a/assets/images/documents.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/download_icon.svg b/assets/images/download_icon.svg deleted file mode 100644 index ef421c3..0000000 --- a/assets/images/download_icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/events.svg b/assets/images/events.svg deleted file mode 100644 index dfce83a..0000000 --- a/assets/images/events.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/filter.svg b/assets/images/filter.svg deleted file mode 100644 index 5de4314..0000000 --- a/assets/images/filter.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/images/flag.svg b/assets/images/flag.svg deleted file mode 100644 index 978b8cd..0000000 --- a/assets/images/flag.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/images/google-icon.png b/assets/images/google-icon.png deleted file mode 100644 index 4bc4e1d..0000000 Binary files a/assets/images/google-icon.png and /dev/null differ diff --git a/assets/images/home.svg b/assets/images/home.svg deleted file mode 100644 index 4c6204e..0000000 --- a/assets/images/home.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/images/icon_close.svg b/assets/images/icon_close.svg deleted file mode 100644 index d2a188d..0000000 --- a/assets/images/icon_close.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/icon_delete_color.svg b/assets/images/icon_delete_color.svg deleted file mode 100644 index 598bbe9..0000000 --- a/assets/images/icon_delete_color.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/assets/images/identification.svg b/assets/images/identification.svg deleted file mode 100644 index 1cb79e8..0000000 --- a/assets/images/identification.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/invoices.svg b/assets/images/invoices.svg deleted file mode 100644 index 43c13d4..0000000 --- a/assets/images/invoices.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/leads.svg b/assets/images/leads.svg deleted file mode 100644 index 10f6caa..0000000 --- a/assets/images/leads.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/logo.svg b/assets/images/logo.svg deleted file mode 100644 index 515d279..0000000 --- a/assets/images/logo.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CRM - - - - diff --git a/assets/images/logo_google.svg b/assets/images/logo_google.svg deleted file mode 100644 index 2cc84b8..0000000 --- a/assets/images/logo_google.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/logout.svg b/assets/images/logout.svg deleted file mode 100644 index fb956bd..0000000 --- a/assets/images/logout.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/images/menu.svg b/assets/images/menu.svg deleted file mode 100644 index b845e77..0000000 --- a/assets/images/menu.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/more.svg b/assets/images/more.svg deleted file mode 100644 index e6d6615..0000000 --- a/assets/images/more.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/new_logo.png b/assets/images/new_logo.png deleted file mode 100644 index d3ee457..0000000 Binary files a/assets/images/new_logo.png and /dev/null differ diff --git a/assets/images/opportunities.svg b/assets/images/opportunities.svg deleted file mode 100644 index 6987a65..0000000 --- a/assets/images/opportunities.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/assets/images/opportunities_color.svg b/assets/images/opportunities_color.svg deleted file mode 100644 index c54089b..0000000 --- a/assets/images/opportunities_color.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/assets/images/pdf_icon.svg b/assets/images/pdf_icon.svg deleted file mode 100644 index 8c9430a..0000000 --- a/assets/images/pdf_icon.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/search.svg b/assets/images/search.svg deleted file mode 100644 index 2497e94..0000000 --- a/assets/images/search.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/assets/images/settings.svg b/assets/images/settings.svg deleted file mode 100644 index de77c38..0000000 --- a/assets/images/settings.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/skype.png b/assets/images/skype.png deleted file mode 100644 index 6f6fecc..0000000 Binary files a/assets/images/skype.png and /dev/null differ diff --git a/assets/images/tasks.svg b/assets/images/tasks.svg deleted file mode 100644 index 8b688ae..0000000 --- a/assets/images/tasks.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/images/teams.svg b/assets/images/teams.svg deleted file mode 100644 index 47d7889..0000000 --- a/assets/images/teams.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/users.svg b/assets/images/users.svg deleted file mode 100644 index 4b808c1..0000000 --- a/assets/images/users.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ios/.gitignore b/ios/.gitignore index 151026b..7a7f987 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 9367d48..7c56964 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index ec97fc6..592ceee 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index c4855bf..592ceee 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile deleted file mode 100644 index 1e8c3c9..0000000 --- a/ios/Podfile +++ /dev/null @@ -1,41 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '9.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index ba6961a..0000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,85 +0,0 @@ -PODS: - - connectivity (0.0.1): - - Flutter - - Reachability - - DKImagePickerController/Core (4.3.2): - - DKImagePickerController/ImageDataManager - - DKImagePickerController/Resource - - DKImagePickerController/ImageDataManager (4.3.2) - - DKImagePickerController/PhotoGallery (4.3.2): - - DKImagePickerController/Core - - DKPhotoGallery - - DKImagePickerController/Resource (4.3.2) - - DKPhotoGallery (0.0.17): - - DKPhotoGallery/Core (= 0.0.17) - - DKPhotoGallery/Model (= 0.0.17) - - DKPhotoGallery/Preview (= 0.0.17) - - DKPhotoGallery/Resource (= 0.0.17) - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Core (0.0.17): - - DKPhotoGallery/Model - - DKPhotoGallery/Preview - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Model (0.0.17): - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Preview (0.0.17): - - DKPhotoGallery/Model - - DKPhotoGallery/Resource - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Resource (0.0.17): - - SDWebImage - - SwiftyGif - - file_picker (0.0.1): - - DKImagePickerController/PhotoGallery - - Flutter - - Flutter (1.0.0) - - Reachability (3.2) - - SDWebImage (5.10.4): - - SDWebImage/Core (= 5.10.4) - - SDWebImage/Core (5.10.4) - - shared_preferences (0.0.1): - - Flutter - - SwiftyGif (5.4.0) - -DEPENDENCIES: - - connectivity (from `.symlinks/plugins/connectivity/ios`) - - file_picker (from `.symlinks/plugins/file_picker/ios`) - - Flutter (from `Flutter`) - - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - -SPEC REPOS: - trunk: - - DKImagePickerController - - DKPhotoGallery - - Reachability - - SDWebImage - - SwiftyGif - -EXTERNAL SOURCES: - connectivity: - :path: ".symlinks/plugins/connectivity/ios" - file_picker: - :path: ".symlinks/plugins/file_picker/ios" - Flutter: - :path: Flutter - shared_preferences: - :path: ".symlinks/plugins/shared_preferences/ios" - -SPEC CHECKSUMS: - connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 - DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d - DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c - Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 - shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - SwiftyGif: 5d4af95df24caf1c570dbbcb32a3b8a0763bc6d7 - -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c - -COCOAPODS: 1.10.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index cd3b7c1..019b0d2 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,19 +3,29 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 7A51D37E4B3C07A9E4AAB281 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E13FE53387896326A28CCC4C /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,10 +42,9 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3C58ADA2AF9A36231637C45A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 4A6BAEEE224906606BFF323C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 5991D44B20C96AC7C24A394E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -46,7 +55,6 @@ 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 = ""; }; - E13FE53387896326A28CCC4C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,22 +62,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7A51D37E4B3C07A9E4AAB281 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 8BE154EB6786E3C4EC629ADE /* Pods */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - 3C58ADA2AF9A36231637C45A /* Pods-Runner.debug.xcconfig */, - 4A6BAEEE224906606BFF323C /* Pods-Runner.release.xcconfig */, - 5991D44B20C96AC7C24A394E /* Pods-Runner.profile.xcconfig */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); - name = Pods; - path = Pods; + path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -89,8 +93,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 8BE154EB6786E3C4EC629ADE /* Pods */, - D604E0DDD2E747A026FD7085 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -98,6 +101,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -117,29 +121,36 @@ path = Runner; sourceTree = ""; }; - D604E0DDD2E747A026FD7085 /* Frameworks */ = { - isa = PBXGroup; - children = ( - E13FE53387896326A28CCC4C /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 2BA59BFA6896C716E21F8987 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 07EEC1E0A643CCF1974E7732 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -156,9 +167,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -179,11 +195,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -198,51 +222,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 07EEC1E0A643CCF1974E7732 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 2BA59BFA6896C716E21F8987 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -253,6 +240,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -268,6 +256,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -279,6 +275,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -303,6 +307,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -332,6 +337,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -340,7 +346,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -358,7 +364,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -367,10 +376,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -400,6 +457,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -414,7 +472,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -426,6 +484,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -455,6 +514,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -463,11 +523,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -482,7 +543,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -501,7 +565,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -513,6 +580,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..e3773d4 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + + + + + @@ -61,8 +73,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - - - diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..6266644 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "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" - } -} +{"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":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"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":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@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"}} \ No newline at end of file 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 index b9c83c6..54f7c8b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png 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 index bb03bf8..515c031 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png 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 index 4884bd8..a2efca6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png 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 index fa4a858..999e6e4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png 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 index c4fbdef..add7e42 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png 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 index 90aee90..9db4e88 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png 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 index cf1255b..0098d6e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png 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 index 4884bd8..a2efca6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png 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 index 44d5c44..ee28bd0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png 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 index 3d5a1be..2231691 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png 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..faccfa8 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..e8a4b62 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..f3aaf10 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..1ceac7c 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 index 3d5a1be..2231691 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png 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 index 69f5fde..c0cffe2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png 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..6672cdd 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..586a567 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 index a6c7c25..eaefaf8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png 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 index 6d57b36..37006b2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png 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 index 7d1e973..436ed14 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index bd4d3c1..777b9f4 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Bottlecrm CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..b635ecb --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'screens/login_screen.dart'; +import 'screens/dashboard_screen.dart'; +import 'screens/company_selection_screen.dart'; +import 'screens/company_create_screen.dart'; +import 'screens/lead_detail_screen.dart'; +import 'screens/lead_create_screen.dart'; +import 'screens/contacts_list_screen.dart'; +import 'screens/contact_create_screen.dart'; +import 'screens/contact_detail_screen.dart'; +import 'screens/tasks_list_screen.dart'; +import 'screens/task_create_screen.dart'; +import 'screens/task_detail_screen.dart'; +import 'screens/task_edit_screen.dart'; +import 'screens/about_screen.dart'; +import 'screens/help_support_screen.dart'; +import 'services/auth_service.dart'; +import 'services/leads_service.dart'; + +class BottleCrmApp extends StatelessWidget { + const BottleCrmApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'BottleCRM', + debugShowCheckedModeBanner: false, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xFF2563EB), // Blue color for CRM + brightness: Brightness.light, + ), + useMaterial3: true, + appBarTheme: const AppBarTheme(centerTitle: false, elevation: 0), + cardTheme: CardThemeData( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), + ), + ), + ), + home: const AuthWrapper(), + routes: { + '/login': (context) => const LoginScreen(), + '/company-selection': (context) => const CompanySelectionScreen(), + '/company-create': (context) => const CompanyCreateScreen(), + '/dashboard': (context) => const DashboardScreen(), + '/about': (context) => const AboutScreen(), + '/help-support': (context) => const HelpSupportScreen(), + '/contacts': (context) => const ContactsListScreen(), + '/contact-create': (context) => const ContactCreateScreen(), + '/contact-detail': (context) { + final contactId = + ModalRoute.of(context)!.settings.arguments as String; + return ContactDetailScreen(contactId: contactId); + }, + '/lead-detail': (context) { + final leadId = ModalRoute.of(context)!.settings.arguments as String; + return LeadDetailScreen(leadId: leadId); + }, + '/lead-create': (context) => const LeadCreateScreen(), + '/tasks': (context) => const TasksListScreen(), + '/task-create': (context) => const TaskCreateScreen(), + '/task-detail': (context) { + final taskId = ModalRoute.of(context)!.settings.arguments as String; + return TaskDetailScreen(taskId: taskId); + }, + '/task-edit': (context) { + final taskId = ModalRoute.of(context)!.settings.arguments as String; + return TaskEditScreen(taskId: taskId); + }, + }, + ); + } +} + +class AuthWrapper extends StatefulWidget { + const AuthWrapper({super.key}); + + @override + State createState() => _AuthWrapperState(); +} + +class _AuthWrapperState extends State { + final AuthService _authService = AuthService(); + final LeadsService _leadsService = LeadsService(); + bool _isInitializing = true; + + @override + void initState() { + super.initState(); + _initializeAuth(); + } + + Future _initializeAuth() async { + try { + await _authService.initialize(); + await _leadsService.initialize(); + } catch (e) { + debugPrint('Auth initialization error: $e'); + } finally { + if (mounted) { + setState(() { + _isInitializing = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + if (_isInitializing) { + return const MaterialApp( + home: Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Loading BottleCRM...'), + ], + ), + ), + ), + ); + } + + // Check authentication status and organization selection + if (!_authService.isLoggedIn) { + return const LoginScreen(); + } + + // If logged in but no organization selected, show company selection + if (!_authService.hasSelectedOrganization) { + return const CompanySelectionScreen(); + } + + // If logged in and organization selected, show dashboard + return const DashboardScreen(); + } +} diff --git a/lib/bloc/account_bloc.dart b/lib/bloc/account_bloc.dart deleted file mode 100644 index 96ec181..0000000 --- a/lib/bloc/account_bloc.dart +++ /dev/null @@ -1,331 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:bottle_crm/bloc/dashboard_bloc.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; -import 'package:bottle_crm/model/account.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/services/crm_services.dart'; - -class AccountBloc { - List _openAccounts = []; - List _closedAccounts = []; - Account? _currentAccount; - String _currentAccountType = "Open"; - int? _currentAccountIndex; - String? _currentEditAccountId = ""; - Map _currentEditAccount = { - "name": "", - "website": "", - "phone": "", - "email": "", - "lead": null, - "billing_address_line": "", - "billing_street": "", - "billing_postcode": "", - "billing_city": "", - "billing_state": "", - "billing_country": null, - "contacts": [], - "teams": [], - "assigned_to": [], - "status": "open", - "tags": [], - "description":"", - }; - String _offset = ""; - - List _assignedToList = []; - List countriesList = []; - List? _tags = []; - List _filterTags = []; - - Future fetchAccounts({filtersData}) async { - try { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - if (filtersData != null) { - _copyFiltersData!['tags'] = _copyFiltersData['tags'].length > 0 - ? jsonEncode(_copyFiltersData['tags']) - : ""; - } - await CrmService() - .getAccounts(queryParams: _copyFiltersData, offset: _offset) - .then((response) { - var res = (json.decode(response.body)); - _assignedToList.clear(); - - res['active_accounts']['open_accounts'].forEach((_account) { - leadBloc.countriesList!.forEach((country) { - if (_account!["billing_country"] == country[0]) { - _account!["billing_country"] = country[1]; - } - }); - Account account = Account.fromJson(_account); - _openAccounts.add(account); - }); - res['closed_accounts']['close_accounts'].forEach((_account) { - leadBloc.countriesList!.forEach((country) { - if (_account!["billing_country"] == country[0]) { - _account!["billing_country"] = country[1]; - } - }); - Account account = Account.fromJson(_account); - _closedAccounts.add(account); - }); - if (res['users'] != null) - res['users'].forEach((_user) { - Profile user = Profile.fromJson(_user); - _assignedToList.add({"name": user.firstName, "id": user.id}); - }); - _filterTags = res['tags'] != null ? res['tags'] : []; - _offset = res['active_accounts']['offset'] != null && - res['active_accounts']['offset'].toString() != "0" - ? res['active_accounts']['offset'].toString() - : res['closed_accounts']['offset'] != null && - res['closed_accounts']['offset'].toString() != "0" - ? res['closed_accounts']['offset'].toString() - : ""; - }).catchError((onError) { - print("fetchAccounts Error>> $onError"); - }); - } catch (e) {} - } - - Future createAccount({File? file}) async { - print(_currentEditAccount); - try { - Map result = {}; - Map _copyCurrentEditAccount = new Map.from(_currentEditAccount); - _copyCurrentEditAccount['contacts'] = (_copyCurrentEditAccount['contacts'] - .map((contact) => contact.toString())).toList().toString(); - _copyCurrentEditAccount['teams'] = (_copyCurrentEditAccount['teams'] - .map((team) => team.toString())).toList().toString(); - _copyCurrentEditAccount['assigned_to'] = - (_copyCurrentEditAccount['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - _copyCurrentEditAccount['status'] = - _copyCurrentEditAccount['status'].toLowerCase(); - leadBloc.countriesList!.forEach((country) { - if (country![1] == _copyCurrentEditAccount['billing_country']) { - _copyCurrentEditAccount['billing_country'] = country[0]; - } - }); - leadBloc.openLeads.forEach((lead) { - if (lead.title == _copyCurrentEditAccount['lead']) { - _copyCurrentEditAccount['lead'] = lead.id.toString(); - } - }); - _copyCurrentEditAccount['tags'] = - jsonEncode(_copyCurrentEditAccount['tags']); - print(_copyCurrentEditAccount); - await CrmService() - .createAccount(_copyCurrentEditAccount, file!) - .then((response) async { - print(_copyCurrentEditAccount); - var res = json.decode(response.body); - if (res["error"] == false) { - offset = ""; - await fetchAccounts(); - await dashboardBloc.fetchDashboardDetails(); - } - result = res; - }) - .catchError((onError) { - print("createAccount Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } catch (e) {} - } - - Future editAccount() async { - Map? result; - Map _copyCurrentEditAccount = new Map.from(_currentEditAccount); - countriesList = leadBloc.countries; - _copyCurrentEditAccount['contacts'] = (_copyCurrentEditAccount['contacts'] - .map((contact) => contact.toString())).toList().toString(); - _copyCurrentEditAccount['teams'] = (_copyCurrentEditAccount['teams'] - .map((team) => team.toString())).toList().toString(); - _copyCurrentEditAccount['assigned_to'] = - (_copyCurrentEditAccount['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - _copyCurrentEditAccount['status'] = - _copyCurrentEditAccount['status'].toLowerCase(); - leadBloc.countriesList!.forEach((country) { - if (country![1] == _copyCurrentEditAccount['billing_country']) { - _copyCurrentEditAccount['billing_country'] = country[0]; - } - }); - leadBloc.openLeads.forEach((lead) { - if (lead.title == _copyCurrentEditAccount['lead']) { - _copyCurrentEditAccount['lead'] = lead.id.toString(); - } - }); - _copyCurrentEditAccount['tags'] = - jsonEncode(_copyCurrentEditAccount['tags']); - await CrmService() - .editAccount(_copyCurrentEditAccount, _currentEditAccountId) - .then((response) async { - var res = json.decode(response.body); - if (res["error"] == false) { - offset = ""; - await fetchAccounts(); - await dashboardBloc.fetchDashboardDetails(); - } - result = res; - }).catchError((onError) { - print("editAccount Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - Future deleteAccount(Account account) async { - Map? result = {}; - await CrmService().deleteAccount(account.id).then((response) async { - var res = (json.decode(response.body)); - await fetchAccounts(); - await dashboardBloc.fetchDashboardDetails(); - result = res; - }).catchError((onError) { - print("deleteAccount Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - resetAccountFields() { - _currentEditAccountId = null; - _currentEditAccount = { - "name": "", - "website": "", - "phone": "", - "email": "", - "lead": null, - "billing_address_line": "", - "billing_street": "", - "billing_postcode": "", - "billing_city": "", - "billing_state": "", - "billing_country": null, - "contacts": [], - "teams": [], - "assigned_to": [], - "status": "open", - "tags": [], - }; - _tags = []; - } - - updateCurrentEditAccount(Account editAccount) { - List contacts = []; - List teams = []; - List assignedUsers = []; - List? tags = []; - _currentEditAccountId = editAccount.id.toString(); - editAccount.contacts!.forEach((contact) { - contacts.add(contact.id); - }); - - editAccount.assignedTo!.forEach((assignedAccount) { - assignedUsers.add(assignedAccount.id); - }); - - editAccount.teams!.forEach((team) { - teams.add(team.id); - }); - - editAccount.tags!.forEach((tag) { - tags.add(tag['name']!); - }); - - _currentEditAccount['name'] = editAccount.name; - _currentEditAccount['website'] = editAccount.website; - _currentEditAccount['phone'] = editAccount.phone; - _currentEditAccount['email'] = editAccount.email; - _currentEditAccount['lead'] = editAccount.lead!.title; - _currentEditAccount['billing_address_line'] = - editAccount.billingAddressLine; - _currentEditAccount['billing_street'] = editAccount.billingStreet; - _currentEditAccount['billing_postcode'] = editAccount.billingPostcode; - _currentEditAccount['billing_city'] = editAccount.billingCity; - _currentEditAccount['billing_state'] = editAccount.billingState; - _currentEditAccount['billing_country'] = editAccount.billingCountry; - _currentEditAccount['contacts'] = contacts; - _currentEditAccount['teams'] = teams; - _currentEditAccount['assigned_to'] = assignedUsers; - _currentEditAccount['status'] = editAccount.status; - _currentEditAccount['tags'] = tags; - _tags = tags; - } - - List get openAccounts { - return _openAccounts; - } - - List get closedAccounts { - return _closedAccounts; - } - - List get assignedToList { - return _assignedToList; - } - - Map get currentEditAccount { - return _currentEditAccount; - } - - set currentEditAccount(currentEditAccount) { - _currentEditAccount = currentEditAccount; - } - - List? get tags { - return _tags; - } - - List get filterTags { - return _filterTags; - } - - String? get currentEditAccountId { - return _currentEditAccountId; - } - - set currentEditAccountId(id) { - _currentEditAccountId = id; - } - - Account? get currentAccount { - return _currentAccount; - } - - set currentAccount(account) { - _currentAccount = account; - } - - int? get currentAccountIndex { - return _currentAccountIndex; - } - - set currentAccountIndex(index) { - _currentAccountIndex = index; - } - - String get currentAccountType { - return _currentAccountType; - } - - set currentAccountType(type) { - _currentAccountType = type; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } -} - -final accountBloc = AccountBloc(); diff --git a/lib/bloc/auth_bloc.dart b/lib/bloc/auth_bloc.dart deleted file mode 100644 index 2319b1a..0000000 --- a/lib/bloc/auth_bloc.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'dart:convert'; - -import 'package:bottle_crm/model/organization.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/services/crm_services.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class AuthBloc { - String? _subDomainName; - String? _authToken; - Profile? _userProfile; - List _companies = []; - Organization? _selectedOrganization; - - String? get subDomainName { - return _subDomainName; - } - - String? get authToken { - return _authToken; - } - - Profile? get userProfile { - return _userProfile; - } - - List get companies { - return _companies; - } - - Organization? get selectedOrganization { - return _selectedOrganization; - } - - set selectedOrganization(selectedOrganization) { - _selectedOrganization = selectedOrganization; - } - - Future validateDomain(data) async { - Map? result; - await CrmService().validateSubdomain(data).then((response) { - var res = (json.decode(response.body)); - if (res['error'] == false) { - _subDomainName = data['sub_domain']; - result = res; - } else { - result = res; - } - }).catchError((onError) { - print("validate domain error>> $onError"); - result = {"status": "error", "message": "Something went wrong"}; - }); - return result; - } - - Future login(data) async { - try { - Map result = {}; - final SharedPreferences preferences = - await SharedPreferences.getInstance(); - await CrmService().userLogin(data).then((response) async { - var res = (json.decode(response.body)); - if (res['error'] == false) { - _authToken = "JWT " + res['token']; - preferences.setString("authToken", _authToken!); - result = res; - } else { - result = res; - } - }).catchError((onError) { - print("login error>> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } catch (e) {} - } - - Future forgotPassword(data) async { - Map? result; - await CrmService().forgotPassword(data).then((response) { - var res = (json.decode(response.body)); - result = res; - }).catchError((onError) { - print("forgot password Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - Future register(data) async { - Map? result = {}; - await CrmService().userRegister(data).then((response) async { - var res = (json.decode(response.body)); - if (res['error'] == false) { - result = res; - } else { - result = res; - } - }).catchError((onError) { - print("register user error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - Future getProfileDetails() async { - await CrmService().getUserProfile().then((response) { - var res = (json.decode(response.body)); - if (res['user_obj'] != null) { - _userProfile = Profile.fromJson(res['user_obj']); - } else { - _userProfile = null; - } - }).catchError((onError) { - print("get profile Error >> $onError"); - }); - } - - Future changePassword(data) async { - Map result = {}; - await CrmService().changePassword(data).then((response) { - var res = (json.decode(response.body)); - result = res; - }).catchError((onError) { - print("change password Error >> $onError"); - result = {"status": "error", "message": "Something went wrong"}; - }); - return result; - } - - Future fetchCompanies() async { - try { - await CrmService().getCompanies().then((response) { - var res = (json.decode(response.body)); - if (res['error'] == false) { - _companies.clear(); - res['companies'].forEach((_company) { - Organization company = Organization.fromJson(_company); - _companies.add(company); - }); - } - }).catchError((onError) { - print("companies list error>> $onError"); - }); - } catch (e) { - } - - } -} - -final authBloc = AuthBloc(); diff --git a/lib/bloc/case_bloc.dart b/lib/bloc/case_bloc.dart deleted file mode 100644 index e80956c..0000000 --- a/lib/bloc/case_bloc.dart +++ /dev/null @@ -1,318 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:bottle_crm/bloc/opportunity_bloc.dart'; -import 'package:bottle_crm/model/case.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/services/crm_services.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:intl/intl.dart'; -import 'package:bottle_crm/model/team.dart'; - -class CaseBloc { - int? _casesCount; - List _cases = []; - List _assignedUsers = []; - List _teams = []; - List _statusObjForDropDown = []; - List _priorityObjForDropDown = []; - List _typeOfCaseObjForDropDown = []; - List _accountsObjForDropDown = []; - Case? _currentCase; - String _offset = ""; - List _teamsObjForDropdown = []; - List _assignedUsersDropdown = []; - Map _currentEditCase = { - 'name': "", - 'status': "", - 'priority': "", - 'type_of_case': "", - 'account': "", - 'contacts': [], - 'closed_on': DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())), - 'description': "", - 'assigned_to': [], - 'teams': [], - }; - String? _currentEditCaseId; - - Future fetchCases({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - if (filtersData != null) { - if (_copyFiltersData!['account'] != null) { - opportunityBloc.accountsList.forEach((element) { - if (element[1] == _copyFiltersData['account']) { - _copyFiltersData['account'] = element[0].toString(); - } - }); - } - } - - await CrmService() - .getCases(queryParams: _copyFiltersData, offset: _offset) - .then((response) { - var res = json.decode(response.body); - // _cases.clear(); - _casesCount = res['cases_count']; - - res['cases'].forEach((_case) { - Case data = Case.fromJson(_case); - _cases.add(data); - }); - - if (_copyFiltersData == null) { - if (res['status'] != null) { - _statusObjForDropDown.clear(); - res['status'].forEach((_status) { - _statusObjForDropDown.add(_status[1]); - }); - } - if (res['priority'] != null) { - _priorityObjForDropDown.clear(); - res['priority'].forEach((_priority) { - _priorityObjForDropDown.add(_priority[1]); - }); - } - - if (res['type_of_case'] != null) { - _typeOfCaseObjForDropDown.clear(); - res['type_of_case'].forEach((_typeOfCase) { - _typeOfCaseObjForDropDown.add(_typeOfCase[1]); - }); - } - - _teams.forEach((_team) { - Map team = {}; - team['id'] = _team.id; - team['name'] = _team.name; - _teamsObjForDropdown.add(team); - }); - - _assignedUsers.forEach((_user) { - Map user = {}; - user['id'] = _user.id; - user['name'] = _user.firstName; - _assignedUsersDropdown.add(user); - }); - - - - // _priorityObjForDropDown = res['priority']; - // _typeOfCaseObjForDropDown = res['type_of_case']; - } - }).catchError((onError) { - print("fetchCases Error >> $onError"); - }); - } - - Future deleteCase(Case data) async { - Map? result; - await CrmService() - .deletefromModule('cases', data.id) - .then((response) async { - var res = (json.decode(response.body)); - await fetchCases(); - // await dashboardBloc.fetchDashboardDetails(); - result = res; - }).catchError((onError) { - print("deleteCase Error >> $onError"); - result = {"status": "error", "message": "Something went wrong."}; - }); - return result; - } - - cancelCurrentEditCase() { - _currentEditCaseId = null; - _currentEditCase = { - 'name': "", - 'status': "", - 'priority': "", - 'type_of_case': "", - 'account': "", - 'contacts': [], - 'closed_on': "", - 'description': "", - 'assigned_to': [], - 'teams': [], - }; - } - - Future createCase({File? file}) async { - Map? result; - Map _copyOfCurrentEditCase = Map.from(_currentEditCase); - - opportunityBloc.accountsList.forEach((element) { - if (element[1] == _copyOfCurrentEditCase['account']) { - _copyOfCurrentEditCase['account'] = element[0].toString(); - } - }); - _copyOfCurrentEditCase['teams'] = (_copyOfCurrentEditCase['teams'] - .map((e) => e.toString())).toList().toString(); - _copyOfCurrentEditCase['assigned_to'] = - (_copyOfCurrentEditCase['assigned_to'].map((e) => e.toString())) - .toList() - .toString(); - _copyOfCurrentEditCase['contacts'] = (_copyOfCurrentEditCase['contacts'] - .map((e) => e.toString())).toList().toString(); - - // if (_copyOfCurrentEditCase['closed_on'] != "") { - // _copyOfCurrentEditCase['closed_on'] = DateFormat("yyyy-MM-dd").format( - // DateFormat("dd-MM-yyyy").parse(_copyOfCurrentEditCase['closed_on'])); - // } - await CrmService() - .createCase(_copyOfCurrentEditCase, file!) - .then((response) async { - // var res = json.decode(response); # for multipartrequest - var res = json.decode(response.body); - if (res["error"] == false) { - //await fetchCases(); - await fetchRequiredData(); - } - result = res; - }).catchError((onError) { - print("createCases Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - updateCurrentEditCase(Case editCase) { - List _contacts = []; - List _teams = []; - List _assignedUsers = []; - - _currentEditCaseId = editCase.id.toString(); - editCase.contacts!.forEach((contact) { - _contacts.add(contact.id); - }); - editCase.assignedTo!.forEach((assignedAccount) { - _assignedUsers.add(assignedAccount.id); - }); - editCase.teams!.forEach((team) { - _teams.add(team.id); - }); - - _currentEditCase = { - 'name': editCase.name, - 'status': editCase.status, - 'priority': editCase.priority, - 'type_of_case': editCase.caseType, - 'account': editCase.account!.name, - 'contacts': _contacts, - 'closed_on': DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(editCase.closedOn!)), - 'description': editCase.description, - 'assigned_to': _assignedUsers, - 'teams': _teams, - }; - } - - Future editCase([file]) async { - Map? result; - Map _copyOfCurrentEditCase = Map.from(_currentEditCase); - - opportunityBloc.accountsList.forEach((element) { - if (element[1] == _copyOfCurrentEditCase['account']) { - _copyOfCurrentEditCase['account'] = element[0].toString(); - } - }); - _copyOfCurrentEditCase['teams'] = (_copyOfCurrentEditCase['teams'] - .map((e) => e.toString())).toList().toString(); - _copyOfCurrentEditCase['assigned_to'] = - (_copyOfCurrentEditCase['assigned_to'].map((e) => e.toString())) - .toList() - .toString(); - _copyOfCurrentEditCase['contacts'] = (_copyOfCurrentEditCase['contacts'] - .map((e) => e.toString())).toList().toString(); - - if (_copyOfCurrentEditCase['closed_on'] != "") { - _copyOfCurrentEditCase['closed_on'] = DateFormat("yyyy-MM-dd").format( - DateFormat("dd-MM-yyyy").parse(_copyOfCurrentEditCase['closed_on'])); - } - - _copyOfCurrentEditCase - .removeWhere((key, value) => value.runtimeType != String); - - await CrmService() - .editCase(_copyOfCurrentEditCase, _currentEditCaseId, file) - .then((response) async { - var res = json.decode(response.body); - if (res["error"] == false) { - await fetchCases(); - } - result = res; - }).catchError((onError) { - print("editCase Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - List get cases { - return _cases; - } - - List get statusObjForDropDown { - return _statusObjForDropDown; - } - - List get priorityObjForDropDown { - return _priorityObjForDropDown; - } - - List get typeOfCaseObjForDropDown { - return _typeOfCaseObjForDropDown; - } - - List get accountCaseObjForDropDown { - return _accountsObjForDropDown; - } - - int? get casesCount { - return _casesCount; - } - - Map get currentEditCase { - return _currentEditCase; - } - - String? get currentEditCaseId { - return _currentEditCaseId; - } - - set currentEditCaseId(id) { - _currentEditCaseId = id; - } - - Case? get currentCase { - return _currentCase; - } - - set currentCase(data) { - _currentCase = data; - } - - List get teamsObjForDropdown { - return _teamsObjForDropdown; - } - - List get assignedUsersDropdown { - return _assignedUsersDropdown; - } - - List get teams { - return _teams; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } -} - -final caseBloc = CaseBloc(); diff --git a/lib/bloc/contact_bloc.dart b/lib/bloc/contact_bloc.dart deleted file mode 100644 index 9a4458b..0000000 --- a/lib/bloc/contact_bloc.dart +++ /dev/null @@ -1,366 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:bottle_crm/bloc/account_bloc.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:bottle_crm/services/crm_services.dart'; -import 'package:intl/intl.dart'; - -import 'dashboard_bloc.dart'; - -class ContactBloc { - List _contacts = []; - List _contactsObjForDropdown = []; - List _teams = []; - List _teamsObjForDropdown = []; - String? _currentEditContactId; - Contact? _currentContact; - int? _currentContactIndex; - List countriesList = accountBloc.countriesList; - List _assignedToList = []; - - Map _currentEditContact = { - 'salutation': "", - 'first_name': "", - 'last_name': "", - 'primary_email': "", - 'mobile_number': "", - 'date_of_birth': DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())), - 'organization': "", - 'department': "", - 'do_not_call': false, - 'title': "", - 'address': { - "address_line": "", - "street": "", - "city": "", - "state": "", - "pincode": "", - "country": "" - }, - 'linked_in_url': "", - 'facebook_url': "", - 'twitter_username': "", - 'description': "", - 'assigned_to': [], - 'teams': [], - }; - String _offset = ""; - - Future fetchContacts({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - if (filtersData != null) { - _copyFiltersData!['assigned_to'] = - _copyFiltersData['assigned_to'].length > 0 - ? jsonEncode(_copyFiltersData['assigned_to']) - : ""; - } - await CrmService() - .getContacts(queryParams: _copyFiltersData, offset: _offset) - .then((response) { - var res = json.decode(response.body); - // _contacts.clear(); - _contactsObjForDropdown.clear(); - _teams.clear(); - _assignedToList.clear(); - res['contact_obj_list'].forEach((_contact) { - leadBloc.countriesList!.forEach((country) { - if (_contact!['address'] != null && - _contact['address']['country'] != null && - _contact['address']['country'] == country[0]) { - _contact!['address']['country'] = country[1]; - } - }); - Contact contact = Contact.fromJson(_contact); - _contacts.add(contact); - }); - - _offset = res['offset'] != null && res['offset'].toString() != "0" - ? res['offset'].toString() - : ""; - - _contacts.forEach((_contact) { - Map contact = {}; - contact['id'] = _contact.id; - contact['name'] = _contact.firstName! + ' ' + _contact.lastName!; - _contactsObjForDropdown.add(contact); - }); - if (res['teams'] != null) - res['teams'].forEach((_team) { - Team team = Team.fromJson(_team); - _teams.add(team); - }); - - _teams.forEach((_team) { - Map team = {}; - team['id'] = _team.id; - team['name'] = _team.name; - _teamsObjForDropdown.add(team); - }); - // if (res['users'] != null) - // res['users'].forEach((_user) { - // Profile user = Profile.fromJson(_user); - // _assignedToList.add({"name": user.firstName, "id": user.id}); - // }); - }).catchError((onError) { - print("fetchContacts Error>> $onError"); - }); - } - - createContact({File? file}) async { - Map result = {}; - leadBloc.countriesList!.forEach((country) { - if (country[1] == _currentEditContact['address']['country']) { - _currentEditContact['address']['country'] = country[0]; - } - }); - // Map _copyOfCurrentEditContact= new Map.from(_currentEditContact); - Map _contact = { - 'salutation': _currentEditContact['salutation'], - 'first_name': _currentEditContact['first_name'], - 'last_name': _currentEditContact['last_name'], - 'mobile_number': _currentEditContact['mobile_number'], - 'primary_email': _currentEditContact['primary_email'], - 'title': _currentEditContact['title'], - 'linked_in_url': _currentEditContact['linked_in_url'], - 'facebook_url': _currentEditContact['facebook_url'], - 'twitter_username': _currentEditContact['twitter_username'], - 'organization': _currentEditContact['organization'], - 'department': _currentEditContact['department'], - 'date_of_birth': _currentEditContact['date_of_birth'], - 'do_not_call': _currentEditContact['do_not_call'], - 'teams': _currentEditContact['teams'], - 'assigned_to': _currentEditContact['assigned_to'], - 'address_line': (_currentEditContact['address'])['address_line'], - 'street': _currentEditContact['address']['street'], - 'city': _currentEditContact['address']['city'], - 'state': _currentEditContact['address']['state'], - 'pincode': _currentEditContact['address']['pincode'], - 'country': _currentEditContact['address']['country'], - 'description': _currentEditContact['description'], - // 'contact_attachment' : '', - }; - - _contact['teams'] = - (_contact['teams'].map((team) => team.toString())).toList().toString(); - _contact['assigned_to'] = (_contact['assigned_to'] - .map((team) => team.toString())).toList().toString(); - _contact['do_not_call'] = _contact['do_not_call'].toString(); - await CrmService().createContact(_contact, file!).then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - //await fetchContacts(); - await dashboardBloc.fetchDashboardDetails(); - } - result = res; - }).catchError((onError) { - print('createContact Error >> $onError'); - result = {"status": "error", "message": onError}; - }); - return result; - } - - editContact() async { - Map? _result; - Map copyOfCurrentEditContact = { - 'salutation': _currentEditContact['salutation'], - 'first_name': _currentEditContact['first_name'], - 'last_name': _currentEditContact['last_name'], - 'mobile_number': _currentEditContact['mobile_number'], - 'primary_email': _currentEditContact['primary_email'], - 'title': _currentEditContact['title'], - 'linked_in_url': _currentEditContact['linked_in_url'], - 'facebook_url': _currentEditContact['facebook_url'], - 'twitter_username': _currentEditContact['twitter_username'], - 'organization': _currentEditContact['organization'], - 'department': _currentEditContact['department'], - 'date_of_birth': _currentEditContact['date_of_birth'], - 'do_not_call': _currentEditContact['do_not_call'], - 'teams': _currentEditContact['teams'], - 'assigned_to': _currentEditContact['assigned_to'], - 'address_line': (_currentEditContact['address'])['address_line'], - 'street': _currentEditContact['address']['street'], - 'city': _currentEditContact['address']['city'], - 'state': _currentEditContact['address']['state'], - 'pincode': _currentEditContact['address']['pincode'], - 'country': _currentEditContact['address']['country'], - 'description': _currentEditContact['description'], - // 'contact_attachment' : '', - }; - countriesList.forEach((country) { - if (country![1] == copyOfCurrentEditContact['country']) { - copyOfCurrentEditContact['country'] = country[0]; - } - }); - - copyOfCurrentEditContact['teams'] = - jsonEncode(copyOfCurrentEditContact['teams']); - copyOfCurrentEditContact['assigned_to'] = - jsonEncode(copyOfCurrentEditContact['assigned_to']); - await CrmService() - .editContact(copyOfCurrentEditContact, currentEditContactId) - .then((response) async { - var res = json.decode(response.body); - - if (res['error'] == false) { - await fetchContacts(); - await dashboardBloc.fetchDashboardDetails(); - } - _result = res; - }).catchError((onError) { - print('editContact Error >> $onError'); - _result = {"status": "error", "message": onError}; - }); - return _result; - } - - Future deleteContact(Contact contact) async { - Map? result; - await CrmService().deleteContact(contact.id).then((response) async { - var res = (json.decode(response.body)); - await fetchContacts(); - await dashboardBloc.fetchDashboardDetails(); - result = res; - }).catchError((onError) { - print("deleteContact Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - cancelCurrentEditContact() { - _currentEditContactId = null; - _currentEditContact = { - 'salutation': "", - 'first_name': "", - 'last_name': "", - 'primary_email': "", - 'mobile_number': "", - 'title': "", - 'linked_in_url': '', - 'facebook_url': '', - 'twitter_username': '', - 'organization': '', - 'department': '', - 'date_of_birth': DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())), - 'do_not_call': false, - 'address': { - "address_line": "", - "street": "", - "city": "", - "state": "", - "pincode": "", - "country": "" - }, - 'description': "", - 'assigned_to': [], - 'teams': [], - }; - } - - updateCurrentEditContact(Contact editContact) { - _currentEditContactId = editContact.id.toString(); - List teams = []; - List assignedUsers = []; - - editContact.teams!.forEach((team) { - teams.add(team.id); - }); - - editContact.assignedTo!.forEach((user) { - assignedUsers.add(user.id); - }); - _currentEditContact['salutation'] = editContact.salutation; - _currentEditContact['id'] = editContact.id; - _currentEditContact['first_name'] = editContact.firstName; - _currentEditContact['last_name'] = editContact.lastName; - _currentEditContact['primary_email'] = editContact.primaryEmail; - _currentEditContact['mobile_number'] = editContact.primaryMobile; - _currentEditContact['title'] = editContact.title; - _currentEditContact['linked_in_url'] = editContact.linkedInUrl; - _currentEditContact['facebook_url'] = editContact.facebookUrl; - _currentEditContact['twitter_username'] = editContact.twitterUserName; - _currentEditContact['organization'] = editContact.organization; - _currentEditContact['department'] = editContact.department; - _currentEditContact['date_of_birth'] = editContact.dateOfBirth; - _currentEditContact['do_not_call'] = editContact.doNotCall; - _currentEditContact['description'] = editContact.description; - _currentEditContact['created_by'] = editContact.createdBy; - _currentEditContact['created_on'] = editContact.createdOn; - _currentEditContact['is_active'] = editContact.isActive; - _currentEditContact['address'] = editContact.address; - countriesList.forEach((country) { - if (country![0] == editContact.address!['country']) { - _currentEditContact['address']['country'] = country[1]; - } - }); - _currentEditContact['teams'] = teams; - _currentEditContact['assigned_to'] = assignedUsers; - } - - Map get currentEditContact { - return _currentEditContact; - } - - set currentEditContact(currentEditContact) { - _currentEditContact = currentEditContact; - } - - List get contacts { - return _contacts; - } - - Contact? get currentContact { - return _currentContact; - } - - set currentContact(contact) { - _currentContact = contact; - } - - int? get currentContactIndex { - return _currentContactIndex; - } - - set currentContactIndex(currentContactIndex) { - _currentContactIndex = currentContactIndex; - } - - List get teams { - return _teams; - } - - List get contactsObjForDropdown { - return _contactsObjForDropdown; - } - - String? get currentEditContactId { - return _currentEditContactId; - } - - set currentEditContactId(id) { - _currentEditContactId = id; - } - - List get teamsObjForDropdown { - return _teamsObjForDropdown; - } - - List get assignedToList { - return _assignedToList; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } -} - -final contactBloc = ContactBloc(); diff --git a/lib/bloc/dashboard_bloc.dart b/lib/bloc/dashboard_bloc.dart deleted file mode 100644 index 264a626..0000000 --- a/lib/bloc/dashboard_bloc.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:convert'; -import 'package:bottle_crm/model/account.dart'; -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/opportunities.dart'; -import 'package:bottle_crm/services/crm_services.dart'; - -class DashboardBloc { - Map _dashboardData = {}; - - Future fetchDashboardDetails() async { - await CrmService().getDashboardDetails().then((response) { - var res = (json.decode(response.body)); - - List _accounts = []; - List _opportunities = []; - List _contacts = []; - - res['accounts'].forEach((_account) { - Account account = Account.fromJson(_account); - _accounts.add(account); - }); - - res['contacts'].forEach((_contact) { - Contact contact = Contact.fromJson(_contact); - _contacts.add(contact); - }); - - res['opportunities'].forEach((_opportunity) { - Opportunity opportunity = Opportunity.fromJson(_opportunity); - _opportunities.add(opportunity); - }); - - _dashboardData['accounts'] = _accounts; - _dashboardData['opportunities'] = _opportunities; - _dashboardData['contacts'] = _contacts; - _dashboardData['accountsCount'] = res['accounts_count']; - _dashboardData['contactsCount'] = res['contacts_count']; - _dashboardData['leadsCount'] = res['leads_count']; - _dashboardData['opportunitiesCount'] = res['opportunities_count']; - }); - // .catchError((onError) { - // print("fetchDashboardDetails Error >> $onError"); - // }); - } - - Map get dashboardData { - return _dashboardData; - } -} - -final dashboardBloc = DashboardBloc(); diff --git a/lib/bloc/document_bloc.dart b/lib/bloc/document_bloc.dart deleted file mode 100644 index db5b99f..0000000 --- a/lib/bloc/document_bloc.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'dart:convert'; - -import 'package:bottle_crm/model/document.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/services/crm_services.dart'; - -class DocumentBloc { - List _activeDocuments = []; - List _inActiveDocuments = []; - List _documents = []; - List _fileSizes = []; - - List _statusObjforDropdown = []; - List _usersObjforMultiselect = []; - - Document? _currentDocument; - int? _currentDocumentIndex; - Map _currentEditDocument = { - 'title': "", - 'teams': [], - 'shared_to': [], - 'document_file': '' - }; - String? _currentEditDocumentId; - - fetchDocuments({searchData}) async { - Map? _copySearchData = searchData != null ? new Map.from(searchData) : null; - - await CrmService() - .getDocuments(queryParams: _copySearchData) - .then((response) async { - _activeDocuments.clear(); - _inActiveDocuments.clear(); - _documents.clear(); - _fileSizes.clear(); - _usersObjforMultiselect.clear(); - - var res = jsonDecode(response.body); - - res['documents_active'].forEach((_document) { - Document document = Document.fromJson(_document); - _activeDocuments.add(document); - _documents.add(document); - }); - res['documents_inactive'].forEach((_document) { - Document document = Document.fromJson(_document); - _inActiveDocuments.add(document); - _documents.add(document); - }); - - res['status_choices'].map((status) { - _statusObjforDropdown.add(status); - }); - - res['users'].forEach((_user) { - Profile user = Profile.fromJson(_user); - _usersObjforMultiselect.add({ - "name": "${user.firstName} ${user.lastName}", - "id": user.id.toString() - }); - }); - }).catchError((onError) { - print('fetchDocuments Error >> $onError'); - }); - - // _fileSizes = await CrmService().getFileSizes(_documents); - // print(fileSizes); - } - - createDocument(file) async { - Map? result; - Map _copyOfCurrentEditDocument = new Map.from(_currentEditDocument); - _copyOfCurrentEditDocument['teams'] = (_copyOfCurrentEditDocument['teams'] - .map((team) => team.toString())).toList().toString(); - _copyOfCurrentEditDocument['shared_to'] = - (_copyOfCurrentEditDocument['shared_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - await CrmService() - .createDocument(_copyOfCurrentEditDocument, file) - .then((response) async { - var res = json.decode(response); - if (res["error"] == false) { - await fetchDocuments(); - } - result = res; - }).catchError((onError) { - print("editDocument Error >> $onError"); - result = {"status": "error", "message": "Something went wrong"}; - }); - return result; - } - - Future editDocument(file) async { - Map? result; - Map _copyOfCurrentEditDocument = Map.from(_currentEditDocument); - _copyOfCurrentEditDocument['teams'] = (_copyOfCurrentEditDocument['teams'] - .map((team) => team.toString())).toList().toString(); - _copyOfCurrentEditDocument['shared_to'] = - (_copyOfCurrentEditDocument['shared_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - await CrmService() - .editDocument(_copyOfCurrentEditDocument, file, _currentEditDocumentId) - .then((response) async { - var res = json.decode(response); - if (res["error"] == false) { - await fetchDocuments(); - } - result = res; - }).catchError((onError) { - print("editDocument Error >> $onError"); - result = {"status": "error", "message": "Something went wrong"}; - }); - return result; - } - - deleteDocument(Document file) async { - Map? result; - await CrmService().deleteDocument(file.id).then((response) async { - var res = (json.decode(response.body)); - await fetchDocuments(); - result = res; - }).catchError((onError) { - print("deleteDocument Error >> $onError"); - result = { - "status": "error", - "message": "deleteDocument : Something went wrong." - }; - }); - return result; - } - - cancelCurrentEditDocument() { - _currentEditDocumentId = null; - _currentEditDocument = {'title': "", 'teams': [], 'shared_to': []}; - } - - updateCurrentEditDocument(Document editFile) { - _currentEditDocumentId = editFile.id.toString(); - List sharedToList = []; - List teams = []; - - editFile.sharedTo!.forEach((sharee) { - sharedToList.add(sharee.id.toString()); - }); - - editFile.teams!.forEach((team) { - teams.add(team.id); - }); - - _currentEditDocument['title'] = editFile.title; - _currentEditDocument['document_file'] = editFile.documentFile; - _currentEditDocument['teams'] = teams; - _currentEditDocument['shared_to'] = sharedToList; - _currentEditDocument['status'] = editFile.status; - } - - List get documents { - return _documents; - } - - List get activeDocuments { - return _activeDocuments; - } - - List get inActiveDocuments { - return _inActiveDocuments; - } - - List get fileSizes { - return _fileSizes; - } - - List get statusObjforDropdown { - return _statusObjforDropdown; - } - - List get usersObjforMultiselect { - return _usersObjforMultiselect; - } - - Document? get currentDocument { - return _currentDocument; - } - - set currentDocument(document) { - _currentDocument = document; - } - - int? get currentDocumentIndex { - return _currentDocumentIndex; - } - - set currentDocumentIndex(index) { - _currentDocumentIndex = index; - } - - String? get currentEditDocumentId { - return _currentEditDocumentId; - } - - Map get currentEditDocument { - return _currentEditDocument; - } - - set currentEditDocumentId(id) { - _currentEditDocumentId = id; - } - // --------------------------DOCUMENT DOWNLOAD METHODS---------------- - -} - -final documentBLoc = DocumentBloc(); diff --git a/lib/bloc/event_bloc.dart b/lib/bloc/event_bloc.dart deleted file mode 100644 index 45b6844..0000000 --- a/lib/bloc/event_bloc.dart +++ /dev/null @@ -1,235 +0,0 @@ -import 'dart:convert'; - -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/events.dart'; -import 'package:bottle_crm/services/crm_services.dart'; - -class EventBloc { - List _events = []; - Event? _currentEvent; - int? _currentEventIndex; - String? _currentEditEventId = ""; - String _offset = ""; - List _assignedToList = []; - List _contacts = []; - List _recurringDays = []; - Map _currentEditEvent = { - "name": "", - "event_type": "Recurring", - "start_date": null, - "start_time": "", - "end_date": "", - "end_time": "", - "description": "", - "contacts": [], - "teams": [], - "assigned_to": [], - "recurring_day": [], - }; - - Future fetchEvents({filtersData}) async { - try { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - - await CrmService() - .getEvents(queryParams: _copyFiltersData) - .then((response) { - var res = (json.decode(response.body)); - _assignedToList.clear(); - - res['events'].forEach((_event) { - Event event = Event.fromJson(_event); - _events.add(event); - }); - if (res['recurring_days'] != null) { - _recurringDays.clear(); - res['recurring_days'].forEach((_recurringDay) { - _recurringDays.add(_recurringDay[1]); - }); - } - print(_recurringDays); - res['contacts_list'].forEach((_contact) { - Contact contact = Contact.fromJson(_contact); - _contacts.add(contact); - }); - - _offset = res['offset'] != null && res['offset'].toString() != "0" - ? res['offset'].toString() - : ""; - }).catchError((onError) { - print("fetchEvents Error>> $onError"); - }); - } catch (e) {} - } - - Future createEvent() async { - Map? result; - Map _copyCurrentEditEvent = Map.from(_currentEditEvent); - _copyCurrentEditEvent['contacts'] = (_copyCurrentEditEvent['contacts'] - .map((contacts) => contacts.toString())).toList().toString(); - - _copyCurrentEditEvent['teams'] = (_copyCurrentEditEvent['teams'] - .map((teams) => teams.toString())).toList().toString(); - - _copyCurrentEditEvent['assigned_to'] = (_copyCurrentEditEvent['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - - await CrmService() - .createEvent(_copyCurrentEditEvent) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchEvents(); - } - result = res; - }).catchError((onError) { - print('createEvents Error >> $onError'); - result = {"status": "error", "message": "Something went wrong"}; - }); - return result; - } - - Future editEvent() async { - Map? _result; - Map _copyOfCurrentEditEvent = Map.from(_currentEditEvent); - - _copyOfCurrentEditEvent['contacts'] = (_copyOfCurrentEditEvent['contacts'] - .map((contacts) => contacts.toString())).toList().toString(); - - _copyOfCurrentEditEvent['teams'] = (_copyOfCurrentEditEvent['teams'] - .map((teams) => teams.toString())).toList().toString(); - - _copyOfCurrentEditEvent['assigned_to'] = - (_copyOfCurrentEditEvent['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - - await CrmService() - .editEvent(_copyOfCurrentEditEvent, _currentEditEventId) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchEvents(); - } - _result = res; - }).catchError((onError) { - print("editEvent Error >> $onError"); - _result = {"status": "error", "message": "Something went wrong."}; - }); - return _result; - } - - Future deleteEvent(Event event) async { - Map? result; - await CrmService().deleteEvent(event.id).then((response) async { - var res = (json.decode(response.body)); - await fetchEvents(); - result = res; - }).catchError((onError) { - print("deleteEvent Error >> $onError"); - result = {"status": "error", "message": "Something went wrong."}; - }); - return result; - } - - updateCurrentEditEvent(Event event) { - _currentEditEventId = event.id.toString(); - - List contacts = []; - List teams = []; - List assignedUsers = []; - - event.contacts!.forEach((contact) { - contacts.add(contact.id); - }); - - event.assignedTo!.forEach((assignedAccount) { - assignedUsers.add(assignedAccount.id); - }); - - event.teams!.forEach((team) { - teams.add(team.id); - }); - - _currentEditEvent = { - "name": "", - "event_type": "Recurring", - "start_date": null, - "start_time": "", - "end_date": "", - "end_time": "", - "description": "", - "contacts": [], - "teams": [], - "assigned_to": [], - "recurring_day": [], - }; - } - - cancelCurrentEditEvent() { - _currentEditEventId = null; - _currentEditEvent = { - "name": "", - "event_type": "Recurring", - "start_date": null, - "start_time": "", - "end_date": "", - "end_time": "", - "description": "", - "contacts": [], - "teams": [], - "assigned_to": [], - "recurring_days": [], - }; - } - - List get events { - return _events; - } - - Event? get currentEvent { - return _currentEvent; - } - - set currentEvent(event) { - _currentEvent = event; - } - - int? get currentEventIndex { - return _currentEventIndex; - } - - set currentEventIndex(index) { - _currentEventIndex = index; - } - - Map get currentEditEvent { - return _currentEditEvent; - } - - set currentEditEvent(currentEditEvent) { - _currentEditEvent = currentEditEvent; - } - - List get recurringDays { - return _recurringDays; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } - - String? get currentEditEventId { - return _currentEditEventId; - } - - set currentEditEventId(id) { - _currentEditEventId = id; - } -} - -final eventBloc = EventBloc(); diff --git a/lib/bloc/lead_bloc.dart b/lib/bloc/lead_bloc.dart deleted file mode 100644 index 3057dbf..0000000 --- a/lib/bloc/lead_bloc.dart +++ /dev/null @@ -1,406 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:bottle_crm/model/lead.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/services/crm_services.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -import 'dashboard_bloc.dart'; - -class LeadBloc { - List _openLeads = []; - List _closedLeads = []; - List _source = []; - List _indrustries = []; - List _status = []; - List? _tags = []; - Lead? _currentLead; - String _currentLeadType = "Open"; - List _users = []; - List _countries = []; - List _countriesList = []; - //List _teams = []; - List _teamsObjForDropdown = []; - List _leadsTitles = []; - List _usersObjForDropdown = []; - String? _currentEditLeadId; - List _filterTags = []; - - Map _currentEditLead = { - "first_name": "", - "last_name": "", - "phone": "", - "account_name": "", - "opportunity_amount": "", - "title": "", - "email": "", - "website": "", - "skype_ID": "", - "description": "", - "assigned_to": [], - "address_line": "", - "street": "", - "postcode": "", - "city": "", - "state": "", - "country": "", - "status": "", - "source": "", - "tags": [], - "industry": "", - }; - String _offset = ""; - - Future fetchLeads({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - if (filtersData != null) { - _copyFiltersData!['tags'] = _copyFiltersData['tags'].length > 0 - ? jsonEncode( - (_copyFiltersData['tags'].map((id) => id.toString())).toList()) - : ""; - _copyFiltersData['assigned_to'] = - _copyFiltersData['assigned_to'].length > 0 - ? jsonEncode((_copyFiltersData['assigned_to'] - .map((id) => id.toString())).toList()) - : ""; - _copyFiltersData['source'] = _copyFiltersData['source'] != null - ? _copyFiltersData['source'].toString().toLowerCase() - : ""; - _copyFiltersData['status'] = _copyFiltersData['status'] != null - ? _copyFiltersData['status'].toString().toLowerCase() - : ""; - } - await CrmService() - .getLeads(queryParams: _copyFiltersData, offset: _offset) - .then((response) { - var res = json.decode(response.body); - - if (res['open_leads'] != null) { - _leadsTitles.clear(); - // _teams.clear(); - res['open_leads']['open_leads'].forEach((_lead) { - Lead lead = Lead.fromJson(_lead); - _openLeads.add(lead); - }); - - _openLeads.forEach((Lead lead) { - _leadsTitles.add(lead.title); - }); - } - - if (res['close_leads'] != null) { - res['close_leads']['close_leads'].forEach((_lead) { - Lead lead = Lead.fromJson(_lead); - _closedLeads.add(lead); - }); - } - - if (res['source'] != null) { - _source.clear(); - res['source'].forEach((_leadsource) { - _source.add(_leadsource[1]); - }); - } - - if (res['industries'] != null) { - _indrustries.clear(); - res['industries'].forEach((_industries) { - _indrustries.add(_industries[1]); - }); - } - - if (res['status'] != null) { - _status.clear(); - res['status'].forEach((_leadstatus) { - _status.add(_leadstatus[1]); - }); - } - - _filterTags = res['tags'] != null ? res['tags'] : []; - _offset = res['open_leads']['offset'] != null && - res['open_leads']['offset'].toString() != "0" - ? res['open_leads']['offset'].toString() - : res['close_leads']['offset'] != null && - res['close_leads']['offset'].toString() != "0" - ? res['close_leads']['offset'].toString() - : ""; - - if (res['countries'] != null) { - _countries.clear(); - _countriesList = res['countries']; - res['countries'].forEach((country) { - _countries.add(country[1]); - }); - } - }).catchError((onError) { - print('fetchLeads error $onError'); - }); - } - - createLead({File? file}) async { - Map? result; - Map _copyCurrentEditLead = new Map.from(_currentEditLead); - _copyCurrentEditLead['status'] = - _copyCurrentEditLead['status'].toLowerCase(); - _copyCurrentEditLead['source'] = - _copyCurrentEditLead['source'].toLowerCase(); - _copyCurrentEditLead['assigned_to'] = (_copyCurrentEditLead['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - _copyCurrentEditLead['tags'] = jsonEncode(_copyCurrentEditLead['tags']); - _countriesList.forEach((country) { - if (country![1] == _copyCurrentEditLead['country']) { - _copyCurrentEditLead['country'] = country[0]; - } - }); - - await CrmService() - .createLead(_copyCurrentEditLead, file!) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - // await fetchLeads(); - await dashboardBloc.fetchDashboardDetails(); - } - result = res; - }).catchError((onError) { - print('createLead Error >> $onError'); - result = {"status": "error", "message": onError}; - }); - return result; - } - - editLead() async { - Map? result; - Map _copyCurrentEditLead = new Map.from(_currentEditLead); - _copyCurrentEditLead['status'] = - _copyCurrentEditLead['status'].toLowerCase(); - _copyCurrentEditLead['industry'] = - _copyCurrentEditLead['industry'].toLowerCase(); - _copyCurrentEditLead['source'] = - _copyCurrentEditLead['source'].toLowerCase(); - _copyCurrentEditLead['teams'] = (_copyCurrentEditLead['teams'] - .map((team) => team.toString())).toList().toString(); - _copyCurrentEditLead['assigned_to'] = (_copyCurrentEditLead['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - - _copyCurrentEditLead['tags'] = jsonEncode(_copyCurrentEditLead['tags']); - _countriesList.forEach((country) { - if (country[1] == _copyCurrentEditLead['country']) { - _copyCurrentEditLead['country'] = country[0]; - } - }); - await CrmService() - .editLead(_copyCurrentEditLead, _currentEditLeadId) - .then((response) async { - var res = json.decode(response.body); - - if (res["error"] == false) { - await fetchLeads(); - await dashboardBloc.fetchDashboardDetails(); - } - result = res; - }).catchError((onError) { - print('editLead Error >> $onError'); - result = {"status": "error", "message": onError}; - }); - return result; - } - - Future deleteLead(Lead lead) async { - Map? result; - await CrmService().deleteLead(lead.id).then((response) async { - var res = (json.decode(response.body)); - // openLeads.clear(); - // closedLeads.clear(); - await fetchLeads(); - await dashboardBloc.fetchDashboardDetails(); - result = res; - }).catchError((onError) { - print("deleteLead Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - cancelCurrentEditLead() { - _currentEditLead = { - "first_name": "", - "last_name": "", - "phone": "", - "account_name": "", - "skype_ID": "", - "title": "", - "email": "", - "website": "", - "description": "", - "opportunity_amount ": "", - "assigned_to": [], - "address_line": "", - "street": "", - "postcode": "", - "city": "", - "state": "", - "country": "", - "status": "", - "source": "", - "tags": [] - }; - } - - updateCurrentEditLead(Lead editLead) async { - _currentEditLeadId = editLead.id.toString(); - - List teams = []; - List assignedUsers = []; - List? tags = []; - - editLead.teams!.forEach((team) { - teams.add(team.id); - }); - - editLead.assignedTo!.forEach((user) { - assignedUsers.add(user.id); - }); - - for (var tag in editLead.tags!) { - tags.add(tag['name']); - } - - _countriesList.forEach((country) { - if (country[0] == editLead.country) { - editLead.country = country[1]; - } - }); - - _currentEditLead['first_name'] = editLead.firstName; - _currentEditLead['last_name'] = editLead.lastName; - _currentEditLead['phone'] = editLead.phone; - _currentEditLead['account_name'] = editLead.accountName; - _currentEditLead['title'] = editLead.title; - _currentEditLead['email'] = editLead.email; - _currentEditLead['website'] = editLead.website; - _currentEditLead['description'] = editLead.description; - _currentEditLead['teams'] = teams; - // _currentEditLead['users'] = editLead.users; - _currentEditLead['assigned_to'] = assignedUsers; - _currentEditLead['address_line'] = editLead.addressLine; - _currentEditLead['street'] = editLead.street; - _currentEditLead['postcode'] = editLead.postcode; - _currentEditLead['city'] = editLead.city; - _currentEditLead['state'] = editLead.state; - _currentEditLead['country'] = editLead.country; - _currentEditLead['status'] = - editLead.status != null && editLead.status != "" - ? editLead.status!.capitalizeFirstofEach() - : editLead.status; - _currentEditLead['source'] = - editLead.source != null && editLead.source != "" - ? editLead.source!.capitalizeFirstofEach() - : editLead.source; - _currentEditLead['tags'] = tags; - } - - List? get tags { - return _tags; - } - - List get filterTags { - return _filterTags; - } - - Map get currentEditLead { - return _currentEditLead; - } - - set currentEditLead(currentEditLead) { - _currentEditLead = currentEditLead; - } - - String? get currentEditLeadId { - return _currentEditLeadId; - } - - set currentEditLeadId(id) { - _currentEditLeadId = id; - } - - List get openLeads { - return _openLeads; - } - - List get closedLeads { - return _closedLeads; - } - - List get source { - return _source; - } - - List get industry { - return _indrustries; - } - - List get status { - return _status; - } - - List get leadsTitles { - return _leadsTitles; - } - - List get users { - return _users; - } - - Lead? get currentLead { - return _currentLead; - } - - set currentLead(lead) { - _currentLead = lead; - } - - String get currentLeadType { - return _currentLeadType; - } - - set currentLeadType(type) { - _currentLeadType = type; - } - - List get countries { - return _countries; - } - - List? get countriesList { - return _countriesList; - } - - set countriesList(countriesList) { - _countriesList = countriesList; - } - - List get usersObjForDropdown { - return _usersObjForDropdown; - } - - // List get teams { - // return _teams; - // } - - List get teamsObjForDropdown { - return _teamsObjForDropdown; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } -} - -final leadBloc = LeadBloc(); diff --git a/lib/bloc/opportunity_bloc.dart b/lib/bloc/opportunity_bloc.dart deleted file mode 100644 index 6e3f6b0..0000000 --- a/lib/bloc/opportunity_bloc.dart +++ /dev/null @@ -1,394 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:bottle_crm/model/account.dart'; -import 'package:bottle_crm/model/opportunities.dart'; -import 'package:bottle_crm/services/crm_services.dart'; -import 'package:intl/intl.dart'; - -import 'dashboard_bloc.dart'; - -class OpportunityBloc { - List _opportunities = []; - int? _currentOpportunityIndex; - Opportunity? _currentOpportunity; - String? _currentEditOpportunityId; - - Map _currentEditOpportunity = { - 'name': "", - 'account': "", - 'stage': "", - 'currency': "", - 'amount': "", - 'lead_source': "", - 'probability': 0, - 'contacts': [], - 'due_date': DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())), - 'description': "", - 'assigned_to': [], - 'tags': [], - 'teams': [], - }; - - List? _tags = []; - List _accountsObjforDropDown = []; - List _stageObjforDropDown = []; - List _leadSourceObjforDropDown = []; - List _currencyObjforDropDown = []; - List? _currencyList = []; - List _accountsList = []; - List _filterTags = []; - String _offset = ""; - - Future fetchOpportunities({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - if (filtersData != null) { - _copyFiltersData!['tags'] = _copyFiltersData['tags'].length > 0 - ? jsonEncode(_copyFiltersData['tags']) - : ""; - if (_copyFiltersData['account'] != null) { - _accountsList.forEach((element) { - if (element[1] == _copyFiltersData['account']) { - _copyFiltersData['account'] = element[0].toString(); - } - }); - } - } - - await CrmService() - .getOpportunities(queryParams: _copyFiltersData, offset: _offset) - .then((response) { - var res = jsonDecode(response.body); - - //_opportunities.clear(); - _accountsObjforDropDown.clear(); - _accountsList.clear(); - _currencyObjforDropDown.clear(); - - res['opportunities'].forEach((_opportunity) { - Opportunity oppor = Opportunity.fromJson(_opportunity); - _opportunities.add(oppor); - }); - - _filterTags = res['tags'] != null ? res['tags'] : []; - _filterTags = res['tags']; - - _offset = res['offset'] != null && res['offset'].toString() != "0" - ? res['offset'].toString() - : ""; - - res['accounts_list'].forEach((_account) { - Account acc = Account.fromJson(_account); - _accountsObjforDropDown.add(acc.name); - _accountsList.add([acc.id, acc.name]); - }); - // _stageObjforDropDown = res['stage']; - //_leadSourceObjforDropDown = res['lead_source']; - if (res['stage'] != null) { - _stageObjforDropDown.clear(); - res['stage'].forEach((_stage) { - _stageObjforDropDown.add(_stage[1]); - }); - } - if (res['lead_source'] != null) { - _leadSourceObjforDropDown.clear(); - res['lead_source'].forEach((_leadsource) { - _leadSourceObjforDropDown.add(_leadsource[1]); - }); - } - res['currency'].forEach((curr) { - _currencyObjforDropDown.add(curr[1]); - }); - - _currencyList = res['currency']; - }).catchError((onError) { - print("fetchOpportunities Error >> $onError"); - }); - } - - Future createOpportunity({File? file}) async { - Map? result; - Map _copyOfCurrentEditOpportunity = new Map.from(_currentEditOpportunity); - _accountsList.forEach((element) { - if (element[1] == _copyOfCurrentEditOpportunity['account']) { - _copyOfCurrentEditOpportunity['account'] = element[0].toString(); - } - }); - if (_copyOfCurrentEditOpportunity['currency'] == "" || - _copyOfCurrentEditOpportunity['currency'] == null) { - _copyOfCurrentEditOpportunity['currency'] = ""; - } else { - _currencyList!.forEach((element) { - if (element[1] == _copyOfCurrentEditOpportunity['currency']) { - _copyOfCurrentEditOpportunity['currency'] = element[0]; - } - }); - } - _copyOfCurrentEditOpportunity['probability'] = - _copyOfCurrentEditOpportunity['probability'].toString(); - - _copyOfCurrentEditOpportunity['teams'] = - (_copyOfCurrentEditOpportunity['teams'].map((e) => e.toString())) - .toList() - .toString(); - _copyOfCurrentEditOpportunity['assigned_to'] = - (_copyOfCurrentEditOpportunity['assigned_to'].map((e) => e.toString())) - .toList() - .toString(); - _copyOfCurrentEditOpportunity['contacts'] = - (_copyOfCurrentEditOpportunity['contacts'].map((e) => e.toString())) - .toList() - .toString(); - - // if (_copyOfCurrentEditOpportunity['due_date'] != "") - // _copyOfCurrentEditOpportunity['due_date'] = - // DateFormat("yyyy-MM-dd") - // .format(DateFormat("dd-MM-yyyy") - // .parse(_copyOfCurrentEditOpportunity['due_date'])); - - _copyOfCurrentEditOpportunity['tags'] = - jsonEncode(_copyOfCurrentEditOpportunity['tags']); - _copyOfCurrentEditOpportunity - .removeWhere((key, value) => value.runtimeType != String); - _copyOfCurrentEditOpportunity - .removeWhere((key, value) => key == "closed_on"); - await CrmService() - .createOpportunity(_copyOfCurrentEditOpportunity, file!) - .then((response) async { - // var res = json.decode(response); # for multipartrequest - var res = json.decode(response.body); - if (res["error"] == false) { - await fetchOpportunities(); - await dashboardBloc.fetchDashboardDetails(); - } - result = res; - }).catchError((onError) { - print("createOpportunity Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - Future editOpportunity() async { - Map? result; - Map _copyOfCurrentEditOpportunity = new Map.from(_currentEditOpportunity); - - _accountsList.forEach((element) { - if (element[1] == _copyOfCurrentEditOpportunity['account']) { - _copyOfCurrentEditOpportunity['account'] = element[0].toString(); - } - }); - - if (_copyOfCurrentEditOpportunity['currency'] == "" || - _copyOfCurrentEditOpportunity['currency'] == null) { - _copyOfCurrentEditOpportunity['currency'] = ""; - } else { - _currencyList!.forEach((element) { - if (element[1] == _copyOfCurrentEditOpportunity['currency']) { - _copyOfCurrentEditOpportunity['currency'] = element[0]; - } - }); - } - _copyOfCurrentEditOpportunity['probability'] = - _copyOfCurrentEditOpportunity['probability'].toString(); - - _copyOfCurrentEditOpportunity['teams'] = - (_copyOfCurrentEditOpportunity['teams'].map((e) => e.toString())) - .toList() - .toString(); - _copyOfCurrentEditOpportunity['assigned_to'] = - (_copyOfCurrentEditOpportunity['assigned_to'].map((e) => e.toString())) - .toList() - .toString(); - _copyOfCurrentEditOpportunity['contacts'] = - (_copyOfCurrentEditOpportunity['contacts'].map((e) => e.toString())) - .toList() - .toString(); - _copyOfCurrentEditOpportunity['tags'] = - jsonEncode(_copyOfCurrentEditOpportunity['tags']); - - _copyOfCurrentEditOpportunity['opportunity_attachment'] = ""; - - if (_copyOfCurrentEditOpportunity['closed_on'] != "") - _copyOfCurrentEditOpportunity['due_date'] = DateFormat("yyyy-MM-dd") - .format(DateFormat("dd-MM-yyyy") - .parse(_copyOfCurrentEditOpportunity['closed_on'])); - - _copyOfCurrentEditOpportunity - .removeWhere((key, value) => value.runtimeType != String); - _copyOfCurrentEditOpportunity - .removeWhere((key, value) => key == "closed_on"); - - await CrmService() - .editOpportunity( - _copyOfCurrentEditOpportunity, _currentEditOpportunityId) - .then((response) async { - var res = json.decode(response.body); - if (res["error"] == false) { - await fetchOpportunities(); - await dashboardBloc.fetchDashboardDetails(); - } - result = res; - }); - // .catchError((onError) { - // print("editOpportunity Error >> $onError"); - // result = {"status": "error", "message": onError}; - // }); - return result; - } - - Future deleteOpportunity(Opportunity opportunity) async { - Map? result; - await CrmService() - .deletefromModule('opportunities', opportunity.id) - .then((response) async { - var res = (json.decode(response.body)); - opportunities.clear(); - await fetchOpportunities(); - await dashboardBloc.fetchDashboardDetails(); - result = res; - }).catchError((onError) { - print("deleteOpportunity Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - cancelCurrentEditOpportunity() { - _currentEditOpportunityId = null; - _currentEditOpportunity = { - 'name': "", - 'account': "", - 'stage': "", - 'currency': "", - 'amount': "", - 'lead_source': "", - 'probability': 0, - 'contacts': [], - 'due_date': DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())), - 'closed_on': "", - 'description': "", - 'assigned_to': [], - 'tags': [], - 'teams': [], - "opportunity_attachment": [] - }; - } - - updateCurrentEditOpportunity(Opportunity editOpportunity) { - List _contacts = []; - List _teams = []; - List _assignedUsers = []; - List _tags = []; - - _currentEditOpportunityId = editOpportunity.id.toString(); - editOpportunity.contacts!.forEach((contact) { - _contacts.add(contact.id); - }); - editOpportunity.assignedTo!.forEach((assignedAccount) { - _assignedUsers.add(assignedAccount.id); - }); - editOpportunity.teams!.forEach((team) { - _teams.add(team.id); - }); - editOpportunity.tags!.forEach((tag) { - _tags.add(tag['name']); - }); - - _currentEditOpportunity = { - 'name': editOpportunity.name, - 'account': editOpportunity.account!.name, - 'stage': editOpportunity.stage, - 'currency': editOpportunity.currency, - 'amount': editOpportunity.amount, - 'lead_source': editOpportunity.leadSource, - 'probability': editOpportunity.probability, - 'contacts': _contacts, - 'closed_on': editOpportunity.closedOn, - 'description': editOpportunity.description, - 'assigned_to': _assignedUsers, - 'tags': _tags, - 'teams': _teams, - 'opportunity_attachment': (editOpportunity.opportunityAttachment!.isEmpty) - ? [] - : editOpportunity.opportunityAttachment![0]['file_path'] - }; - } - - List get opportunities { - return _opportunities; - } - - int? get currentOpportunityIndex { - return _currentOpportunityIndex; - } - - set currentOpportunityIndex(index) { - _currentOpportunityIndex = index; - } - - Opportunity? get currentOpportunity { - return _currentOpportunity; - } - - set currentOpportunity(currOpp) { - _currentOpportunity = currOpp; - } - - String? get currentEditOpportunityId { - return _currentEditOpportunityId; - } - - set currentEditOpportunityId(id) { - _currentEditOpportunityId = id; - } - - Map get currentEditOpportunity { - return _currentEditOpportunity; - } - - set currentEditOpportunity(currEditOpp) { - _currentEditOpportunity = currEditOpp; - } - - List? get tags { - return _tags; - } - - List get filtertags { - return _filterTags; - } - - List get accountsObjforDropDown { - return _accountsObjforDropDown; - } - - List get accountsList { - return _accountsList; - } - - List get stageObjforDropDown { - return _stageObjforDropDown; - } - - List get leadSourceObjforDropDown { - return _leadSourceObjforDropDown; - } - - List get currencyObjforDropDown { - return _currencyObjforDropDown; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } -} - -final opportunityBloc = OpportunityBloc(); diff --git a/lib/bloc/setting_bloc.dart b/lib/bloc/setting_bloc.dart deleted file mode 100644 index 9061933..0000000 --- a/lib/bloc/setting_bloc.dart +++ /dev/null @@ -1,318 +0,0 @@ -import 'dart:convert'; - -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/domain.dart'; -import 'package:bottle_crm/model/email.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/user.dart'; -import 'package:bottle_crm/services/crm_services.dart'; -import 'package:intl/intl.dart'; -import 'package:bottle_crm/model/settings.dart'; - -class SettingsBloc { - Map _currentEditSetting = { - 'name': "", - 'last_name': "", - 'email': "", - 'domain': "" - }; - String? _currentEditSettingId; - int _currentSettingsTabIndex = 0; - Settings? _currentSettings; - -//////////////// API SETTINGS ///////////////////////// - List _users = []; - List _usersList = []; - List _apiSettings = []; - List _usersObjForDropDown = []; - String _offset = ""; - - Future fetchApiSettings({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - await CrmService() - .getApiSettings(queryParams: _copyFiltersData, offset: _offset) - .then((response) { - var res = json.decode(response.body); - _usersObjForDropDown.clear(); - _users.clear(); - _apiSettings.clear(); - res['api_settings'].forEach((_settings) { - Settings settings = Settings.fromJson(_settings); - if (_settings['api_settings'] != null && - _settings['api_settings'] != "") - settings.createdOn = DateFormat("dd MMM, yyyy").format( - DateFormat("yyyy-MM-dd").parse(_settings['api_settings'])); - //Settings settings = Settings.fromJson(_settings); - _apiSettings.add(settings); - }); - - res['users'].forEach((_user) { - User user = User.fromJson(_user); - _usersList.add(user); - }); - res['users'].forEach((_user) { - Profile user = Profile.fromJson(_user); - _users.add([user.id, user.firstName]); - _usersObjForDropDown.add(user.firstName); - }); - }); - } - - List get apiSettings { - return _apiSettings; - } - - List get usersList { - return _usersList; - } - - List get users { - return _users; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } - -//////////////// Setting Contacts ///////////////////////// - List _settingsContacts = []; - String _currentSettingsTab = "Contacts"; - Future fetchSettingsContacts({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - if (_copyFiltersData != null) { - _users.forEach((e) { - if (_copyFiltersData['created_by'] != null) { - if (e[1] == _copyFiltersData['created_by']) { - _copyFiltersData['created_by'] = e[0].toString(); - } - } - }); - } - - await CrmService() - .getSettingsContacts(queryParams: _copyFiltersData) - .then((response) { - var res = json.decode(response.body); - _settingsContacts.clear(); - _usersObjForDropDown.clear(); - _users.clear(); - - res['contacts'].forEach((_contact) { - Contact contact = Contact.fromJson(_contact); - if (_contact['created_on'] != null && _contact['created_on'] != "") - contact.createdOn = DateFormat("dd MMM, yyyy") - .format(DateFormat("yyyy-MM-dd").parse(_contact['created_on'])); - _settingsContacts.add(contact); - }); - res['users'].forEach((_user) { - Profile user = Profile.fromJson(_user); - _users.add([user.id, user.firstName]); - _usersObjForDropDown.add(user.firstName); - }); - }); - } - - Future deleteSettingsContacts(Contact contact) async { - Map? result; - await CrmService() - .deleteSettingsContacts(contact.id) - .then((response) async { - var res = (json.decode(response.body)); - await fetchSettingsContacts(); - result = res; - }).catchError((onError) { - print("deleteSettingsContacts Error >> $onError"); - result = {"status": "error", "message": "Something went wrong."}; - }); - return result; - } - - // List get settingsContacts { - // return _settingsContacts; - // } - - // List get users { - // return _users; - // } - - List get userObjForDropDown { - return _usersObjForDropDown; - } - - String get currentSettingsTab { - return _currentSettingsTab; - } - - set currentSettingsTab(tab) { - _currentSettingsTab = tab; - } - - //////////////// BLOCKED DOMAINS ///////////////////////// - - List _blockedDomains = []; - - Future fetchBlockedDomains({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - - await CrmService() - .getBlockedDomains(queryParams: _copyFiltersData) - .then((response) { - var res = json.decode(response.body); - _blockedDomains.clear(); - - res['blocked_domains'].forEach((_domain) { - Domain domain = Domain.fromJson(_domain); - _blockedDomains.add(domain); - }); - }); - } - - Future deleteBlockedDomains(Domain domain) async { - Map? result; - await CrmService().deleteBlockedDomains(domain.id).then((response) async { - var res = (json.decode(response.body)); - await fetchBlockedDomains(); - result = res; - }).catchError((onError) { - print("deleteBlockedDomains Error >> $onError"); - result = {"status": "error", "message": "Something went wrong."}; - }); - return result; - } - - List get blockedDomains { - return _blockedDomains; - } - - //////////////// BLOCKED EMAILS ///////////////////////// - - List _blockedEmails = []; - - Future fetchBlockedEmails({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - - await CrmService() - .getBlockedEmails(queryParams: _copyFiltersData) - .then((response) { - var res = json.decode(response.body); - _blockedEmails.clear(); - - res['blocked_emails'].forEach((_email) { - Email email = Email.fromJson(_email); - _blockedEmails.add(email); - }); - }); - } - - Future deleteBlockedEmails(Email email) async { - Map? result; - await CrmService().deleteBlockedEmails(email.id).then((response) async { - var res = (json.decode(response.body)); - await fetchBlockedEmails(); - result = res; - }).catchError((onError) { - print("deleteBlockedEmails Error >> $onError"); - result = {"status": "error", "message": "Something went wrong."}; - }); - return result; - } - - List get blockedEmails { - return _blockedEmails; - } - - Future createSetting() async { - Map? _result; - Map _copyOfCurrentEditSetting = Map.from(_currentEditSetting); - await CrmService() - .createSetting(_copyOfCurrentEditSetting) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchBlockedDomains(); - await fetchBlockedEmails(); - await fetchSettingsContacts(); - } - _result = res; - }).catchError((onError) { - print("createUser Error >> $onError"); - _result = {"status": "error", "message": "Something went wrong."}; - }); - return _result; - } - - updateCurrentEditSetting(setting) { - _currentEditSettingId = setting.id.toString(); - - if (_currentSettingsTab == "Contacts") { - _currentEditSetting['name'] = setting.name; - _currentEditSetting['last_name'] = setting.lastName; - _currentEditSetting['email'] = setting.email; - } else if (_currentSettingsTab == "Blocked Domains") { - _currentEditSetting['domain'] = setting.domain; - } else { - _currentEditSetting['email'] = setting.email; - } - } - - resetValues() { - _currentEditSetting['name'] = ""; - _currentEditSetting['last_name'] = ""; - _currentEditSetting['email'] = ""; - _currentEditSetting['domain'] = ""; - } - - Future editSetting() async { - Map? _result; - Map _copyOfCurrentEditSetting = Map.from(_currentEditSetting); - await CrmService() - .editSetting(_copyOfCurrentEditSetting, currentEditSettingId) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchBlockedDomains(); - await fetchBlockedEmails(); - await fetchSettingsContacts(); - } - _result = res; - }).catchError((onError) { - print("createSetting Error >> $onError"); - _result = {"status": "error", "message": "Something went wrong."}; - }); - return _result; - } - - Map get currentEditSetting { - return _currentEditSetting; - } - - String? get currentEditSettingId { - return _currentEditSettingId; - } - - Settings? get currentSettings { - return _currentSettings; - } - - set currentSettings(settings) { - _currentSettings = settings; - } - - int get currentSettingsTabIndex { - return _currentSettingsTabIndex; - } - - set currentSettingsTabIndex(currentSettingsTabIndex) { - _currentSettingsTabIndex = currentSettingsTabIndex; - } -} - -final settingsBloc = SettingsBloc(); diff --git a/lib/bloc/task_bloc.dart b/lib/bloc/task_bloc.dart deleted file mode 100644 index 05375a8..0000000 --- a/lib/bloc/task_bloc.dart +++ /dev/null @@ -1,273 +0,0 @@ -import 'dart:convert'; - -import 'package:bottle_crm/model/task.dart'; -import 'package:bottle_crm/services/crm_services.dart'; -import 'package:intl/intl.dart'; - -import '../model/account.dart'; - -class TaskBloc { - List _tasks = []; - List? _status = []; - List? _priorities = []; - Map? _currentEditTask = { - 'title': "", - 'status': "", - 'priority': "", - 'due_date': "", - 'account': null, - 'contacts': [], - 'teams': [], - 'assigned_to': [] - }; - List _accounts = []; - List _accountsObjforDropDown = []; - String? _currentEditTaskId; - Task? _currentTask; - int? _currentTaskIndex; - String _offset = ""; - - Future fetchTasks({filtersData}) async { - await CrmService() - .getTasks(queryParams: filtersData, offset: _offset) - .then((response) { - var res = json.decode(response.body); - - //_tasks.clear(); - _status!.clear(); - _priorities!.clear(); - if (res['accounts_list'] != null) { - _accounts.clear(); - res['accounts_list'].forEach((_account) { - Account account = Account.fromJson(_account); - _accounts.add(account); - _accountsObjforDropDown.add(account.name); - }); - } - - if (res['tasks'] != null) { - res['tasks'].forEach((_task) { - _accounts.forEach((Account _account) { - if (_task['account'] == _account.id) { - _task['account'] = _account; - } - }); - Task task = Task.fromJson(_task); - _tasks.add(task); - }); - } - - if (res['status'] != null) { - _status!.clear(); - res['status'].forEach((_priority) { - _status!.add(_priority[1]); - }); - } - // if (res['status'] != null) { - // _status.clear(); - // _status = res['status']; - // } - // if (res['priority'] != null) { - // _priorities.clear(); - // _priorities = res['priority']; - // } - if (res['priority'] != null) { - _priorities!.clear(); - res['priority'].forEach((_priority) { - _priorities!.add(_priority[1]); - }); - } - }); - } - - Future createTask() async { - Map? result; - Map _copyCurrentEditTask = Map.from(_currentEditTask!); - _copyCurrentEditTask['contacts'] = (_copyCurrentEditTask['contacts'] - .map((contacts) => contacts.toString())).toList().toString(); - - _copyCurrentEditTask['teams'] = (_copyCurrentEditTask['teams'] - .map((teams) => teams.toString())).toList().toString(); - - _copyCurrentEditTask['assigned_to'] = (_copyCurrentEditTask['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - - if (_copyCurrentEditTask['due_date'] != "") - _copyCurrentEditTask['due_date'] = DateFormat("yyyy-MM-dd").format( - DateFormat("dd-MM-yyyy").parse(_copyCurrentEditTask['due_date'])); - - accounts.forEach((account) { - if (account.name == _copyCurrentEditTask['account']) { - _copyCurrentEditTask['account'] = account.id.toString(); - } - }); - - await CrmService().createTask(_copyCurrentEditTask).then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchTasks(); - } - result = res; - }); - // .catchError((onError) { - // print('createTask Error >> $onError'); - // result = {"status": "error", "message": "Something went wrong"}; - // }); - return result; - } - - Future editTask() async { - Map? _result; - Map _copyOfCurrentEditTask = Map.from(_currentEditTask!); - - _copyOfCurrentEditTask['contacts'] = (_copyOfCurrentEditTask['contacts'] - .map((contacts) => contacts.toString())).toList().toString(); - - _copyOfCurrentEditTask['teams'] = (_copyOfCurrentEditTask['teams'] - .map((teams) => teams.toString())).toList().toString(); - - _copyOfCurrentEditTask['assigned_to'] = - (_copyOfCurrentEditTask['assigned_to'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - - accounts.forEach((account) { - if (account.name == _copyOfCurrentEditTask['account']) { - _copyOfCurrentEditTask['account'] = account.id.toString(); - } - }); - - if (_copyOfCurrentEditTask['due_date'] != "") - _copyOfCurrentEditTask['due_date'] = DateFormat("yyyy-MM-dd").format( - DateFormat("dd-MM-yyyy").parse(_copyOfCurrentEditTask['due_date'])); - - await CrmService() - .editTask(_copyOfCurrentEditTask, _currentEditTaskId) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchTasks(); - } - _result = res; - }).catchError((onError) { - print("editTask Error >> $onError"); - _result = {"status": "error", "message": "Something went wrong."}; - }); - return _result; - } - - Future deleteTask(Task task) async { - Map? result; - await CrmService().deleteTask(task.id).then((response) async { - var res = (json.decode(response.body)); - await fetchTasks(); - result = res; - }).catchError((onError) { - print("deleteTask Error >> $onError"); - result = {"status": "error", "message": "Something went wrong."}; - }); - return result; - } - - cancelCurrentEditTask() { - _currentEditTaskId = null; - _currentEditTask = { - 'title': "", - 'status': "", - 'priority': "", - 'due_date': "", - 'account': null, - 'contacts': [], - 'teams': [], - 'assigned_to': [] - }; - } - - updateCurrentEditTask(Task task) { - _currentEditTaskId = task.id.toString(); - - List contacts = []; - List teams = []; - List assignedUsers = []; - - task.contacts!.forEach((contact) { - contacts.add(contact.id); - }); - - task.assignedTo!.forEach((assignedAccount) { - assignedUsers.add(assignedAccount.id); - }); - - task.teams!.forEach((team) { - teams.add(team.id); - }); - - _currentEditTask = { - 'title': task.title, - 'status': task.status, - 'priority': task.priority, - 'due_date': task.dueDate, - 'account': null, - 'contacts': contacts, - 'teams': teams, - 'assigned_to': assignedUsers - }; - } - - List get tasks { - return _tasks; - } - - List? get status { - return _status; - } - - List? get priorities { - return _priorities; - } - - Map? get currentEditTask { - return _currentEditTask; - } - - String? get currentEditTaskId { - return _currentEditTaskId; - } - - set currentEditTaskId(id) { - _currentEditTaskId = id; - } - - Task? get currentTask { - return _currentTask; - } - - set currentTask(task) { - _currentTask = task; - } - - int? get currentTaskIndex { - return _currentTaskIndex; - } - - set currentTaskIndex(index) { - _currentTaskIndex = index; - } - - List get accounts { - return _accounts; - } - - List get accountsObjforDropDown { - return _accountsObjforDropDown; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } -} - -final taskBloc = TaskBloc(); diff --git a/lib/bloc/team_bloc.dart b/lib/bloc/team_bloc.dart deleted file mode 100644 index b3dfce1..0000000 --- a/lib/bloc/team_bloc.dart +++ /dev/null @@ -1,203 +0,0 @@ -import 'dart:convert'; - -import 'package:bottle_crm/model/team.dart'; -import 'package:bottle_crm/services/crm_services.dart'; - -class TeamBloc { - List _teams = []; - List _users = []; - List _usersObjForDropDown = []; - List _teamsObjForDropdown = []; - Map? _currentEditTeam = { - 'name': "", - 'description': "", - 'assign_users': [], - }; - String? _currentEditTeamId; - Team? _currentTeam; - int? _currentTeamIndex; - String _offset = ""; - - Future fetchTeams({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - if (_copyFiltersData != null) { - _users.forEach((e) { - if (_copyFiltersData['created_by'] != null) { - if (e[1] == _copyFiltersData['created_by']) { - _copyFiltersData['created_by'] = e[0].toString(); - } - } - }); - if (_copyFiltersData['assigne_users'].length != 0) { - _copyFiltersData['assigne_users'] = (_copyFiltersData['assigne_users'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - } - } - - await CrmService().getTeams(queryParams: _copyFiltersData, offset: _offset).then((response) { - var res = json.decode(response.body); - - _teams.clear(); - _users.clear(); - _usersObjForDropDown.clear(); - - res['teams'].forEach((_team) { - Team team = Team.fromJson(_team); - _teams.add(team); - }); - - _teams.forEach((_team) { - Map team = {}; - team['id'] = _team.id; - team['name'] = _team.name!; - _teamsObjForDropdown.add(team); - }); - // res['users'].forEach((_user) { - // Profile user = Profile.fromJson(_user); - // _users.add([user.id, user.firstName]); - // _usersObjForDropDown.add(user.firstName); - // }); - }); - } - - Future createTeam() async { - Map? result; - //print("----"); - // print(_currentEditTeam); - Map _copyCurrentEditTeam = Map.from(_currentEditTeam!); - - _copyCurrentEditTeam['assign_users'] = (_copyCurrentEditTeam['assign_users'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - //print("==="); - // print(_copyCurrentEditTeam); - - - await CrmService().createTeam(_copyCurrentEditTeam).then((response) async { - print(_copyCurrentEditTeam); - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchTeams(); - } - result = res; - }); - - print(result) -; // .catchError((onError) { - // print('createTeam Error >> $onError'); - // result = {"status": "error", "message": "Something went wrong"}; - // }); - return result; - } - - Future editTeam() async { - Map? _result; - Map _copyOfCurrentEditTeam = Map.from(_currentEditTeam!); - - _copyOfCurrentEditTeam['assign_users'] = - (_copyOfCurrentEditTeam['assign_users'] - .map((assignedTo) => assignedTo.toString())).toList().toString(); - await CrmService() - .editTeam(_copyOfCurrentEditTeam, _currentEditTeamId) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchTeams(); - } - _result = res; - }).catchError((onError) { - print("editTeam Error >> $onError"); - _result = {"status": "error", "message": "Something went wrong."}; - }); - return _result; - } - - Future deleteTeam(Team team) async { - Map? result; - await CrmService().deleteTeam(team.id).then((response) async { - var res = (json.decode(response.body)); - await fetchTeams(); - result = res; - }).catchError((onError) { - print("deleteTeam Error >> $onError"); - result = {"status": "error", "message": "Something went wrong."}; - }); - return result; - } - - cancelCurrentEditTeam() { - _currentEditTeamId = null; - _currentEditTeam = { - 'name': "", - 'description': "", - 'assign_users': [], - }; - } - - updateCurrentEditTeam(Team team) { - List _currUsers = []; - _currentEditTeamId = team.id.toString(); - _currentEditTeam = { - 'name': team.name, - 'description': team.description, - }; - team.users!.forEach((element) { - _currUsers.add(element.id); - }); - _currentEditTeam!['assign_users'] = _currUsers; - } - - List get teams { - return _teams; - } - - List get users { - return _users; - } - - List get userObjForDropDown { - return _usersObjForDropDown; - } - - List get teamsObjForDropdown { - return _teamsObjForDropdown; - } - - Map? get currentEditTeam { - return _currentEditTeam; - } - - String? get currentEditTeamId { - return _currentEditTeamId; - } - - set currentEditTeamId(id) { - _currentEditTeamId = id; - } - - Team? get currentTeam { - return _currentTeam; - } - - set currentTeam(team) { - _currentTeam = team; - } - - int? get currentTeamIndex { - return _currentTeamIndex; - } - - set currentTeamIndex(index) { - _currentTeamIndex = index; - } - - String get offset { - return _offset; - } - - set offset(offset) { - _offset = offset; - } -} - -final teamBloc = TeamBloc(); diff --git a/lib/bloc/user_bloc.dart b/lib/bloc/user_bloc.dart deleted file mode 100644 index d46feac..0000000 --- a/lib/bloc/user_bloc.dart +++ /dev/null @@ -1,251 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:bottle_crm/model/user.dart'; -import 'package:bottle_crm/services/crm_services.dart'; - -class UserBloc { - List _activeUsers = []; - List _inActiveUsers = []; - List _usersObjForDropdown = []; - List _statusObjForDropdown = []; - List _rolesObjForDropdown = []; - User? _currentUser; - String _currentUserStatus = "Active"; - int? _currentUserIndex; - String? _currentEditUserId; - Map _currentEditUser = { - 'username': "", - 'role': "USER", - 'status': "Active", - 'profile_pic': "", - 'date_joined': "", - 'email': "", - 'phone': "", - "password": "", - 'first_name': "", - 'last_name': "", - 'has_marketing_access': false, - 'has_sales_access': false, - 'is_active': "True", - 'is_organization_admin': false, - 'description':"", - }; - - Future fetchUsers({filtersData}) async { - Map? _copyFiltersData = - filtersData != null ? new Map.from(filtersData) : null; - - await CrmService().getUsers(queryParams: _copyFiltersData).then((response) { - _activeUsers.clear(); - _inActiveUsers.clear(); - - var res = json.decode(response.body); - if (res['active_users']['active_users'] != null) - res['active_users']['active_users'].forEach((_user) { - User user = User.fromJson(_user); - _activeUsers.add(user); - }); - res['inactive_users']['inactive_users'].forEach((_user) { - User user = User.fromJson(_user); - _activeUsers.add(user); - }); - //_statusObjForDropdown = res['status']; - //_rolesObjForDropdown = res['roles']; - if (res['active_users'] != null) { - // _users.clear(); - // _usersObjForDropdown.clear(); - // res['active_users'].forEach((_user) { - // Profile user = Profile.fromJson(_user); - // _users.add(user); - // }); - _usersObjForDropdown.clear(); - _activeUsers.forEach((_user) { - Map user = {}; - user['id'] = _user.id; - user['name'] = _user.firstName! + ' ' + _user.lastName!; - _usersObjForDropdown.add(user); - }); - } - if (res['roles'] != null) { - _rolesObjForDropdown.clear(); - res['roles'].forEach((_role) { - _rolesObjForDropdown.add(_role[1]); - }); - } - if (res['status'] != null) { - _statusObjForDropdown.clear(); - res['status'].forEach((_status) { - _statusObjForDropdown.add(_status[1]); - }); - } - }).catchError((onError) { - print('fetchUsers Error >> $onError'); - }); - } - - Future createUser({File? file}) async { - Map? _result; - Map _copyOfCurrentEditUser = Map.from(_currentEditUser); - _copyOfCurrentEditUser['has_marketing_access'] = - json.encode(_copyOfCurrentEditUser['has_marketing_access']); - _copyOfCurrentEditUser['has_sales_access'] = - json.encode(_copyOfCurrentEditUser['has_sales_access']); - _copyOfCurrentEditUser['role'] = _copyOfCurrentEditUser['is_admin']; - _copyOfCurrentEditUser['status'] = _copyOfCurrentEditUser['is_active']; - await CrmService() - .createUser(_copyOfCurrentEditUser, file!) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchUsers(); - } - _result = res; - }).catchError((onError) { - print("createUser Error >> $onError"); - _result = {"status": "error", "message": onError}; - }); - return _result; - } - - editUser() async { - Map? _result; - Map _copyOfCurrentEditUser = Map.from(_currentEditUser); - _copyOfCurrentEditUser['has_marketing_access'] = - json.encode(_copyOfCurrentEditUser['has_marketing_access']); - _copyOfCurrentEditUser['has_sales_access'] = - json.encode(_copyOfCurrentEditUser['has_sales_access']); - _copyOfCurrentEditUser['role'] = _copyOfCurrentEditUser['is_admin']; - _copyOfCurrentEditUser['status'] = _copyOfCurrentEditUser['is_active']; - await CrmService() - .editUser(_copyOfCurrentEditUser, _currentEditUserId) - .then((response) async { - var res = json.decode(response.body); - if (res['error'] == false) { - await fetchUsers(); - } - _result = res; - }).catchError((onError) { - print("editUser Error >> $onError"); - _result = {"status": "error", "message": onError}; - }); - return _result; - } - - Future deleteUser(User user) async { - Map? result; - await CrmService().deleteUser(user.id).then((response) async { - var res = (json.decode(response.body)); - await fetchUsers(); - result = res; - }).catchError((onError) { - print("deleteUser Error >> $onError"); - result = {"status": "error", "message": onError}; - }); - return result; - } - - cancelCurrentEditUser() { - _currentEditUserId = null; - _currentEditUser = { - 'username': "", - 'role': "", - 'profile_pic': "", - 'date_joined': "", - 'email': "", - 'phone': "", - "password": "", - 'first_name': "", - 'last_name': "", - 'has_marketing_access': false, - 'has_sales_access': false, - 'is_active': "True", - 'is_organization_admin': "USER", - }; - } - - updateCurrentEditUser(User user) { - _currentEditUserId = user.id.toString(); - - _currentEditUser['username'] = user.firstName; - _currentEditUser['role'] = user.role; - _currentEditUser['profile_pic'] = user.profilePic; - //_currentEditUser['date_joined'] = user.dateOfJoin; - _currentEditUser['email'] = user.email; - _currentEditUser['first_name'] = user.firstName; - _currentEditUser['last_name'] = user.lastName; - _currentEditUser['has_marketing_access'] = user.hasMarktingAccess; - _currentEditUser['has_sales_access'] = user.hasSalesAccess; - - if (user.isActive == true) { - _currentEditUser['is_active'] = "True"; - } else { - _currentEditUser['is_active'] = "False"; - } - if (user.role == "ADMIN") { - _currentEditUser['is_organization_admin'] = true; - _currentEditUser['has_marketing_access'] = true; - _currentEditUser['has_sales_access'] = true; - } else { - _currentEditUser['is_organization_admin'] = false; - } - } - - List get activeUsers { - return _activeUsers; - } - - List get inActiveUsers { - return _inActiveUsers; - } - - List get statusObjForDropdown { - return _statusObjForDropdown; - } - - List get rolesObjForDropdown { - return _rolesObjForDropdown; - } - - User? get currentUser { - return _currentUser; - } - - set currentUser(user) { - _currentUser = user; - } - - String get currentUserStatus { - return _currentUserStatus; - } - - List get usersObjForDropdown { - return _usersObjForDropdown; - } - - set currentUserStatus(status) { - _currentUserStatus = status; - } - - int? get currentUserIndex { - return _currentUserIndex; - } - - set currentUserIndex(index) { - _currentUserIndex = index; - } - - set currentEditUserId(id) { - _currentEditUserId = id; - } - - String? get currentEditUserId { - return _currentEditUserId; - } - - Map get currentEditUser { - return _currentEditUser; - } -} - -final userBloc = UserBloc(); diff --git a/lib/config/api_config.dart b/lib/config/api_config.dart new file mode 100644 index 0000000..c3c4d6f --- /dev/null +++ b/lib/config/api_config.dart @@ -0,0 +1,83 @@ +import 'package:flutter/foundation.dart'; + +class ApiConfig { + // Base URLs for different environments + static const String _developmentUrl = 'https://b2ad5166b831.ngrok-free.app'; + static const String _productionUrl = 'https://api.bottlecrm.io'; + + // Get base URL based on debug mode + static String get baseUrl => kDebugMode ? _developmentUrl : _productionUrl; + + static String get apiBaseUrl => baseUrl; + + // Authentication endpoints + static String get googleLogin => '$apiBaseUrl/auth/google'; + static String get logout => '$apiBaseUrl/auth/logout'; + + // Contacts endpoints + static String get contacts => '$apiBaseUrl/contacts'; + static String get contactById => '$apiBaseUrl/contacts/{id}'; + static String get contactSearch => '$apiBaseUrl/contacts/search'; + + // Leads endpoints + static String get leads => '$apiBaseUrl/leads'; + static String get leadById => '$apiBaseUrl/leads/{id}'; + static String get leadSearch => '$apiBaseUrl/leads/search'; + static String get leadConvert => '$apiBaseUrl/leads/{id}/convert'; + + // Deals endpoints + static String get deals => '$apiBaseUrl/deals'; + static String get dealById => '$apiBaseUrl/deals/{id}'; + static String get dealSearch => '$apiBaseUrl/deals/search'; + static String get dealStages => '$apiBaseUrl/deals/stages'; + + // Companies endpoints + static String get companies => '$apiBaseUrl/companies'; + static String get companyById => '$apiBaseUrl/companies/{id}'; + static String get companySearch => '$apiBaseUrl/companies/search'; + + // Organizations endpoints + static String get organizations => '$apiBaseUrl/organizations'; + + // Activities endpoints + static String get activities => '$apiBaseUrl/activities'; + static String get activityById => '$apiBaseUrl/activities/{id}'; + static String get activityTypes => '$apiBaseUrl/activities/types'; + + // Tasks endpoints + static String get tasks => '$apiBaseUrl/tasks'; + static String get taskById => '$apiBaseUrl/tasks/{id}'; + static String get taskSearch => '$apiBaseUrl/tasks/search'; + + // Dashboard endpoints + static String get dashboard => '$apiBaseUrl/dashboard'; + + // Settings endpoints + static String get settings => '$apiBaseUrl/settings'; + static String get userSettings => '$apiBaseUrl/settings/user'; + + // File upload endpoints + static String get upload => '$apiBaseUrl/upload'; + static String get uploadAvatar => '$apiBaseUrl/upload/avatar'; + + // Helper method to replace path parameters + static String replacePathParam(String url, String param, String value) { + return url.replaceAll('{$param}', value); + } + + // Request timeout configurations + static const Duration connectTimeout = Duration(seconds: 30); + static const Duration receiveTimeout = Duration(seconds: 30); + static const Duration sendTimeout = Duration(seconds: 30); + + // Common headers + static Map get defaultHeaders => { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }; + + static Map getAuthHeaders(String token) => { + ...defaultHeaders, + 'Authorization': 'Bearer $token', + }; +} diff --git a/lib/main.dart b/lib/main.dart index 19809ca..eaedfb1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,129 +1,6 @@ -import 'package:bottle_crm/ui/screens/accounts/accounts_list.dart'; -import 'package:bottle_crm/ui/screens/accounts/account_create.dart'; -import 'package:bottle_crm/ui/screens/accounts/account_details.dart'; -import 'package:bottle_crm/ui/screens/authentication/change_password.dart'; -import 'package:bottle_crm/ui/screens/authentication/companies_List.dart'; -import 'package:bottle_crm/ui/screens/authentication/forgot_password.dart'; -import 'package:bottle_crm/ui/screens/authentication/login.dart'; -import 'package:bottle_crm/ui/screens/authentication/profile.dart'; -import 'package:bottle_crm/ui/screens/authentication/register.dart'; -import 'package:bottle_crm/ui/screens/cases/case_create.dart'; -import 'package:bottle_crm/ui/screens/cases/case_details.dart'; -import 'package:bottle_crm/ui/screens/cases/cases_list.dart'; -import 'package:bottle_crm/ui/screens/contacts/contact_create.dart'; -import 'package:bottle_crm/ui/screens/contacts/contact_details.dart'; -import 'package:bottle_crm/ui/screens/contacts/contacts_list.dart'; -import 'package:bottle_crm/ui/screens/dashboard/dashboard.dart'; -import 'package:bottle_crm/ui/screens/documents/documents_list.dart'; -import 'package:bottle_crm/ui/screens/events/event_create.dart'; -import 'package:bottle_crm/ui/screens/events/event_details.dart'; -import 'package:bottle_crm/ui/screens/events/events_list.dart'; -import 'package:bottle_crm/ui/screens/invoices/invoices_list.dart'; -import 'package:bottle_crm/ui/screens/leads/lead_create.dart'; -import 'package:bottle_crm/ui/screens/leads/lead_details.dart'; -import 'package:bottle_crm/ui/screens/leads/leads_list.dart'; -import 'package:bottle_crm/ui/screens/more_options_screen.dart'; -import 'package:bottle_crm/ui/screens/opportunities/opportunitie_create.dart'; -import 'package:bottle_crm/ui/screens/opportunities/opportunitie_details.dart'; -import 'package:bottle_crm/ui/screens/opportunities/opportunities_list.dart'; -import 'package:bottle_crm/ui/screens/settings/settings.dart'; -import 'package:bottle_crm/ui/screens/settings/settings_details.dart'; -import 'package:bottle_crm/ui/screens/settings/settings_userDetails.dart'; -import 'package:bottle_crm/ui/screens/tasks/task_create.dart'; -import 'package:bottle_crm/ui/screens/tasks/task_details.dart'; -import 'package:bottle_crm/ui/screens/tasks/tasks_list.dart'; -import 'package:bottle_crm/ui/screens/teams/team_create.dart'; -import 'package:bottle_crm/ui/screens/teams/team_details.dart'; -import 'package:bottle_crm/ui/screens/teams/teams_list.dart'; -import 'package:bottle_crm/ui/screens/users/user_create.dart'; -import 'package:bottle_crm/ui/screens/users/user_details.dart'; -import 'package:bottle_crm/ui/screens/users/users_list.dart'; import 'package:flutter/material.dart'; -import 'package:firebase_core/firebase_core.dart'; +import 'app.dart'; -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - //static FirebaseAnalytics _analytics = FirebaseAnalytics.instance; - // static FirebaseAnalyticsObserver getAnalyticsObserver = - // FirebaseAnalyticsObserver(analytics: _analytics); - @override - Widget build(BuildContext context) { - return MaterialApp( - //navigatorObservers: [getAnalyticsObserver], - title: 'bottlecrm', - debugShowCheckedModeBanner: false, - theme: ThemeData( - buttonTheme:ButtonThemeData(buttonColor:Color.fromRGBO(62, 121, 247, 1)), - scaffoldBackgroundColor: Color.fromRGBO(236, 238, 244, 1), - //buttonColor: Color.fromRGBO(62, 121, 247, 1), - primaryColor: Color.fromRGBO(62, 121, 247, 1), - secondaryHeaderColor: Color.fromRGBO(112, 121, 128, 1), - dividerColor: Color.fromRGBO(69, 85, 96, 1)), - home: Login(), - onUnknownRoute: (RouteSettings settings) { - return MaterialPageRoute( - settings: settings, - builder: (BuildContext context) => Scaffold( - body: Container( - alignment: Alignment.center, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('oops! something went wrong.', - style: TextStyle(fontSize: 20.0)) - ], - ), - )), - ); - }, - routes: { - '/login': (BuildContext context) => Login(), - '/register': (BuildContext context) => Register(), - '/forgot_password': (BuildContext context) => ForgotPassword(), - '/change_password': (BuildContext context) => ChangePassword(), - '/profile': (BuildContext context) => Profile(), - '/dashboard': (BuildContext context) => Dashboard(), - '/more_options': (BuildContext context) => MoreOptions(), - '/leads_list': (BuildContext context) => LeadsList(), - '/leads_create': (BuildContext context) => CreateLead(), - '/lead_details': (BuildContext context) => LeadDetails(), - '/accounts_list': (BuildContext context) => AccountsList(), - '/account_create': (BuildContext context) => CreateAccount(), - '/account_details': (BuildContext context) => AccountDetails(), - '/cases_list': (BuildContext context) => CasesList(), - '/case_details': (BuildContext context) => CaseDetails(), - '/case_create': (BuildContext context) => CreateCase(), - '/contacts_list': (BuildContext context) => ContactsList(), - '/contact_create': (BuildContext context) => CreateContact(), - '/contact_details': (BuildContext context) => ContactDetails(), - '/documents_list': (BuildContext context) => DocumentsList(), - '/events_list': (BuildContext context) => EventsList(), - '/event_details': (BuildContext context) => EventDetails(), - '/event_create': (BuildContext context) => CreateEvent(), - '/invoices_list': (BuildContext context) => InvoicesList(), - '/opportunitie_create': (BuildContext context) => CreateOpportunities(), - '/opportunitie_details': (BuildContext context) => - OpportunitiesDetails(), - '/opportunities_list': (BuildContext context) => OpportunitiesList(), - '/tasks_list': (BuildContext context) => TasksList(), - '/task_details': (BuildContext context) => TasskDeails(), - '/task_create': (BuildContext context) => CreateTask(), - '/teams_list': (BuildContext context) => TeamsList(), - '/team_create': (BuildContext context) => CreateTeam(), - '/team_details': (BuildContext context) => TeamkDeails(), - '/users_list': (BuildContext context) => UsersList(), - '/user_create': (BuildContext context) => CreateUser(), - '/user_details': (BuildContext context) => UserDetails(), - '/companies_List': (BuildContext context) => CompaniesList(), - '/settings_List': (BuildContext context) => SettingsList(), - '/settings_details': (BuildContext context) => SettingsDeails(), - '/settings_userDetails': (BuildContext context) => SettingsUserDetails(), - }, - ); - } +void main() { + runApp(const BottleCrmApp()); } diff --git a/lib/model/account.dart b/lib/model/account.dart deleted file mode 100644 index 42bf981..0000000 --- a/lib/model/account.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/lead.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:intl/intl.dart'; - -class Account { - int? id; - String? name; - String? email; - String? phone; - String? industry; - String? billingAddressLine; - String? billingStreet; - String? billingCity; - String? billingState; - String? billingPostcode; - String? billingCountry; - String? website; - String? description; - Profile? createdBy; - String? createdOn; - bool? isActive; - List? tags; - String? status; - Lead? lead; - String? contactName; - List? contacts; - List? assignedTo; - List? teams; - - Account( - {this.id, - this.name, - this.email, - this.phone, - this.industry, - this.billingAddressLine, - this.billingStreet, - this.billingCity, - this.billingState, - this.billingPostcode, - this.billingCountry, - this.website, - this.description, - this.createdBy, - this.assignedTo, - this.createdOn, - this.isActive, - this.tags, - this.status, - this.lead, - this.contactName, - this.contacts, - this.teams}); - - Account.fromJson(Map account) { - this.id = account['id'] != null ? account['id'] : 0; - this.name = account['name'] != null ? account['name'] : ""; - this.email = account['email'] != null ? account['email'] : ""; - this.phone = account['phone'] != null ? account['phone'] : ""; - this.industry = account['industry'] != null ? account['industry'] : ""; - this.billingAddressLine = account['billing_address_line'] != null - ? account['billing_address_line'] - : ""; - this.billingStreet = - account['billing_street'] != null ? account['billing_street'] : ""; - this.billingCity = - account['billing_city'] != null ? account['billing_city'] : ""; - this.billingState = - account['billing_state'] != null ? account['billing_state'] : ""; - this.billingPostcode = - account['billing_postcode'] != null ? account['billing_postcode'] : ""; - this.billingCountry = - account['billing_country'] != null ? account['billing_country'] : ""; - this.website = account['website'] != null ? account['website'] : ""; - this.description = - account['description'] != null ? account['description'] : ""; - this.createdBy = account['created_by'] != null - ? Profile.fromJson(account['created_by']) - : Profile(); - this.assignedTo = account['assigned_to'] != null - ? List.from( - account['assigned_to'].map((x) => Profile.fromJson(x))) - : []; - this.createdOn = account['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(account['created_on'])) - : ""; - this.isActive = account['is_active'] != null ? account['is_active'] : false; - this.tags = account['tags'] != null ? account['tags'] : []; - this.status = account['status'] != null ? account['status'] : ""; - this.lead = - account['lead'] != null ? Lead.fromJson(account['lead']) : Lead(); - this.contactName = - account['contact_name'] != null ? account['contact_name'] : ""; - this.contacts = account["contacts"] != null - ? List.from( - account["contacts"].map((x) => Contact.fromJson(x))) - : []; - this.teams = account["teams"] != null - ? List.from(account["teams"].map((x) => Team.fromJson(x))) - : []; - } - - toJson() { - return { - 'id': id, - 'name': name, - 'email': email, - 'phone': phone, - 'industry': industry, - 'billing_address_line': billingAddressLine, - 'billing_street': billingStreet, - 'billing_city': billingCity, - 'billing_state': billingState, - 'billing_postcode': billingPostcode, - 'billing_country': billingCountry, - 'website': website, - 'description': description, - 'created_by': createdBy, - 'assigned_to': assignedTo, - 'created_on': createdOn, - 'is_active': isActive, - 'tags': tags, - 'status': status, - 'lead': lead, - 'contact_name': contactName, - 'contacts': contacts, - 'teams': teams - }; - } -} diff --git a/lib/model/case.dart b/lib/model/case.dart deleted file mode 100644 index d051e17..0000000 --- a/lib/model/case.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:intl/intl.dart'; - -import 'account.dart'; -import 'company.dart'; - -class Case { - int? id; - String? name; - String? status; - String? priority; - String? caseType; - String? closedOn; - String? description; - Profile? createdBy; - String? createdOn; - bool? isActive; - Account? account; - List? contacts; - List? teams; - List? assignedTo; - Company? company; - String? createdOnText; - List? caseAttachment; - - Case( - {this.id, - this.name, - this.status, - this.priority, - this.caseType, - this.closedOn, - this.description, - this.createdBy, - this.createdOn, - this.isActive, - this.account, - this.contacts, - this.teams, - this.assignedTo, - this.company, - this.createdOnText, - this.caseAttachment}); - - Case.fromJson(Map data) { - this.status = data['status'] != null ? data['status'] : ""; - this.priority = data['priority'] != null ? data['priority'] : ""; - this.caseType = data['type_of_case'] != null ? data['type_of_case'] : ""; - this.id = data['id'] != null ? data['id'] : 0; - this.name = data['name'] != null ? data['name'] : ""; - this.account = - data['account'] != null ? Account.fromJson(data['account']) : Account(); - this.contacts = data['contacts'] != null - ? List.from(data["contacts"].map((x) => Contact.fromJson(x))) - : []; - - this.closedOn = data['closed_on'] != null ? data['closed_on'] : ""; - this.description = data['description'] != null ? data['description'] : ""; - this.assignedTo = data['assigned_to'] != null - ? List.from( - data["assigned_to"].map((x) => Profile.fromJson(x))) - : []; - this.createdBy = data['created_by'] != null - ? Profile.fromJson(data['created_by']) - : Profile(); - this.createdOn = data['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(data['created_on'])) - : ""; - this.isActive = data['is_active'] != null ? data['is_active'] : false; - this.teams = data['teams'] != null - ? List.from(data["teams"].map((x) => Team.fromJson(x))) - : []; - this.company = - data['company'] != null ? Company.fromJson(data['company']) : Company(); - this.createdOnText = - data['created_on_arrow'] != null ? data['created_on_arrow'] : ""; - this.caseAttachment = - data['case_attachment'] != null ? data['case_attachment'] : []; - } - - toJson() { - return { - 'id': id, - 'name': name, - 'status': status, - 'priority': priority, - 'type_of_case': caseType, - 'account': account, - 'contacts': contacts, - 'closed_on': closedOn, - 'description': description, - 'assigned_to': assignedTo, - 'created_by': createdBy, - 'created_on': createdOn, - 'is_active': isActive, - 'teams': teams, - 'company': company, - 'created_on_arrow': createdOnText, - 'case_attachment': caseAttachment - }; - } -} diff --git a/lib/model/company.dart b/lib/model/company.dart deleted file mode 100644 index 9e8e7c8..0000000 --- a/lib/model/company.dart +++ /dev/null @@ -1,36 +0,0 @@ -class Company { - int? id; - String? name; - dynamic address; - String? subDomain; - int? userLimit; - String? country; - - Company( - {this.id, - this.name, - this.address, - this.subDomain, - this.userLimit, - this.country}); - - Company.fromJson(Map company) { - this.id = company['id'] != null ? company['id'] : 0; - this.name = company['name'] != null ? company['name'] : ''; - this.address = company['address'] != null ? company['address'] : ''; - this.subDomain = company['sub_domain'] != null ? company['sub_domain'] : ""; - this.userLimit = company['user_limit'] != null ? company['user_limit'] : 0; - this.country = company['country'] != null ? company['country'] : ''; - } - - toJson() { - return { - 'id': id, - 'name': name, - 'address': address, - 'sub_domain': subDomain, - 'user_limit': userLimit, - 'country': country - }; - } -} diff --git a/lib/model/contact.dart b/lib/model/contact.dart deleted file mode 100644 index ccc6990..0000000 --- a/lib/model/contact.dart +++ /dev/null @@ -1,185 +0,0 @@ -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:intl/intl.dart'; - -class Contact { - int? id; - String? salutation; - String? firstName; - String? lastName; - String? primaryEmail; - String? secondaryEmail; - String? primaryMobile; - String? secondaryMobile; - String? dateOfBirth; - String? linkedInUrl; - String? facebookUrl; - String? twitterUserName; - String? organization; - String? department; - bool? doNotCall; - String? title; - Map? address; - String? description; - List? assignedTo; - Profile? createdBy; - String? createdOn; - bool? isActive; - List? teams; - String? createdOnText; - List? teamAndAssignedUsers; - List? assignedUsersNotInTeams; - - Contact( - {this.id, - this.salutation, - this.firstName, - this.lastName, - this.primaryMobile, - this.secondaryMobile, - this.primaryEmail, - this.secondaryEmail, - this.dateOfBirth, - this.linkedInUrl, - this.facebookUrl, - this.twitterUserName, - this.organization, - this.department, - this.doNotCall, - this.title, - this.address, - this.description, - this.assignedTo, - this.createdBy, - this.createdOn, - this.isActive, - this.teams, - this.createdOnText, - this.teamAndAssignedUsers, - this.assignedUsersNotInTeams}); - - Contact.fromJson(Map contact) { - Map address = { - "address_line": "", - "street": "", - "city": "", - "state": "", - "postcode": "", - "country": "" - }; - - address['address_line'] = - contact['address'] != null && contact['address']['address_line'] != null - ? contact['address']['address_line'] - : ""; - address['street'] = - contact['address'] != null && contact['address']['street'] != null - ? ", " + contact['address']['street'] - : ""; - address['city'] = - contact['address'] != null && contact['address']['city'] != null - ? ", " + contact['address']['city'] - : ""; - address['state'] = - contact['address'] != null && contact['address']['state'] != null - ? ", " + contact['address']['state'] - : ""; - address['postcode'] = - contact['address'] != null && contact['address']['postcode'] != null - ? ", " + contact['address']['postcode'].toString() - : ""; - address['country'] = - contact['address'] != null && contact['address']['country'] != null - ? ", " + contact['address']['country'] - : ""; - - this.id = contact['id'] != null ? contact['id'] : 0; - this.salutation = - contact['salutation'] != null ? contact['salutation'] : ''; - this.firstName = contact['first_name'] != null ? contact['first_name'] : ''; - this.lastName = contact['last_name'] != null ? contact['last_name'] : ''; - this.primaryEmail = - contact['primary_email'] != null ? contact['primary_email'] : ""; - this.secondaryEmail = - contact['secondary_email'] != null ? contact['secondary_email'] : ""; - this.primaryMobile = - contact['mobile_number'] != null ? contact['mobile_number'] : ""; - this.secondaryEmail = - contact['secondary_number'] != null ? contact['secondary_number'] : ""; - this.linkedInUrl = - contact['linked_in_url'] != null ? contact['linked_in_url'] : ""; - this.facebookUrl = - contact['facebook_url'] != null ? contact['facebook_url'] : ""; - this.twitterUserName = - contact['twitter_username'] != null ? contact['twitter_username'] : ""; - this.dateOfBirth = - contact['date_of_birth'] != null ? contact['date_of_birth'] : ""; - this.organization = - contact['organization'] != null ? contact['organization'] : ""; - this.department = - contact['department'] != null ? contact['department'] : ""; - this.doNotCall = - contact['do_not_call'] != null ? contact['do_not_call'] : ""; - this.title = contact['title'] != null ? contact['title'] : ""; - this.address = address; - this.description = - contact['description'] != null ? contact['description'] : ""; - this.assignedTo = contact['assigned_to'] != null - ? List.from( - contact['assigned_to'].map((x) => Profile.fromJson(x))) - : []; - this.createdBy = contact['created_by'] != null - ? Profile.fromJson(contact['created_by']) - : Profile(); - this.createdOn = contact['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(contact['created_on'])) - : ""; - this.createdOnText = - contact['created_on_arrow'] != null ? contact['created_on_arrow'] : ""; - this.isActive = contact['is_active'] != null ? contact['is_active'] : false; - this.teams = contact['teams'] != null - ? List.from(contact['teams'].map((x) => Team.fromJson(x))) - : []; - this.teamAndAssignedUsers = contact['get_team_and_assigned_users'] != null - ? List.from(contact['get_team_and_assigned_users'] - .map((x) => Profile.fromJson(x))) - : []; - this.assignedUsersNotInTeams = - contact['get_assigned_users_not_in_teams'] != null - ? List.from(contact['get_assigned_users_not_in_teams'] - .map((x) => Profile.fromJson(x))) - : []; - } - - toJson() { - return { - 'salutation': salutation, - 'id': id, - 'first_name': firstName, - 'last_name': lastName, - 'primary_email': primaryEmail, - 'secondary_email': secondaryEmail, - 'mobile_number': primaryMobile, - 'secondary_number': secondaryMobile, - 'linked_in_url': linkedInUrl, - 'facebook_url': facebookUrl, - 'twitter_username': twitterUserName, - 'organization': organization, - 'department': department, - 'date_of_birth': dateOfBirth, - 'do_not_call': doNotCall, - 'title ': title, - 'address': address, - 'description': description, - 'assigned_to': assignedTo, - 'created_by': createdBy, - 'created_on': createdOn, - 'created_on_arrow': createdOnText, - 'is_active': isActive, - 'teams': teams, - 'get_team_and_assigned_users': teamAndAssignedUsers, - 'get_assigned_users_not_in_teams': assignedUsersNotInTeams - }; - } -} diff --git a/lib/model/document.dart b/lib/model/document.dart deleted file mode 100644 index fa32330..0000000 --- a/lib/model/document.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:bottle_crm/model/company.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/team.dart'; - -class Document { - int? id; - String? title; - String? documentFile; - String? status; - List? sharedTo; - List? teams; - String? createdOn; - Profile? createdBy; - Company? company; - - Document( - {this.id, - this.title, - this.documentFile, - this.status, - this.sharedTo, - this.teams, - this.createdOn, - this.createdBy, - this.company}); - - Document.fromJson(Map document) { - this.id = document['id'] != null ? document['id'] : 0; - this.title = document['title'] != null ? document['title'] : ""; - this.documentFile = - document['document_file'] != null ? document['document_file'] : ""; - this.status = document['status'] != null ? document['status'] : ""; - this.sharedTo = document['shared_to'] != null - ? List.from( - document['shared_to'].map((contact) => Profile.fromJson(contact))) - : []; - this.teams = document["teams"] != null - ? List.from(document["teams"].map((team) => Team.fromJson(team))) - : []; - this.createdOn = document['created_on'] != null - ? document['created_on'] - // ? DateFormat("dd MMM, yyyy 'at' HH:mm") - // .format(DateFormat("yyyy-MM-dd").parse(document['created_on'])) - : ""; - this.createdBy = document['created_by'] != null - ? Profile.fromJson(document['created_by']) - : Profile(); - this.company = document['company'] != null - ? Company.fromJson(document['company']) - : Company(); - } - - toJson() { - return { - "id": id, - "title": title, - "document_file": documentFile, - "status": status, - "shared_to": sharedTo, - "teams": teams, - "created_on": createdOn, - "created_by": createdBy, - "company": company - }; - } -} diff --git a/lib/model/domain.dart b/lib/model/domain.dart deleted file mode 100644 index 44727bb..0000000 --- a/lib/model/domain.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:bottle_crm/model/profile.dart'; -import 'package:intl/intl.dart'; -import 'company.dart'; - -class Domain { - int? id; - String? domain; - String? createdOn; - Profile? createdBy; - Company? company; - - Domain({this.id, this.domain, this.createdOn, this.createdBy, this.company}); - - Domain.fromJson(Map domain) { - this.id = domain['id'] != null ? domain['id'] : 0; - this.domain = domain['domain'] != null ? domain['domain'] : ""; - this.createdOn = domain['created_on'] != null - ? DateFormat("dd MMM, yyyy") - .format(DateFormat("yyyy-MM-dd").parse(domain['created_on'])) - : ""; - this.createdBy = domain['created_by'] != null - ? Profile.fromJson(domain['created_by']) - : Profile(); - this.company = domain['company'] != null - ? Company.fromJson(domain['company']) - : Company(); - } - - toJson() { - return { - "id": id, - "domain": domain, - "created_on": createdOn, - "created_by": createdBy, - "company": company - }; - } -} diff --git a/lib/model/email.dart b/lib/model/email.dart deleted file mode 100644 index ac1533f..0000000 --- a/lib/model/email.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:bottle_crm/model/profile.dart'; -import 'package:intl/intl.dart'; -import 'company.dart'; - -class Email { - int? id; - String? email; - String? createdOn; - Profile? createdBy; - Company? company; - - Email({this.id, this.email, this.createdOn, this.createdBy, this.company}); - - Email.fromJson(Map email) { - this.id = email['id'] != null ? email['id'] : 0; - this.email = email['email'] != null ? email['email'] : ""; - this.createdOn = email['created_on'] != null - ? DateFormat("dd MMM, yyyy") - .format(DateFormat("yyyy-MM-dd").parse(email['created_on'])) - : ""; - this.createdBy = email['created_by'] != null - ? Profile.fromJson(email['created_by']) - : Profile(); - this.company = email['company'] != null - ? Company.fromJson(email['company']) - : Company(); - } - - toJson() { - return { - "id": id, - "email": email, - "created_on": createdOn, - "created_by": createdBy, - "company": company - }; - } -} diff --git a/lib/model/events.dart b/lib/model/events.dart deleted file mode 100644 index e98f9ea..0000000 --- a/lib/model/events.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:intl/intl.dart'; - -class Event { - int? id; - String? name; - String? eventType; - String? startDate; - String? startTime; - String? endDate; - String? endTime; - String? description; - String? dateOfMeeting; - String? createdOn; - Profile? createdBy; - List? contacts; - List? assignedTo; - List? teams; - bool? isActive; - String? status; - - Event( - {this.id, - this.name, - this.eventType, - this.startDate, - this.startTime, - this.endDate, - this.endTime, - this.description, - this.dateOfMeeting, - this.contacts, - this.assignedTo, - this.teams, - this.createdOn, - this.isActive, - this.status, - this.createdBy}); - - Event.fromJson(Map event) { - this.id = event['id'] != null ? event['id'] : 0; - this.name = event['name'] != null ? event['name'] : ""; - this.description = event['description'] != null ? event['description'] : ""; - this.createdBy = event['created_by'] != null - ? Profile.fromJson(event['created_by']) - : Profile(); - this.assignedTo = event['assigned_to'] != null - ? List.from( - event['assigned_to'].map((x) => Profile.fromJson(x))) - : []; - this.createdOn = event['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(event['created_on'])) - : ""; - this.contacts = event["contacts"] != null - ? List.from(event["contacts"].map((x) => Contact.fromJson(x))) - : []; - this.teams = event["teams"] != null - ? List.from(event["teams"].map((x) => Team.fromJson(x))) - : []; - this.isActive = event['is_active'] != null ? event['is_active'] : false; - this.status = event['status'] != null ? event['status'] : ""; - this.eventType = event['event_type'] != null ? event['event_type'] : ""; - this.startDate = event['start_date'] != null ? event['start_date'] : ""; - this.startTime = event['start_time'] != null ? event['start_time'] : ""; - this.endDate = event['end_date'] != null ? event['end_date'] : ""; - this.endTime = event['end_time'] != null ? event['end_time'] : ""; - this.dateOfMeeting= - event['date_of_meeting'] != null ? event['date_of_meeting'] : ""; - } - - toJson() { - return { - 'id': id, - 'name': name, - 'event_type': eventType, - 'status': status, - 'is_active': isActive, - 'start_date': startDate, - 'start_time': startTime, - 'end_date': endDate, - 'end_time': endTime, - 'description': description, - 'date_of_meeting': dateOfMeeting, - 'created_by': createdBy, - 'created_on': createdOn, - 'contacts': contacts, - 'teams': teams, - 'assigned_to': assignedTo, - }; - } -} diff --git a/lib/model/lead.dart b/lib/model/lead.dart deleted file mode 100644 index 694b933..0000000 --- a/lib/model/lead.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:intl/intl.dart'; - -import 'contact.dart'; - -class Lead { - int? id; - String? title; - String? firstName; - String? lastName; - String? email; - String? phone; - String? status; - String? source; - String? addressLine; - String? street; - String? city; - String? state; - String? postcode; - String? country; - String? website; - String? skypeID; - String? description; - List? assignedTo; - String? accountName; - String? opportunityAmount; - Profile? createdBy; - String? createdOn; - bool? isActive; - dynamic enqueryType; - List? tags; - List? contacts; - bool? createdFromSite; - List? teams; - // Company company; - - Lead({ - this.id, - this.title, - this.firstName, - this.lastName, - this.email, - this.phone, - this.status, - this.source, - this.addressLine, - this.street, - this.city, - this.state, - this.postcode, - this.country, - this.website, - this.skypeID, - this.description, - this.assignedTo, - this.accountName, - this.opportunityAmount, - this.createdBy, - this.createdOn, - this.isActive, - this.enqueryType, - this.tags, - this.contacts, - this.createdFromSite, - this.teams, - // this.company, - }); - - factory Lead.fromJson(Map lead) => Lead( - id: lead["id"] != null ? lead["id"] : 0, - title: lead["title"] != null ? lead["title"] : "", - firstName: lead["first_name"] != null ? lead["first_name"] : "", - lastName: lead["last_name"] != null ? lead["last_name"] : "", - email: lead["email"] != null ? lead["email"] : "", - phone: lead["phone"] != null ? lead['phone'] : "", - status: lead["status"] != null ? lead["status"] : "", - source: lead["source"] != null ? lead["source"] : "", - addressLine: lead["address_line"] != null ? lead["address_line"] : "", - street: lead["street"] != null ? lead["street"] : "", - city: lead["city"] != null ? lead["city"] : "", - state: lead["state"] != null ? lead["state"] : "", - postcode: lead["postcode"] != null ? lead["postcode"] : "", - country: lead["country"] != null ? lead["country"] : "", - website: lead["website"] != null ? lead["website"] : "", - skypeID: lead["skype_ID"] != null ? lead["skype_ID"] : "", - description: lead["description"] != null ? lead["description"] : "", - assignedTo: lead["assigned_to"] != null - ? List.from(lead["assigned_to"] - .map((_profile) => Profile.fromJson(_profile))) - : [], - accountName: lead["account_name"] != null ? lead["account_name"] : "", - opportunityAmount: lead["opportunity_amount"] != null - ? lead["opportunity_amount"] - : "", - createdBy: lead["created_by"] != null - ? Profile.fromJson(lead["created_by"]) - : Profile(), - createdOn: lead["created_on"] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(lead['created_on'])) - : "", - isActive: lead["is_active"] != null ? lead["is_active"] : false, - enqueryType: lead["enquery_type"] != null ? lead["enquery_type"] : "", - tags: lead["tags"] != null ? lead["tags"] : [], - contacts: lead["contacts"] != null - ? List.from( - lead["contacts"].map((x) => Contact.fromJson(x))) - : [], - createdFromSite: lead["created_from_site"] != null - ? lead["created_from_site"] - : false, - teams: lead["teams"] != null - ? List.from(lead["teams"].map((x) => Team.fromJson(x))) - : [], - // teams: lead - // company: lead["company"] != null ? lead["company"] : Company(), - ); - - Map toJson() => { - "id": id, - "title": title, - "first_name": firstName, - "last_name": lastName, - "email": email, - "phone": phone, - "status": status, - "source": source, - "address_line": addressLine, - "street": street, - "city": city, - "state": state, - "postcode": postcode, - "country": country, - "website": website, - "skype_ID":skypeID, - "description": description, - "assigned_to": List.from(assignedTo!.map((x) => x.toJson())), - "account_name": accountName, - "opportunity_amount": opportunityAmount, - "created_by": createdBy!.toJson(), - "created_on": createdOn, - "is_active": isActive, - "enquery_type": enqueryType, - "tags": tags, - "contacts": List.from(contacts!.map((x) => x)), - "created_from_site": createdFromSite, - // "teams": List.from(teams.map((x) => x)), - // "company": company, - }; -} diff --git a/lib/model/opportunities.dart b/lib/model/opportunities.dart deleted file mode 100644 index 7d6d8a6..0000000 --- a/lib/model/opportunities.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:intl/intl.dart'; - -import 'account.dart'; -import 'company.dart'; - -class Opportunity { - int? id; - String? name; - Account? account; - String? stage; - String? currency; - String? amount; - String? leadSource; - int? probability; - List? contacts; - Profile? closedBy; - String? closedOn; - String? dueDate; - String? description; - List? assignedTo; - Profile? createdBy; - String? createdOn; - bool? isActive; - List? tags; - List? teams; - Company? company; - String? createdOnText; - List? opportunityAttachment; - - Opportunity( - {this.id, - this.name, - this.account, - this.amount, - this.closedBy, - this.closedOn, - this.dueDate, - this.company, - this.createdBy, - this.createdOn, - this.createdOnText, - this.currency, - this.description, - this.isActive, - this.leadSource, - this.probability, - this.stage, - this.tags, - this.contacts, - this.assignedTo, - this.teams, - this.opportunityAttachment}); - - Opportunity.fromJson(Map opportunity) { - this.id = opportunity['id'] != null ? opportunity['id'] : 0; - this.name = opportunity['name'] != null ? opportunity['name'] : ""; - this.account = opportunity['account'] != null - ? Account.fromJson(opportunity['account']) - : Account(); - this.stage = opportunity['stage'] != null ? opportunity['stage'] : ""; - this.currency = - opportunity['currency'] != null ? opportunity['currency'] : ""; - this.amount = opportunity['amount'] != null ? opportunity['amount'] : ""; - this.leadSource = - opportunity['lead_source'] != null ? opportunity['lead_source'] : ""; - this.probability = - opportunity['probability'] != null ? opportunity['probability'] : 0; - this.contacts = opportunity['contacts'] != null - ? List.from( - opportunity["contacts"].map((x) => Contact.fromJson(x))) - : []; - this.closedBy = opportunity['closed_by'] != null - ? Profile.fromJson(opportunity['closed_by']) - : Profile(); - this.closedOn = - opportunity['closed_on'] != null ? opportunity['closed_on'] : ""; - this.dueDate = - opportunity['due_date'] != null ? opportunity['due_date'] : ""; - this.description = - opportunity['description'] != null ? opportunity['description'] : ""; - this.description = - opportunity['description'] != null ? opportunity['description'] : ""; - this.assignedTo = opportunity['assigned_to'] != null - ? List.from( - opportunity["assigned_to"].map((x) => Profile.fromJson(x))) - : []; - this.createdBy = opportunity['created_by'] != null - ? Profile.fromJson(opportunity['created_by']) - : Profile(); - this.createdOn = opportunity['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(opportunity['created_on'])) - : ""; - this.isActive = - opportunity['is_active'] != null ? opportunity['is_active'] : false; - this.tags = opportunity['tags'] != null ? opportunity['tags'] : []; - this.teams = opportunity['teams'] != null - ? List.from(opportunity["teams"].map((x) => Team.fromJson(x))) - : []; - this.company = opportunity['company'] != null - ? Company.fromJson(opportunity['company']) - : Company(); - this.createdOnText = opportunity['created_on_arrow'] != null - ? opportunity['created_on_arrow'] - : ""; - this.opportunityAttachment = - opportunity['opportunity_attachment'].length != 0 - ? opportunity['opportunity_attachment'] - : []; - } - - toJson() { - return { - 'id': id, - 'name': name, - 'account': account, - 'stage': stage, - 'currency': currency, - 'amount': amount, - 'lead_source': leadSource, - 'probability': probability, - 'contacts': contacts, - 'closed_by': closedBy, - 'closed_on': closedOn, - 'due_date': dueDate, - 'description': description, - 'assigned_to': assignedTo, - 'created_by': createdBy, - 'created_on': createdOn, - 'is_active': isActive, - 'tags': tags, - 'teams': teams, - 'company': company, - 'created_on_arrow': createdOnText, - 'opportunity_attachment': opportunityAttachment - }; - } -} diff --git a/lib/model/organization.dart b/lib/model/organization.dart deleted file mode 100644 index 64acd5d..0000000 --- a/lib/model/organization.dart +++ /dev/null @@ -1,15 +0,0 @@ -class Organization { - int? id; - String? name; - - Organization({this.id, this.name}); - - Organization.fromJson(Map data) { - this.id = data['id'] != null ? data['id'] : 0; - this.name = data['name'] != null ? data['name'] : ""; - } - - toJson() { - return {'id': id, 'name': name}; - } -} diff --git a/lib/model/profile.dart b/lib/model/profile.dart deleted file mode 100644 index 3a484f9..0000000 --- a/lib/model/profile.dart +++ /dev/null @@ -1,75 +0,0 @@ -class Profile { - int? id; - String? email; - String? firstName; - String? lastName; - String? profileUrl; - String? role; - bool? isActive; - bool? isAdmin; - bool? isStaff; - bool? hasSalesAccess; - bool? hasMarketingAccess; - String? dateOfJoin; - - Profile( - {this.id, - this.email, - this.firstName, - this.lastName, - this.profileUrl, - this.role, - this.dateOfJoin, - this.hasMarketingAccess, - this.hasSalesAccess, - this.isActive, - this.isAdmin, - this.isStaff}); - - Profile.fromJson(Map profile) { - this.id = profile['user_details']['id'] != null - ? profile['user_details']['id'] - : 0; - this.role = profile['role'] != null ? profile['role'] : ""; - this.profileUrl = profile['user_details']['profile_pic'] != null - ? profile['user_details']['profile_pic'] - : ""; - this.dateOfJoin = - profile['date_of_joining'] != null ? profile['date_of_joining'] : ""; - this.email = profile['user_details']['email'] != null - ? profile['user_details']['email'] - : ""; - this.firstName = profile['user_details']['first_name'] != null - ? profile['user_details']['first_name'] - : ""; - this.lastName = profile['user_details']['last_name'] != null - ? profile['user_details']['last_name'] - : ""; - this.hasMarketingAccess = profile['has_marketing_access'] != null - ? profile['has_marketing_access'] - : false; - this.hasSalesAccess = profile['has_sales_access'] != null - ? profile['has_sales_access'] - : false; - this.isActive = profile['is_active'] != null ? profile['is_active'] : false; - this.isAdmin = profile['is_admin'] != null ? profile['is_admin'] : false; - this.isStaff = profile['is_staff'] != null ? profile['is_staff'] : false; - } - - toJson() { - return { - 'id': id, - 'role': role, - 'profile_pic': profileUrl, - 'date_joined': dateOfJoin, - 'email': email, - 'first_name': firstName, - 'last_name': lastName, - 'has_marketing_access': hasMarketingAccess, - 'has_sales_access': hasSalesAccess, - 'is_active': isActive, - 'is_admin': isAdmin, - 'is_staff': isStaff - }; - } -} diff --git a/lib/model/settings.dart b/lib/model/settings.dart deleted file mode 100644 index d8a995a..0000000 --- a/lib/model/settings.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:bottle_crm/model/lead.dart'; -import 'package:bottle_crm/model/organization.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:intl/intl.dart'; - -class Settings { - int? id; - String? title; - String? website; - Profile? createdBy; - String? createdOn; - List? leadAssignedTo; - List? tags; - Organization? org; - - Settings( - {this.id, - this.title, - this.website, - this.createdBy, - this.createdOn, - this.leadAssignedTo, - this.tags, - this.org}); - - Settings.fromJson(Map settings) { - this.id = settings['id'] != null ? settings['id'] : 0; - this.title = settings['title'] != null ? settings['title'] : ""; - this.website = settings['website'] != null ? settings['website'] : ""; - this.createdBy = settings['created_by'] != null - ? Profile.fromJson(settings['created_by']) - : Profile(); - this.createdOn = settings['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(settings['created_on'])) - : ""; - - this.leadAssignedTo = settings['lead_assigned_to'] != null - ? List.from( - settings['lead_assigned_to'].map((x) => Lead.fromJson(x))) - : []; - this.tags = settings['tags'] != null ? settings['tags'] : []; - this.org = settings['org'] != null - ? Organization.fromJson(settings['org']) - : Organization(); - } - - toJson() { - return { - 'id': id, - 'title': title, - 'website': website, - 'created_by': createdBy, - 'created_on': createdOn, - 'lead_assigned_to': leadAssignedTo, - 'tags': tags, - 'org': org - }; - } -} diff --git a/lib/model/task.dart b/lib/model/task.dart deleted file mode 100644 index db7e0a0..0000000 --- a/lib/model/task.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:bottle_crm/model/company.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:intl/intl.dart'; - -import 'account.dart'; -import 'contact.dart'; -import 'team.dart'; - -class Task { - int? id; - String? title; - String? status; - String? priority; - String? dueDate; - Account? account; - Profile? createdBy; - String? createdOn; - List? contacts; - List? teams; - List? assignedTo; - Company? company; - - Task( - {this.id, - this.title, - this.status, - this.priority, - this.dueDate, - this.account, - this.createdBy, - this.createdOn, - this.contacts, - this.teams, - this.assignedTo, - this.company}); - - Task.fromJson(Map task) { - this.id = task['id'] != null ? task['id'] : 0; - this.title = task['title'] != null ? task['title'] : ""; - this.status = task['status'] != null ? task['status'] : ""; - this.priority = task['priority'] != null ? task['priority'] : ""; - this.dueDate = task['due_date'] != null ? task['due_date'] : ""; - this.account = task['account'] != null ? task['account'] : Account(); - this.createdBy = task['created_by'] != null - ? Profile.fromJson(task['created_by']) - : Profile(); - this.createdOn = task['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(task['created_on'])) - : ""; - this.contacts = task['contacts'] != null - ? List.from(task['contacts'].map((x) => Contact.fromJson(x))) - : []; - this.teams = task['teams'] != null - ? List.from(task['teams'].map((x) => Team.fromJson(x))) - : []; - this.assignedTo = task['assigned_to'] != null - ? List.from( - task['assigned_to'].map((x) => Profile.fromJson(x))) - : []; - this.company = - task['company'] != null ? Company.fromJson(task['company']) : Company(); - } - - toJson() { - return { - 'id': id, - 'title': title, - 'status': status, - 'priority': priority, - 'due_date': dueDate, - 'account': account, - 'created_by': createdBy, - 'created_on': createdOn, - 'contacts': contacts, - 'teams': teams, - 'assigned_to': assignedTo, - 'company': company - }; - } -} diff --git a/lib/model/team.dart b/lib/model/team.dart deleted file mode 100644 index dd1bc6d..0000000 --- a/lib/model/team.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:bottle_crm/model/company.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:intl/intl.dart'; - -class Team { - int? id; - String? name; - String? description; - List? users; - String? createdOn; - Profile? createdBy; - int? createdById; - Company? company; - int? companyId; - String? createdOnText; - - Team( - {this.id, - this.name, - this.description, - this.users, - this.createdOn, - this.createdBy, - this.createdById, - this.company, - this.companyId, - this.createdOnText}); - - Team.fromJson(Map team) { - this.id = team['id'] != null ? team['id'] : 0; - this.name = team['name'] != null ? team['name'] : ""; - this.description = team['description'] != null ? team['description'] : ""; - this.users = team['users'] != null - ? List.from(team['users'].map((x) => Profile.fromJson(x))) - : []; - this.createdOn = team['created_on'] != null - ? DateFormat("dd-MM-yyyy") - .format(DateFormat("yyyy-MM-dd").parse(team['created_on'])) - : ""; - this.createdBy = team['created_by'] != null - ? Profile.fromJson(team['created_by']) - : Profile(); - this.createdById = - team['created_by'] != null ? team['created_by']['id'] : 0; - this.company = - team['company'] != null ? Company.fromJson(team['company']) : Company(); - this.companyId = team['company'] != null ? team['company']['id'] : 0; - this.createdOnText = - team['created_on_arrow'] != null ? team['created_on_arrow'] : ""; - } - - toJson() { - return { - 'id': id, - 'name': name, - 'description': description, - 'users': users, - 'created_on': createdOn, - 'created_by': createdBy, - 'created_by_id': createdById, - 'company': company, - 'company_id': companyId, - 'created_on_arrow': createdOnText, - }; - } - - void forEach(Null Function(dynamic _user) param0) {} -} diff --git a/lib/model/user.dart b/lib/model/user.dart deleted file mode 100644 index 8333d85..0000000 --- a/lib/model/user.dart +++ /dev/null @@ -1,121 +0,0 @@ -class User { - int? id; - String? firstName; - String? lastName; - String? role; - String? email; - String? alternateEmail; - String? phone; - String? alternatePhone; - String? skypeID; - String? profilePic; - bool? hasSalesAccess; - bool? hasMarktingAccess; - String? isMarketingAdmin; - bool? isActive; - String? status; - String? adressLine; - String? street; - String? city; - String? state; - String? pincode; - String? country; - String? description; - - User({ - this.id, - this.firstName, - this.lastName, - this.role, - this.phone, - this.alternatePhone, - this.email, - this.alternateEmail, - this.skypeID, - this.profilePic, - this.hasSalesAccess, - this.hasMarktingAccess, - this.isMarketingAdmin, - this.isActive, - this.status, - this.adressLine, - this.street, - this.city, - this.state, - this.pincode, - this.country, - this.description, - }); - - User.fromJson(Map user) { - this.id = user['id'] != null ? user['id'] : 0; - this.firstName = user['user_details']['first_name'] != null - ? user['user_details']['first_name'] - : ""; - this.lastName = user['user_details']['last_name'] != null - ? user['user_details']['last_name'] - : ""; - this.role = user['role'] != null ? user['role'] : ""; - this.email = user['user_details']['email'] != null - ? user['user_details']['email'] - : ""; - this.alternateEmail = user['user_details']['alternate_email'] != null - ? user['user_details']['alternate_email'] - : ""; - this.phone = user['phone'] != null ? user['phone'] : ""; - this.alternatePhone = - user['alternate_phone'] != null ? user['alternate_phone'] : ""; - this.skypeID = user['user_details']['skype_ID'] != null - ? user['user_details']['skype_ID'] - : ""; - this.profilePic = user['user_details']['profile_pic '] != null - ? user['user_details']['profile_pic '] - : ""; - this.hasSalesAccess = - user['has_sales_access'] != null ? user['has_sales_access'] : ""; - this.hasMarktingAccess = user['has_marketing_access'] != null - ? user['has_marketing_access'] - : ""; - this.isMarketingAdmin = user['is_organization_admin'] != null - ? user['is_organization_admin'] - : ""; - this.description = user['user_details']['description'] != null - ? user['user_details']['description'] - : ""; - this.isActive = user['is_active'] != null ? user['is_active'] : false; - this.status = user['status'] != null ? user['status'] : ""; - this.adressLine = user['address_line'] != null ? user['address_line'] : ""; - this.street = user['street'] != null ? user['street'] : ""; - this.city = user['city'] != null ? user['city'] : ""; - this.state = user['state'] != null ? user['state'] : ""; - this.pincode = user['pincode'] != null ? user['pincode'] : ""; - this.country = user['country'] != null ? user['country'] : ""; - } - - toJson() { - return { - 'id': id, - 'first_name': firstName, - 'last_name': lastName, - 'role': role, - 'email': email, - 'alternate_email': alternateEmail, - 'phone': phone, - 'alternate_phone': alternatePhone, - 'profile_pic': profilePic, - 'skype_ID': skypeID, - 'has_sales_access ': hasSalesAccess, - 'has_marketing_access': hasMarktingAccess, - 'is_organization_admin': isMarketingAdmin, - 'is_active':isActive, - 'status':status, - 'description': description, - 'address_line': adressLine, - 'street': street, - 'state': state, - 'city': city, - 'pincode': pincode, - 'country': country, - }; - } -} diff --git a/lib/models/api_models.dart b/lib/models/api_models.dart new file mode 100644 index 0000000..ec73f24 --- /dev/null +++ b/lib/models/api_models.dart @@ -0,0 +1,1541 @@ +// Base model classes for API responses + +class ApiResponse { + final bool success; + final T? data; + final String? message; + final int statusCode; + final Map? errors; + final Pagination? pagination; + + ApiResponse({ + required this.success, + this.data, + this.message, + required this.statusCode, + this.errors, + this.pagination, + }); + + factory ApiResponse.fromJson( + Map json, + T Function(dynamic)? fromJsonT, + ) { + return ApiResponse( + success: json['success'] ?? false, + data: json['data'] != null && fromJsonT != null + ? fromJsonT(json['data']) + : json['data'], + message: json['message'], + statusCode: json['status_code'] ?? 200, + errors: json['errors'], + pagination: json['pagination'] != null + ? Pagination.fromJson(json['pagination']) + : null, + ); + } +} + +class Pagination { + final int page; + final int limit; + final int total; + final int? totalPages; + final bool hasNext; + final bool hasPrev; + + Pagination({ + required this.page, + required this.limit, + required this.total, + this.totalPages, + required this.hasNext, + required this.hasPrev, + }); + + bool get hasPrevious => hasPrev; + int get pages => totalPages ?? 1; + + factory Pagination.fromJson(Map json) { + final totalPagesValue = json['totalPages'] ?? json['pages']; + int? totalPagesInt; + + if (totalPagesValue is int) { + totalPagesInt = totalPagesValue; + } else if (totalPagesValue is String && int.tryParse(totalPagesValue) != null) { + totalPagesInt = int.parse(totalPagesValue); + } + + return Pagination( + page: (json['page'] is int) ? json['page'] : int.tryParse(json['page']?.toString() ?? '1') ?? 1, + limit: (json['limit'] is int) ? json['limit'] : int.tryParse(json['limit']?.toString() ?? '10') ?? 10, + total: (json['total'] is int) ? json['total'] : int.tryParse(json['total']?.toString() ?? '0') ?? 0, + totalPages: totalPagesInt, + hasNext: json['hasNext'] is bool ? json['hasNext'] : (json['hasNext']?.toString().toLowerCase() == 'true'), + hasPrev: json['hasPrev'] is bool ? json['hasPrev'] : (json['hasPrev']?.toString().toLowerCase() == 'true'), + ); + } + + Map toJson() { + return { + 'page': page, + 'limit': limit, + 'total': total, + 'totalPages': totalPages, + 'hasNext': hasNext, + 'hasPrev': hasPrev, + }; + } +} + +// Contact model +class Contact { + final String id; + final String firstName; + final String lastName; + final String? email; + final String? phone; + final String? title; + final String? department; + final String? street; + final String? city; + final String? state; + final String? postalCode; + final String? country; + final double? latitude; + final double? longitude; + final String? description; + final DateTime createdAt; + final DateTime updatedAt; + final String? ownerId; + final String organizationId; + final ContactOwner? owner; + final ContactOrganization? organization; + final List? relatedAccounts; + + Contact({ + required this.id, + required this.firstName, + required this.lastName, + this.email, + this.phone, + this.title, + this.department, + this.street, + this.city, + this.state, + this.postalCode, + this.country, + this.latitude, + this.longitude, + this.description, + required this.createdAt, + required this.updatedAt, + this.ownerId, + required this.organizationId, + this.owner, + this.organization, + this.relatedAccounts, + }); + + String get fullName => '$firstName $lastName'; + + String get fullAddress { + final parts = [ + street, + city, + state, + postalCode, + country, + ].where((part) => part != null && part.isNotEmpty).toList(); + return parts.join(', '); + } + + factory Contact.fromJson(Map json) { + return Contact( + id: json['id'] ?? '', + firstName: json['firstName'] ?? '', + lastName: json['lastName'] ?? '', + email: json['email'], + phone: json['phone'], + title: json['title'], + department: json['department'], + street: json['street'], + city: json['city'], + state: json['state'], + postalCode: json['postalCode'], + country: json['country'], + latitude: json['latitude']?.toDouble(), + longitude: json['longitude']?.toDouble(), + description: json['description'], + createdAt: DateTime.parse( + json['createdAt'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updatedAt'] ?? DateTime.now().toIso8601String(), + ), + ownerId: json['ownerId'], + organizationId: json['organizationId'] ?? '', + owner: json['owner'] != null + ? ContactOwner.fromJson(json['owner']) + : null, + organization: json['organization'] != null + ? ContactOrganization.fromJson(json['organization']) + : null, + relatedAccounts: json['relatedAccounts'] != null + ? (json['relatedAccounts'] as List) + .map( + (account) => + RelatedAccount.fromJson(account as Map), + ) + .toList() + : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'phone': phone, + 'title': title, + 'department': department, + 'street': street, + 'city': city, + 'state': state, + 'postalCode': postalCode, + 'country': country, + 'latitude': latitude, + 'longitude': longitude, + 'description': description, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'ownerId': ownerId, + 'organizationId': organizationId, + 'owner': owner?.toJson(), + 'organization': organization?.toJson(), + 'relatedAccounts': relatedAccounts + ?.map((account) => account.toJson()) + .toList(), + }; + } +} + +class ContactOwner { + final String id; + final String name; + final String email; + + ContactOwner({required this.id, required this.name, required this.email}); + + String get fullName => name; + + factory ContactOwner.fromJson(Map json) { + return ContactOwner( + id: json['id'] ?? '', + name: json['name'] ?? '', + email: json['email'] ?? '', + ); + } + + Map toJson() { + return {'id': id, 'name': name, 'email': email}; + } +} + +class ContactOrganization { + final String id; + final String name; + + ContactOrganization({required this.id, required this.name}); + + factory ContactOrganization.fromJson(Map json) { + return ContactOrganization(id: json['id'] ?? '', name: json['name'] ?? ''); + } + + Map toJson() { + return {'id': id, 'name': name}; + } +} + +class RelatedAccount { + final String id; + final String role; + final bool isPrimary; + final DateTime startDate; + final DateTime? endDate; + final String? description; + final DateTime createdAt; + final DateTime updatedAt; + final Account account; + + RelatedAccount({ + required this.id, + required this.role, + required this.isPrimary, + required this.startDate, + this.endDate, + this.description, + required this.createdAt, + required this.updatedAt, + required this.account, + }); + + factory RelatedAccount.fromJson(Map json) { + return RelatedAccount( + id: json['id'] ?? '', + role: json['role'] ?? '', + isPrimary: json['isPrimary'] ?? false, + startDate: DateTime.parse( + json['startDate'] ?? DateTime.now().toIso8601String(), + ), + endDate: json['endDate'] != null ? DateTime.parse(json['endDate']) : null, + description: json['description'], + createdAt: DateTime.parse( + json['createdAt'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updatedAt'] ?? DateTime.now().toIso8601String(), + ), + account: Account.fromJson(json['account'] ?? {}), + ); + } + + Map toJson() { + return { + 'id': id, + 'role': role, + 'isPrimary': isPrimary, + 'startDate': startDate.toIso8601String(), + 'endDate': endDate?.toIso8601String(), + 'description': description, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'account': account.toJson(), + }; + } +} + +class Account { + final String id; + final String name; + final String type; + final String? website; + final String? phone; + + Account({ + required this.id, + required this.name, + required this.type, + this.website, + this.phone, + }); + + factory Account.fromJson(Map json) { + return Account( + id: json['id'] ?? '', + name: json['name'] ?? '', + type: json['type'] ?? '', + website: json['website'], + phone: json['phone'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'type': type, + 'website': website, + 'phone': phone, + }; + } +} + +// Contacts response wrapper for API +class ContactsResponse { + final List contacts; + final Pagination? pagination; + + ContactsResponse({required this.contacts, this.pagination}); + + factory ContactsResponse.fromJson(Map json) { + return ContactsResponse( + contacts: + (json['contacts'] as List?) + ?.map( + (contactJson) => + Contact.fromJson(contactJson as Map), + ) + .toList() ?? + [], + pagination: json['pagination'] != null + ? Pagination.fromJson(json['pagination']) + : null, + ); + } +} + +// Lead model +class Lead { + final String id; + final String firstName; + final String lastName; + final String? email; + final String? phone; + final String? company; + final String? title; + final String status; + final String leadSource; + final String? industry; + final String? rating; + final String? description; + final DateTime createdAt; + final DateTime updatedAt; + final String? ownerId; + final String organizationId; + final bool isConverted; + final DateTime? convertedAt; + final String? convertedAccountId; + final String? convertedContactId; + final String? convertedOpportunityId; + final String? contactId; + final LeadOwner? owner; + + Lead({ + required this.id, + required this.firstName, + required this.lastName, + this.email, + this.phone, + this.company, + this.title, + required this.status, + required this.leadSource, + this.industry, + this.rating, + this.description, + required this.createdAt, + required this.updatedAt, + this.ownerId, + required this.organizationId, + required this.isConverted, + this.convertedAt, + this.convertedAccountId, + this.convertedContactId, + this.convertedOpportunityId, + this.contactId, + this.owner, + }); + + String get fullName => '$firstName $lastName'; + + factory Lead.fromJson(Map json) { + return Lead( + id: json['id'] ?? '', + firstName: json['firstName'] ?? '', + lastName: json['lastName'] ?? '', + email: json['email'], + phone: json['phone'], + company: json['company'], + title: json['title'], + status: json['status'] ?? 'NEW', + leadSource: json['leadSource'] ?? '', + industry: json['industry'], + rating: json['rating'], + description: json['description'], + createdAt: DateTime.parse( + json['createdAt'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updatedAt'] ?? DateTime.now().toIso8601String(), + ), + ownerId: json['ownerId'], + organizationId: json['organizationId'] ?? '', + isConverted: json['isConverted'] ?? false, + convertedAt: json['convertedAt'] != null + ? DateTime.parse(json['convertedAt']) + : null, + convertedAccountId: json['convertedAccountId'], + convertedContactId: json['convertedContactId'], + convertedOpportunityId: json['convertedOpportunityId'], + contactId: json['contactId'], + owner: json['owner'] != null ? LeadOwner.fromJson(json['owner']) : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'phone': phone, + 'company': company, + 'title': title, + 'status': status, + 'leadSource': leadSource, + 'industry': industry, + 'rating': rating, + 'description': description, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'ownerId': ownerId, + 'organizationId': organizationId, + 'isConverted': isConverted, + 'convertedAt': convertedAt?.toIso8601String(), + 'convertedAccountId': convertedAccountId, + 'convertedContactId': convertedContactId, + 'convertedOpportunityId': convertedOpportunityId, + 'contactId': contactId, + 'owner': owner?.toJson(), + }; + } +} + +class LeadOwner { + final String id; + final String name; + final String email; + + LeadOwner({required this.id, required this.name, required this.email}); + + String get fullName => name; + + factory LeadOwner.fromJson(Map json) { + return LeadOwner( + id: json['id'] ?? '', + name: json['name'] ?? '', + email: json['email'] ?? '', + ); + } + + Map toJson() { + return {'id': id, 'name': name, 'email': email}; + } +} + +// Lead response wrapper for API +class LeadsResponse { + final List leads; + final Pagination pagination; + + LeadsResponse({required this.leads, required this.pagination}); + + factory LeadsResponse.fromJson(Map json) { + return LeadsResponse( + leads: + (json['leads'] as List?) + ?.map( + (leadJson) => Lead.fromJson(leadJson as Map), + ) + .toList() ?? + [], + pagination: Pagination.fromJson(json['pagination'] ?? {}), + ); + } +} + +// Deal model +class Deal { + final String id; + final String title; + final String? description; + final double value; + final String currency; + final DealStage stage; + final String? contactId; + final String? companyId; + final DateTime? expectedCloseDate; + final double probability; + final DateTime createdAt; + final DateTime updatedAt; + final Map? customFields; + + Deal({ + required this.id, + required this.title, + this.description, + required this.value, + required this.currency, + required this.stage, + this.contactId, + this.companyId, + this.expectedCloseDate, + required this.probability, + required this.createdAt, + required this.updatedAt, + this.customFields, + }); + + factory Deal.fromJson(Map json) { + return Deal( + id: json['id'] ?? '', + title: json['title'] ?? '', + description: json['description'], + value: (json['value'] ?? 0).toDouble(), + currency: json['currency'] ?? 'USD', + stage: DealStage.fromString(json['stage'] ?? 'prospect'), + contactId: json['contact_id'], + companyId: json['company_id'], + expectedCloseDate: json['expected_close_date'] != null + ? DateTime.parse(json['expected_close_date']) + : null, + probability: (json['probability'] ?? 0).toDouble(), + createdAt: DateTime.parse( + json['created_at'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updated_at'] ?? DateTime.now().toIso8601String(), + ), + customFields: json['custom_fields'], + ); + } + + Map toJson() { + return { + 'id': id, + 'title': title, + 'description': description, + 'value': value, + 'currency': currency, + 'stage': stage.value, + 'contact_id': contactId, + 'company_id': companyId, + 'expected_close_date': expectedCloseDate?.toIso8601String(), + 'probability': probability, + 'created_at': createdAt.toIso8601String(), + 'updated_at': updatedAt.toIso8601String(), + 'custom_fields': customFields, + }; + } +} + +enum DealStage { + prospect('prospect'), + qualified('qualified'), + proposal('proposal'), + negotiation('negotiation'), + closed('closed'), + won('won'), + lost('lost'); + + const DealStage(this.value); + final String value; + + static DealStage fromString(String value) { + return DealStage.values.firstWhere( + (stage) => stage.value == value, + orElse: () => DealStage.prospect, + ); + } +} + +// Company model +class Company { + final String id; + final String name; + final String? website; + final String? industry; + final String? phone; + final String? email; + final Address? address; + final int? employeeCount; + final double? revenue; + final DateTime createdAt; + final DateTime updatedAt; + final Map? customFields; + + Company({ + required this.id, + required this.name, + this.website, + this.industry, + this.phone, + this.email, + this.address, + this.employeeCount, + this.revenue, + required this.createdAt, + required this.updatedAt, + this.customFields, + }); + + factory Company.fromJson(Map json) { + return Company( + id: json['id'] ?? '', + name: json['name'] ?? '', + website: json['website'], + industry: json['industry'], + phone: json['phone'], + email: json['email'], + address: json['address'] != null + ? Address.fromJson(json['address']) + : null, + employeeCount: json['employee_count'], + revenue: json['revenue']?.toDouble(), + createdAt: DateTime.parse( + json['created_at'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updated_at'] ?? DateTime.now().toIso8601String(), + ), + customFields: json['custom_fields'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'website': website, + 'industry': industry, + 'phone': phone, + 'email': email, + 'address': address?.toJson(), + 'employee_count': employeeCount, + 'revenue': revenue, + 'created_at': createdAt.toIso8601String(), + 'updated_at': updatedAt.toIso8601String(), + 'custom_fields': customFields, + }; + } +} + +// Address model +class Address { + final String? street; + final String? city; + final String? state; + final String? zipCode; + final String? country; + + Address({this.street, this.city, this.state, this.zipCode, this.country}); + + factory Address.fromJson(Map json) { + return Address( + street: json['street'], + city: json['city'], + state: json['state'], + zipCode: json['zip_code'], + country: json['country'], + ); + } + + Map toJson() { + return { + 'street': street, + 'city': city, + 'state': state, + 'zip_code': zipCode, + 'country': country, + }; + } + + String get fullAddress { + final parts = [ + street, + city, + state, + zipCode, + country, + ].where((part) => part != null && part.isNotEmpty).toList(); + return parts.join(', '); + } +} + +// Activity model +class Activity { + final String id; + final String type; + final String title; + final String? description; + final String? contactId; + final String? dealId; + final String? companyId; + final DateTime scheduledAt; + final DateTime? completedAt; + final ActivityStatus status; + final DateTime createdAt; + final DateTime updatedAt; + + Activity({ + required this.id, + required this.type, + required this.title, + this.description, + this.contactId, + this.dealId, + this.companyId, + required this.scheduledAt, + this.completedAt, + required this.status, + required this.createdAt, + required this.updatedAt, + }); + + factory Activity.fromJson(Map json) { + return Activity( + id: json['id'] ?? '', + type: json['type'] ?? '', + title: json['title'] ?? '', + description: json['description'], + contactId: json['contact_id'], + dealId: json['deal_id'], + companyId: json['company_id'], + scheduledAt: DateTime.parse( + json['scheduled_at'] ?? DateTime.now().toIso8601String(), + ), + completedAt: json['completed_at'] != null + ? DateTime.parse(json['completed_at']) + : null, + status: ActivityStatus.fromString(json['status'] ?? 'pending'), + createdAt: DateTime.parse( + json['created_at'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updated_at'] ?? DateTime.now().toIso8601String(), + ), + ); + } + + Map toJson() { + return { + 'id': id, + 'type': type, + 'title': title, + 'description': description, + 'contact_id': contactId, + 'deal_id': dealId, + 'company_id': companyId, + 'scheduled_at': scheduledAt.toIso8601String(), + 'completed_at': completedAt?.toIso8601String(), + 'status': status.value, + 'created_at': createdAt.toIso8601String(), + 'updated_at': updatedAt.toIso8601String(), + }; + } +} + +enum ActivityStatus { + pending('pending'), + completed('completed'), + cancelled('cancelled'), + overdue('overdue'); + + const ActivityStatus(this.value); + final String value; + + static ActivityStatus fromString(String value) { + return ActivityStatus.values.firstWhere( + (status) => status.value == value, + orElse: () => ActivityStatus.pending, + ); + } +} + +// Dashboard metrics model +class DashboardMetrics { + final int totalLeads; + final int totalOpportunities; + final int totalAccounts; + final int totalContacts; + final int pendingTasks; + final double opportunityRevenue; + + DashboardMetrics({ + required this.totalLeads, + required this.totalOpportunities, + required this.totalAccounts, + required this.totalContacts, + required this.pendingTasks, + required this.opportunityRevenue, + }); + + factory DashboardMetrics.fromJson(Map json) { + final metrics = json['metrics'] ?? {}; + return DashboardMetrics( + totalLeads: metrics['totalLeads'] ?? 0, + totalOpportunities: metrics['totalOpportunities'] ?? 0, + totalAccounts: metrics['totalAccounts'] ?? 0, + totalContacts: metrics['totalContacts'] ?? 0, + pendingTasks: metrics['pendingTasks'] ?? 0, + opportunityRevenue: (metrics['opportunityRevenue'] ?? 0).toDouble(), + ); + } + + Map toJson() { + return { + 'metrics': { + 'totalLeads': totalLeads, + 'totalOpportunities': totalOpportunities, + 'totalAccounts': totalAccounts, + 'totalContacts': totalContacts, + 'pendingTasks': pendingTasks, + 'opportunityRevenue': opportunityRevenue, + }, + }; + } +} + +// Task model +class Task { + final String id; + final String subject; + final String? description; + final TaskStatus status; + final TaskPriority priority; + final DateTime? dueDate; + final String? ownerId; + final String? createdById; + final String? accountId; + final String? contactId; + final String? leadId; + final String? opportunityId; + final String? caseId; + final String organizationId; + final DateTime createdAt; + final DateTime updatedAt; + final TaskOwner? owner; + final TaskUser? createdBy; + final TaskAccount? account; + final TaskContact? contact; + final TaskLead? lead; + final TaskOpportunity? opportunity; + final TaskCase? case_; + final TaskOrganization? organization; + final List? comments; + + Task({ + required this.id, + required this.subject, + this.description, + required this.status, + required this.priority, + this.dueDate, + this.ownerId, + this.createdById, + this.accountId, + this.contactId, + this.leadId, + this.opportunityId, + this.caseId, + required this.organizationId, + required this.createdAt, + required this.updatedAt, + this.owner, + this.createdBy, + this.account, + this.contact, + this.lead, + this.opportunity, + this.case_, + this.organization, + this.comments, + }); + + // Backward compatibility getter + String get title => subject; + + bool get isOverdue => + dueDate != null && + dueDate!.isBefore(DateTime.now()) && + status != TaskStatus.completed; + + factory Task.fromJson(Map json) { + return Task( + id: json['id'] ?? '', + subject: json['subject'] ?? json['title'] ?? '', + description: json['description'], + status: TaskStatus.fromString(json['status'] ?? 'Not Started'), + priority: TaskPriority.fromString(json['priority'] ?? 'Medium'), + dueDate: json['dueDate'] != null ? DateTime.parse(json['dueDate']) : null, + ownerId: json['ownerId'], + createdById: json['createdById'], + accountId: json['accountId'], + contactId: json['contactId'], + leadId: json['leadId'], + opportunityId: json['opportunityId'], + caseId: json['caseId'], + organizationId: json['organizationId'] ?? '', + createdAt: DateTime.parse( + json['createdAt'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updatedAt'] ?? DateTime.now().toIso8601String(), + ), + owner: json['owner'] != null ? TaskOwner.fromJson(json['owner']) : null, + createdBy: json['createdBy'] != null + ? TaskUser.fromJson(json['createdBy']) + : null, + account: json['account'] != null + ? TaskAccount.fromJson(json['account']) + : null, + contact: json['contact'] != null + ? TaskContact.fromJson(json['contact']) + : null, + lead: json['lead'] != null ? TaskLead.fromJson(json['lead']) : null, + opportunity: json['opportunity'] != null + ? TaskOpportunity.fromJson(json['opportunity']) + : null, + case_: json['case'] != null ? TaskCase.fromJson(json['case']) : null, + organization: json['organization'] != null + ? TaskOrganization.fromJson(json['organization']) + : null, + comments: json['comments'] != null + ? (json['comments'] as List) + .map( + (commentJson) => + TaskComment.fromJson(commentJson as Map), + ) + .toList() + : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'subject': subject, + 'description': description, + 'status': status.value, + 'priority': priority.value, + 'dueDate': dueDate?.toIso8601String(), + 'ownerId': ownerId, + 'createdById': createdById, + 'accountId': accountId, + 'contactId': contactId, + 'leadId': leadId, + 'opportunityId': opportunityId, + 'caseId': caseId, + 'organizationId': organizationId, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'owner': owner?.toJson(), + 'createdBy': createdBy?.toJson(), + 'account': account?.toJson(), + 'contact': contact?.toJson(), + 'lead': lead?.toJson(), + 'opportunity': opportunity?.toJson(), + 'case': case_?.toJson(), + 'organization': organization?.toJson(), + 'comments': comments?.map((comment) => comment.toJson()).toList(), + }; + } +} + +enum TaskStatus { + notStarted('Not Started'), + inProgress('In Progress'), + completed('Completed'), + cancelled('Cancelled'); + + const TaskStatus(this.value); + final String value; + + static TaskStatus fromString(String value) { + return TaskStatus.values.firstWhere( + (status) => status.value == value, + orElse: () => TaskStatus.notStarted, + ); + } + + // Backward compatibility getter + static TaskStatus get toDo => TaskStatus.notStarted; +} + +enum TaskPriority { + high('High'), + normal('Normal'), + low('Low'); + + const TaskPriority(this.value); + final String value; + + static TaskPriority fromString(String value) { + return TaskPriority.values.firstWhere( + (priority) => priority.value == value, + orElse: () => TaskPriority.normal, + ); + } +} + +enum LeadStatus { + newLead('NEW'), + pending('PENDING'), + contacted('CONTACTED'), + qualified('QUALIFIED'), + unqualified('UNQUALIFIED'), + converted('CONVERTED'); + + const LeadStatus(this.value); + final String value; + + static LeadStatus fromString(String value) { + return LeadStatus.values.firstWhere( + (status) => status.value == value, + orElse: () => LeadStatus.newLead, + ); + } +} + +enum LeadSource { + web('WEB'), + phoneInquiry('PHONE_INQUIRY'), + partnerReferral('PARTNER_REFERRAL'), + coldCall('COLD_CALL'), + tradeShow('TRADE_SHOW'), + employeeReferral('EMPLOYEE_REFERRAL'), + advertisement('ADVERTISEMENT'), + other('OTHER'); + + const LeadSource(this.value); + final String value; + + static LeadSource fromString(String value) { + return LeadSource.values.firstWhere( + (source) => source.value == value, + orElse: () => LeadSource.web, + ); + } + + String get displayName { + switch (this) { + case LeadSource.phoneInquiry: + return 'Phone Inquiry'; + case LeadSource.partnerReferral: + return 'Partner Referral'; + case LeadSource.coldCall: + return 'Cold Call'; + case LeadSource.tradeShow: + return 'Trade Show'; + case LeadSource.employeeReferral: + return 'Employee Referral'; + case LeadSource.advertisement: + return 'Advertisement'; + case LeadSource.other: + return 'Other'; + default: + return value; + } + } +} + +enum LeadRating { + hot('Hot'), + warm('Warm'), + cold('Cold'); + + const LeadRating(this.value); + final String value; + + static LeadRating fromString(String value) { + return LeadRating.values.firstWhere( + (rating) => rating.value == value, + orElse: () => LeadRating.warm, + ); + } +} + +enum LeadIndustry { + technology('Technology'), + healthcare('Healthcare'), + finance('Finance'), + education('Education'), + manufacturing('Manufacturing'), + retail('Retail'), + realEstate('Real Estate'), + consulting('Consulting'), + media('Media'), + transportation('Transportation'), + energy('Energy'), + government('Government'), + nonProfit('Non-profit'), + other('Other'); + + const LeadIndustry(this.value); + final String value; + + static LeadIndustry fromString(String value) { + return LeadIndustry.values.firstWhere( + (industry) => industry.value == value, + orElse: () => LeadIndustry.other, + ); + } +} + +class TaskOwner { + final String id; + final String name; + final String email; + + TaskOwner({required this.id, required this.name, required this.email}); + + factory TaskOwner.fromJson(Map json) { + return TaskOwner( + id: json['id'] ?? '', + name: json['name'] ?? '', + email: json['email'] ?? '', + ); + } + + Map toJson() { + return {'id': id, 'name': name, 'email': email}; + } +} + +class TaskAccount { + final String id; + final String name; + final String? type; + final String? website; + final String? phone; + + TaskAccount({ + required this.id, + required this.name, + this.type, + this.website, + this.phone, + }); + + factory TaskAccount.fromJson(Map json) { + return TaskAccount( + id: json['id'] ?? '', + name: json['name'] ?? '', + type: json['type'], + website: json['website'], + phone: json['phone'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'type': type, + 'website': website, + 'phone': phone, + }; + } +} + +class TaskContact { + final String id; + final String firstName; + final String lastName; + final String? email; + final String? phone; + final String? title; + + TaskContact({ + required this.id, + required this.firstName, + required this.lastName, + this.email, + this.phone, + this.title, + }); + + String get fullName => '$firstName $lastName'; + + factory TaskContact.fromJson(Map json) { + return TaskContact( + id: json['id'] ?? '', + firstName: json['firstName'] ?? '', + lastName: json['lastName'] ?? '', + email: json['email'], + phone: json['phone'], + title: json['title'], + ); + } + + Map toJson() { + return { + 'id': id, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'phone': phone, + 'title': title, + }; + } +} + +class TaskUser { + final String id; + final String name; + final String email; + + TaskUser({required this.id, required this.name, required this.email}); + + factory TaskUser.fromJson(Map json) { + return TaskUser( + id: json['id'] ?? '', + name: json['name'] ?? '', + email: json['email'] ?? '', + ); + } + + Map toJson() { + return {'id': id, 'name': name, 'email': email}; + } +} + +class TaskLead { + final String id; + final String firstName; + final String lastName; + final String? email; + final String? company; + + TaskLead({ + required this.id, + required this.firstName, + required this.lastName, + this.email, + this.company, + }); + + String get fullName => '$firstName $lastName'; + + factory TaskLead.fromJson(Map json) { + return TaskLead( + id: json['id'] ?? '', + firstName: json['firstName'] ?? '', + lastName: json['lastName'] ?? '', + email: json['email'], + company: json['company'], + ); + } + + Map toJson() { + return { + 'id': id, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'company': company, + }; + } +} + +class TaskOpportunity { + final String id; + final String name; + final double amount; + final String status; + final String stage; + final DateTime? closeDate; + + TaskOpportunity({ + required this.id, + required this.name, + required this.amount, + required this.status, + required this.stage, + this.closeDate, + }); + + factory TaskOpportunity.fromJson(Map json) { + return TaskOpportunity( + id: json['id'] ?? '', + name: json['name'] ?? '', + amount: (json['amount'] ?? 0).toDouble(), + status: json['status'] ?? '', + stage: json['stage'] ?? '', + closeDate: json['closeDate'] != null + ? DateTime.parse(json['closeDate']) + : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'amount': amount, + 'status': status, + 'stage': stage, + 'closeDate': closeDate?.toIso8601String(), + }; + } +} + +class TaskCase { + final String id; + final String subject; + final String status; + final String priority; + + TaskCase({ + required this.id, + required this.subject, + required this.status, + required this.priority, + }); + + factory TaskCase.fromJson(Map json) { + return TaskCase( + id: json['id'] ?? '', + subject: json['subject'] ?? '', + status: json['status'] ?? '', + priority: json['priority'] ?? '', + ); + } + + Map toJson() { + return { + 'id': id, + 'subject': subject, + 'status': status, + 'priority': priority, + }; + } +} + +class TaskOrganization { + final String id; + final String name; + + TaskOrganization({required this.id, required this.name}); + + factory TaskOrganization.fromJson(Map json) { + return TaskOrganization(id: json['id'] ?? '', name: json['name'] ?? ''); + } + + Map toJson() { + return {'id': id, 'name': name}; + } +} + +class TaskComment { + final String id; + final String body; + final bool isPrivate; + final DateTime createdAt; + final DateTime updatedAt; + final String authorId; + final String organizationId; + final String taskId; + final TaskUser? author; + + TaskComment({ + required this.id, + required this.body, + required this.isPrivate, + required this.createdAt, + required this.updatedAt, + required this.authorId, + required this.organizationId, + required this.taskId, + this.author, + }); + + factory TaskComment.fromJson(Map json) { + return TaskComment( + id: json['id'] ?? '', + body: json['body'] ?? '', + isPrivate: json['isPrivate'] ?? false, + createdAt: DateTime.parse( + json['createdAt'] ?? DateTime.now().toIso8601String(), + ), + updatedAt: DateTime.parse( + json['updatedAt'] ?? DateTime.now().toIso8601String(), + ), + authorId: json['authorId'] ?? '', + organizationId: json['organizationId'] ?? '', + taskId: json['taskId'] ?? '', + author: json['author'] != null ? TaskUser.fromJson(json['author']) : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'body': body, + 'isPrivate': isPrivate, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'authorId': authorId, + 'organizationId': organizationId, + 'taskId': taskId, + 'author': author?.toJson(), + }; + } +} + +// Tasks response wrapper for API +class TasksResponse { + final List tasks; + final Pagination? pagination; + + TasksResponse({required this.tasks, this.pagination}); + + factory TasksResponse.fromJson(Map json) { + return TasksResponse( + tasks: + (json['tasks'] as List?) + ?.map( + (taskJson) => Task.fromJson(taskJson as Map), + ) + .toList() ?? + [], + pagination: json['pagination'] != null + ? Pagination.fromJson(json['pagination']) + : null, + ); + } +} + +// Keep the old model for backward compatibility (deprecated) +@Deprecated('Use DashboardMetrics instead') +class DashboardStats { + final int totalContacts; + final int totalLeads; + final int totalDeals; + final double totalRevenue; + final int activitiesThisWeek; + final double conversionRate; + final Map leadsBySource; + final Map dealsByStage; + + DashboardStats({ + required this.totalContacts, + required this.totalLeads, + required this.totalDeals, + required this.totalRevenue, + required this.activitiesThisWeek, + required this.conversionRate, + required this.leadsBySource, + required this.dealsByStage, + }); + + factory DashboardStats.fromJson(Map json) { + return DashboardStats( + totalContacts: json['total_contacts'] ?? 0, + totalLeads: json['total_leads'] ?? 0, + totalDeals: json['total_deals'] ?? 0, + totalRevenue: (json['total_revenue'] ?? 0).toDouble(), + activitiesThisWeek: json['activities_this_week'] ?? 0, + conversionRate: (json['conversion_rate'] ?? 0).toDouble(), + leadsBySource: Map.from(json['leads_by_source'] ?? {}), + dealsByStage: Map.from(json['deals_by_stage'] ?? {}), + ); + } + + Map toJson() { + return { + 'total_contacts': totalContacts, + 'total_leads': totalLeads, + 'total_deals': totalDeals, + 'total_revenue': totalRevenue, + 'activities_this_week': activitiesThisWeek, + 'conversion_rate': conversionRate, + 'leads_by_source': leadsBySource, + 'deals_by_stage': dealsByStage, + }; + } +} diff --git a/lib/responsive.dart b/lib/responsive.dart deleted file mode 100644 index 56a27f6..0000000 --- a/lib/responsive.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; - -class Responsive extends StatelessWidget { - final Widget mobile; - final Widget tablet; - final Widget desktop; - - const Responsive({ - Key? key, - required this.mobile, - required this.tablet, - required this.desktop, - }) : super(key: key); - -// This size work fine on my design, maybe you need some customization depends on your design - - // This isMobile, isTablet, isDesktop helep us later - static bool isMobile(BuildContext context) => - MediaQuery.of(context).size.width < 650; - - static bool isTablet(BuildContext context) => - MediaQuery.of(context).size.width < 1100 && - MediaQuery.of(context).size.width >= 650; - - static bool isDesktop(BuildContext context) => - MediaQuery.of(context).size.width >= 1100; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - // If our width is more than 1100 then we consider it a desktop - builder: (context, constraints) { - if (constraints.maxWidth >= 1100) { - return desktop; - } - // If width it less then 1100 and more then 650 we consider it as tablet - else if (constraints.maxWidth >= 650) { - return tablet; - } - // Or less then that we called it mobile - else { - return mobile; - } - }, - ); - } -} diff --git a/lib/screens/about_screen.dart b/lib/screens/about_screen.dart new file mode 100644 index 0000000..c2ca806 --- /dev/null +++ b/lib/screens/about_screen.dart @@ -0,0 +1,760 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'dart:io' show Platform; + +class AboutScreen extends StatefulWidget { + const AboutScreen({super.key}); + + @override + State createState() => _AboutScreenState(); +} + +class _AboutScreenState extends State { + PackageInfo? _packageInfo; + + @override + void initState() { + super.initState(); + _loadPackageInfo(); + } + + Future _loadPackageInfo() async { + try { + final packageInfo = await PackageInfo.fromPlatform(); + if (mounted) { + setState(() { + _packageInfo = packageInfo; + }); + } + } catch (e) { + debugPrint('Error loading package info: $e'); + } + } + + Widget _buildInfoCard({ + required String title, + required List children, + required ThemeData theme, + IconData? icon, + }) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: theme.colorScheme.outline.withValues(alpha: 0.08), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.02), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + if (icon != null) ...[ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + size: 20, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(width: 12), + ], + Text( + title, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onSurface, + letterSpacing: 0.3, + ), + ), + ], + ), + ), + ...children, + ], + ), + ); + } + + Widget _buildInfoRow({ + required String label, + required String value, + required ThemeData theme, + VoidCallback? onTap, + bool copyable = false, + }) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: copyable + ? () { + HapticFeedback.lightImpact(); + Clipboard.setData(ClipboardData(text: value)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Copied $label to clipboard'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + : onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 4), + Text( + value, + style: theme.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w600, + height: 1.2, + ), + ), + ], + ), + ), + if (copyable) + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.copy_rounded, + size: 16, + color: theme.colorScheme.primary, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildFeatureChip({ + required String label, + required IconData icon, + required ThemeData theme, + }) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + margin: const EdgeInsets.only(right: 8, bottom: 8), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.colorScheme.primary.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 16, color: theme.colorScheme.primary), + const SizedBox(width: 6), + Text( + label, + style: TextStyle( + color: theme.colorScheme.primary, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + backgroundColor: theme.colorScheme.surface, + appBar: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + backgroundColor: theme.colorScheme.surface, + surfaceTintColor: Colors.transparent, + title: Text( + 'About BottleCRM', + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + ), + body: ListView( + padding: const EdgeInsets.only(top: 8, bottom: 32), + children: [ + // App Hero Section + RepaintBoundary( + child: Container( + margin: const EdgeInsets.fromLTRB(20, 0, 20, 16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + theme.colorScheme.primary.withValues(alpha: 0.05), + theme.colorScheme.primary.withValues(alpha: 0.02), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: theme.colorScheme.outline.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + // App Icon + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: theme.colorScheme.primary.withValues( + alpha: 0.3, + ), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: Image.asset( + 'assets/icon/icon.png', + width: 80, + height: 80, + fit: BoxFit.cover, + ), + ), + ), + + const SizedBox(height: 20), + + // App Name and Version + Text( + 'BottleCRM', + style: theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w800, + color: theme.colorScheme.onSurface, + ), + ), + + const SizedBox(height: 8), + + Text( + 'CRM for everyone', + style: theme.textTheme.bodyLarge?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w500, + ), + ), + + const SizedBox(height: 8), + + // Company Badge + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest + .withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.colorScheme.outline.withValues( + alpha: 0.2, + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.business_rounded, + size: 16, + color: theme.colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 8), + Text( + 'by MicroPyramid', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + + const SizedBox(height: 4), + + if (_packageInfo != null) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer.withValues( + alpha: 0.5, + ), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Version ${_packageInfo!.version} (${_packageInfo!.buildNumber})', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ), + ), + + // App Information + RepaintBoundary( + child: _buildInfoCard( + title: 'App Information', + icon: Icons.info_rounded, + theme: theme, + children: [ + if (_packageInfo != null) ...[ + _buildInfoRow( + label: 'Package Name', + value: _packageInfo!.packageName, + theme: theme, + copyable: true, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + label: 'Version', + value: _packageInfo!.version, + theme: theme, + copyable: true, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + label: 'Build Number', + value: _packageInfo!.buildNumber, + theme: theme, + copyable: true, + ), + ], + const SizedBox(height: 8), + ], + ), + ), + + // Key Features + RepaintBoundary( + child: _buildInfoCard( + title: 'Key Features', + icon: Icons.star_rounded, + theme: theme, + children: [ + Container( + padding: const EdgeInsets.all(20), + child: Wrap( + children: [ + _buildFeatureChip( + label: 'Lead Management', + icon: Icons.person_add_rounded, + theme: theme, + ), + _buildFeatureChip( + label: 'Contact Management', + icon: Icons.contacts_rounded, + theme: theme, + ), + _buildFeatureChip( + label: 'Task Tracking', + icon: Icons.task_rounded, + theme: theme, + ), + _buildFeatureChip( + label: 'Multi-tenant', + icon: Icons.business_rounded, + theme: theme, + ), + _buildFeatureChip( + label: 'Dashboard Analytics', + icon: Icons.analytics_rounded, + theme: theme, + ), + _buildFeatureChip( + label: 'Google OAuth', + icon: Icons.security_rounded, + theme: theme, + ), + _buildFeatureChip( + label: 'Open Source', + icon: Icons.code_rounded, + theme: theme, + ), + ], + ), + ), + ], + ), + ), + + // Company Information + RepaintBoundary( + child: _buildInfoCard( + title: 'Developed by', + icon: Icons.business_center_rounded, + theme: theme, + children: [ + _buildInfoRow( + label: 'Company', + value: 'MicroPyramid', + theme: theme, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + label: 'Website', + value: 'micropyramid.com', + theme: theme, + onTap: () { + HapticFeedback.lightImpact(); + // TODO: Launch website URL + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text( + 'Visit micropyramid.com for more information', + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + action: SnackBarAction( + label: 'Copy', + onPressed: () { + Clipboard.setData( + const ClipboardData( + text: 'https://micropyramid.com', + ), + ); + }, + ), + ), + ); + }, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + Container( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 20, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues( + alpha: 0.08, + ), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.description_rounded, + size: 20, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(width: 16), + Text( + 'About MicroPyramid', + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest + .withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.colorScheme.outline.withValues( + alpha: 0.2, + ), + ), + ), + child: Text( + 'MicroPyramid is a technology company specializing in open-source CRM solutions and custom software development. We are committed to building accessible, user-friendly business tools that help organizations manage their customer relationships effectively.', + style: theme.textTheme.bodyMedium?.copyWith( + height: 1.5, + color: theme.colorScheme.onSurface.withValues( + alpha: 0.8, + ), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 8), + ], + ), + ), + + // System Information + RepaintBoundary( + child: _buildInfoCard( + title: 'System Information', + icon: Icons.phone_android_rounded, + theme: theme, + children: [ + _buildInfoRow( + label: 'Platform', + value: Platform.isAndroid + ? 'Android' + : Platform.isIOS + ? 'iOS' + : 'Unknown', + theme: theme, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + label: 'Framework', + value: 'Flutter', + theme: theme, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow(label: 'Language', value: 'Dart', theme: theme), + const SizedBox(height: 8), + ], + ), + ), + + // Legal Information + RepaintBoundary( + child: _buildInfoCard( + title: 'Legal', + icon: Icons.gavel_rounded, + theme: theme, + children: [ + Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + HapticFeedback.lightImpact(); + showLicensePage( + context: context, + applicationName: 'BottleCRM', + applicationVersion: _packageInfo?.version ?? '1.0.0', + applicationIcon: Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: theme.colorScheme.primary, + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.business_center_rounded, + color: Colors.white, + size: 24, + ), + ), + ); + }, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 20, + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Open Source Licenses', + style: theme.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w600, + height: 1.2, + ), + ), + const SizedBox(height: 4), + Text( + 'View third-party licenses', + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.onSurface + .withValues(alpha: 0.7), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues( + alpha: 0.08, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.launch, + size: 16, + color: theme.colorScheme.primary, + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ), + + // Development Information + RepaintBoundary( + child: Container( + margin: const EdgeInsets.fromLTRB(20, 8, 20, 0), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest.withValues( + alpha: 0.3, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: theme.colorScheme.outline.withValues(alpha: 0.1), + ), + ), + child: Column( + children: [ + Icon( + Icons.code_rounded, + size: 32, + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + ), + const SizedBox(height: 12), + Text( + 'Made with โค๏ธ using Flutter', + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.7), + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'Developed by MicroPyramid', + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.8), + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 4), + Text( + 'ยฉ ${DateTime.now().year} MicroPyramid. All rights reserved.', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.5), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/company_create_screen.dart b/lib/screens/company_create_screen.dart new file mode 100644 index 0000000..822a5d2 --- /dev/null +++ b/lib/screens/company_create_screen.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import '../services/api_service.dart'; +import '../config/api_config.dart'; + +class CompanyCreateScreen extends StatefulWidget { + const CompanyCreateScreen({super.key}); + + @override + State createState() => _CompanyCreateScreenState(); +} + +class _CompanyCreateScreenState extends State { + final _formKey = GlobalKey(); + final _nameController = TextEditingController(); + bool _isLoading = false; + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + Future _createCompany() async { + if (!_formKey.currentState!.validate()) { + return; + } + + setState(() { + _isLoading = true; + }); + + try { + final response = await ApiService().post(ApiConfig.organizations, { + 'name': _nameController.text.trim(), + }); + + if (response.success) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Company "${_nameController.text.trim()}" created successfully', + ), + backgroundColor: Colors.green, + ), + ); + Navigator.of(context).pop(true); // Return true to indicate success + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Error creating company: ${response.message ?? "Unknown error"}', + ), + backgroundColor: Colors.red, + ), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error creating company: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey[50], + appBar: AppBar( + title: const Text('Create Company'), + backgroundColor: Colors.white, + foregroundColor: Colors.black, + elevation: 0, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade200), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Company Details', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.grey[800], + ), + ), + const SizedBox(height: 16), + TextFormField( + controller: _nameController, + decoration: InputDecoration( + labelText: 'Company Name *', + hintText: 'Enter company name', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Theme.of(context).primaryColor, + ), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Company name is required'; + } + if (value.trim().length < 2) { + return 'Company name must be at least 2 characters'; + } + return null; + }, + textCapitalization: TextCapitalization.words, + ), + ], + ), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: _isLoading ? null : _createCompany, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 0, + ), + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ) + : const Text( + 'Create Company', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/company_selection_screen.dart b/lib/screens/company_selection_screen.dart new file mode 100644 index 0000000..2e7f6a5 --- /dev/null +++ b/lib/screens/company_selection_screen.dart @@ -0,0 +1,309 @@ +import 'package:flutter/material.dart'; +import '../services/auth_service.dart'; + +class CompanySelectionScreen extends StatefulWidget { + const CompanySelectionScreen({super.key}); + + @override + State createState() => _CompanySelectionScreenState(); +} + +class _CompanySelectionScreenState extends State { + final AuthService _authService = AuthService(); + List? _organizations; + bool _isLoading = false; + + @override + void initState() { + super.initState(); + _loadOrganizations(); + } + + Future _loadOrganizations() async { + setState(() { + _isLoading = true; + }); + + try { + // First try to use cached organizations + if (_authService.organizations != null && + _authService.organizations!.isNotEmpty) { + setState(() { + _organizations = _authService.organizations; + _isLoading = false; + }); + + // Fetch fresh data in background + _authService.fetchOrganizations(); + return; + } + + // If no cached data, fetch from API + final success = await _authService.fetchOrganizations(); + if (success && mounted) { + setState(() { + _organizations = _authService.organizations; + }); + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to load organizations'), + backgroundColor: Colors.red, + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error loading organizations: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + Future _selectCompany(Organization organization) async { + setState(() { + _isLoading = true; + }); + + try { + await _authService.selectOrganization(organization); + + debugPrint('Organization selected: ${organization.name}'); + + // Navigate to dashboard - let dashboard handle its own data loading + if (mounted) { + Navigator.of(context).pushReplacementNamed('/dashboard'); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error selecting company: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + Widget _buildCompanyCard(Organization organization) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade200), + ), + child: ListTile( + contentPadding: const EdgeInsets.all(16), + leading: organization.logo != null + ? CircleAvatar( + backgroundImage: NetworkImage(organization.logo!), + backgroundColor: Colors.grey[200], + onBackgroundImageError: (_, _) {}, + child: organization.logo == null + ? Text( + organization.name.isNotEmpty + ? organization.name[0].toUpperCase() + : 'C', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ) + : null, + ) + : CircleAvatar( + backgroundColor: Theme.of(context).primaryColor, + child: Text( + organization.name.isNotEmpty + ? organization.name[0].toUpperCase() + : 'C', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + title: Text( + organization.name, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Role: ${organization.role}', + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + if (organization.industry != null) + Text( + organization.industry!, + style: TextStyle(fontSize: 12, color: Colors.grey[500]), + ), + ], + ), + trailing: const Icon(Icons.arrow_forward_ios), + onTap: _isLoading ? null : () => _selectCompany(organization), + ), + ); + } + + @override + Widget build(BuildContext context) { + final user = _authService.currentUser; + + return Scaffold( + backgroundColor: Colors.grey[50], + appBar: AppBar( + title: const Text('Select Company'), + backgroundColor: Colors.white, + foregroundColor: Colors.black, + elevation: 0, + automaticallyImplyLeading: false, + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () async { + final navigator = Navigator.of(context); + final messenger = ScaffoldMessenger.of(context); + final result = await navigator.pushNamed('/company-create'); + if (result == true && mounted) { + // Refresh organizations from API after creating a new company + final success = await _authService.refreshOrganizations(); + if (success) { + await _loadOrganizations(); + messenger.showSnackBar( + const SnackBar( + content: Text('Company created successfully!'), + backgroundColor: Colors.green, + ), + ); + } else { + messenger.showSnackBar( + const SnackBar( + content: Text( + 'Company created, but failed to refresh list', + ), + backgroundColor: Colors.orange, + ), + ); + } + } + }, + tooltip: 'Create Company', + ), + IconButton( + icon: const Icon(Icons.logout), + onPressed: () async { + final navigator = Navigator.of(context); + await _authService.logout(); + if (mounted) { + navigator.pushReplacementNamed('/login'); + } + }, + ), + ], + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + // User info section + Container( + width: double.infinity, + color: Colors.white, + padding: const EdgeInsets.all(16), + child: Column( + children: [ + if (user?.profileImage != null) + CircleAvatar( + radius: 40, + backgroundImage: NetworkImage(user!.profileImage!), + ) + else + CircleAvatar( + radius: 40, + backgroundColor: Theme.of(context).primaryColor, + child: Text( + user?.name.isNotEmpty == true + ? user!.name[0].toUpperCase() + : 'U', + style: const TextStyle( + fontSize: 24, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 12), + Text( + user?.name ?? 'Unknown User', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + Text( + user?.email ?? '', + style: TextStyle(fontSize: 16, color: Colors.grey[600]), + ), + ], + ), + ), + const SizedBox(height: 16), + + // Company selection section + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + 'Choose your company to continue:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey[700], + ), + ), + ), + ), + const SizedBox(height: 8), + + // Organizations list + Expanded( + child: _organizations == null || _organizations!.isEmpty + ? const Center( + child: Text( + 'No companies available', + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ) + : ListView.builder( + itemCount: _organizations!.length, + itemBuilder: (context, index) { + return _buildCompanyCard(_organizations![index]); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/contact_create_screen.dart b/lib/screens/contact_create_screen.dart new file mode 100644 index 0000000..258ad52 --- /dev/null +++ b/lib/screens/contact_create_screen.dart @@ -0,0 +1,398 @@ +import 'package:flutter/material.dart'; +import '../services/contacts_service.dart'; + +class ContactCreateScreen extends StatefulWidget { + const ContactCreateScreen({super.key}); + + @override + State createState() => _ContactCreateScreenState(); +} + +class _ContactCreateScreenState extends State { + final GlobalKey _formKey = GlobalKey(); + final ContactsService _contactsService = ContactsService(); + + // Form controllers + final TextEditingController _firstNameController = TextEditingController(); + final TextEditingController _lastNameController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _phoneController = TextEditingController(); + final TextEditingController _titleController = TextEditingController(); + final TextEditingController _departmentController = TextEditingController(); + final TextEditingController _streetController = TextEditingController(); + final TextEditingController _cityController = TextEditingController(); + final TextEditingController _stateController = TextEditingController(); + final TextEditingController _postalCodeController = TextEditingController(); + final TextEditingController _countryController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + final TextEditingController _accountIdController = TextEditingController(); + + bool _isLoading = false; + + @override + void dispose() { + _firstNameController.dispose(); + _lastNameController.dispose(); + _emailController.dispose(); + _phoneController.dispose(); + _titleController.dispose(); + _departmentController.dispose(); + _streetController.dispose(); + _cityController.dispose(); + _stateController.dispose(); + _postalCodeController.dispose(); + _countryController.dispose(); + _descriptionController.dispose(); + _accountIdController.dispose(); + super.dispose(); + } + + Future _createContact() async { + if (!_formKey.currentState!.validate()) { + return; + } + + setState(() { + _isLoading = true; + }); + + try { + final contactData = { + 'firstName': _firstNameController.text.trim(), + 'lastName': _lastNameController.text.trim(), + 'email': _emailController.text.trim().isEmpty + ? null + : _emailController.text.trim(), + 'phone': _phoneController.text.trim().isEmpty + ? null + : _phoneController.text.trim(), + 'title': _titleController.text.trim().isEmpty + ? null + : _titleController.text.trim(), + 'department': _departmentController.text.trim().isEmpty + ? null + : _departmentController.text.trim(), + 'street': _streetController.text.trim().isEmpty + ? null + : _streetController.text.trim(), + 'city': _cityController.text.trim().isEmpty + ? null + : _cityController.text.trim(), + 'state': _stateController.text.trim().isEmpty + ? null + : _stateController.text.trim(), + 'postalCode': _postalCodeController.text.trim().isEmpty + ? null + : _postalCodeController.text.trim(), + 'country': _countryController.text.trim().isEmpty + ? null + : _countryController.text.trim(), + 'description': _descriptionController.text.trim().isEmpty + ? null + : _descriptionController.text.trim(), + 'accountId': _accountIdController.text.trim().isEmpty + ? null + : _accountIdController.text.trim(), + }; + + // Remove null values to clean up the payload + contactData.removeWhere((key, value) => value == null); + + final contact = await _contactsService.createContact(contactData); + + if (contact != null && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Contact ${contact.fullName} created successfully'), + backgroundColor: Colors.green, + ), + ); + Navigator.of(context).pop(true); // Return true to indicate success + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to create contact'), + backgroundColor: Colors.red, + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error creating contact: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + String? _validateRequired(String? value, String fieldName) { + if (value == null || value.trim().isEmpty) { + return '$fieldName is required'; + } + return null; + } + + String? _validateEmail(String? value) { + if (value == null || value.trim().isEmpty) { + return null; // Email is optional + } + + final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); + if (!emailRegex.hasMatch(value.trim())) { + return 'Please enter a valid email address'; + } + return null; + } + + String? _validatePhone(String? value) { + if (value == null || value.trim().isEmpty) { + return null; // Phone is optional + } + + final phoneRegex = RegExp(r'^\+?[\d\s\-\(\)]+$'); + if (!phoneRegex.hasMatch(value.trim())) { + return 'Please enter a valid phone number'; + } + return null; + } + + Widget _buildTextField({ + required TextEditingController controller, + required String label, + String? hint, + bool required = false, + TextInputType keyboardType = TextInputType.text, + int maxLines = 1, + String? Function(String?)? validator, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: TextFormField( + controller: controller, + keyboardType: keyboardType, + maxLines: maxLines, + decoration: InputDecoration( + labelText: required ? '$label *' : label, + hintText: hint, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + validator: + validator ?? + (required ? (value) => _validateRequired(value, label) : null), + ), + ); + } + + Widget _buildSectionHeader(String title) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Create Contact'), + elevation: 0, + actions: [ + TextButton( + onPressed: _isLoading ? null : _createContact, + child: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Save'), + ), + ], + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Basic Information Section + _buildSectionHeader('Basic Information'), + _buildTextField( + controller: _firstNameController, + label: 'First Name', + required: true, + hint: 'Enter first name', + ), + _buildTextField( + controller: _lastNameController, + label: 'Last Name', + required: true, + hint: 'Enter last name', + ), + _buildTextField( + controller: _emailController, + label: 'Email', + hint: 'Enter email address', + keyboardType: TextInputType.emailAddress, + validator: _validateEmail, + ), + _buildTextField( + controller: _phoneController, + label: 'Phone', + hint: 'Enter phone number', + keyboardType: TextInputType.phone, + validator: _validatePhone, + ), + + // Professional Information Section + _buildSectionHeader('Professional Information'), + _buildTextField( + controller: _titleController, + label: 'Job Title', + hint: 'Enter job title', + ), + _buildTextField( + controller: _departmentController, + label: 'Department', + hint: 'Enter department', + ), + + // Address Information Section + _buildSectionHeader('Address Information'), + _buildTextField( + controller: _streetController, + label: 'Street Address', + hint: 'Enter street address', + ), + Row( + children: [ + Expanded( + flex: 2, + child: _buildTextField( + controller: _cityController, + label: 'City', + hint: 'Enter city', + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildTextField( + controller: _stateController, + label: 'State', + hint: 'Enter state', + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: _buildTextField( + controller: _postalCodeController, + label: 'Postal Code', + hint: 'Enter postal/zip code', + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildTextField( + controller: _countryController, + label: 'Country', + hint: 'Enter country', + ), + ), + ], + ), + + // Additional Information Section + _buildSectionHeader('Additional Information'), + _buildTextField( + controller: _descriptionController, + label: 'Description', + hint: 'Enter additional notes or description', + maxLines: 3, + ), + _buildTextField( + controller: _accountIdController, + label: 'Account ID', + hint: 'Enter associated account ID (optional)', + ), + + const SizedBox(height: 32), + + // Create Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _createContact, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: _isLoading + ? const Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ), + SizedBox(width: 12), + Text('Creating Contact...'), + ], + ) + : const Text( + 'Create Contact', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Help text + Center( + child: Text( + 'Fields marked with * are required', + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: Colors.grey[600]), + ), + ), + + const SizedBox(height: 32), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/contact_detail_screen.dart b/lib/screens/contact_detail_screen.dart new file mode 100644 index 0000000..17f15c0 --- /dev/null +++ b/lib/screens/contact_detail_screen.dart @@ -0,0 +1,608 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../models/api_models.dart'; +import '../services/contacts_service.dart'; + +class ContactDetailScreen extends StatefulWidget { + final String contactId; + + const ContactDetailScreen({super.key, required this.contactId}); + + @override + State createState() => _ContactDetailScreenState(); +} + +class _ContactDetailScreenState extends State { + final ContactsService _contactsService = ContactsService(); + + Contact? _contact; + bool _isLoading = true; + bool _hasError = false; + String? _errorMessage; + + @override + void initState() { + super.initState(); + _loadContactDetails(); + } + + Future _loadContactDetails() async { + setState(() { + _isLoading = true; + _hasError = false; + }); + + try { + final contact = await _contactsService.getContactById(widget.contactId); + + if (contact != null && mounted) { + setState(() { + _contact = contact; + _isLoading = false; + }); + } else if (mounted) { + setState(() { + _hasError = true; + _errorMessage = 'Contact not found'; + _isLoading = false; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _hasError = true; + _errorMessage = e.toString(); + _isLoading = false; + }); + } + } + } + + Future _copyToClipboard(String text, String label) async { + await Clipboard.setData(ClipboardData(text: text)); + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('$label copied to clipboard'))); + } + } + + Widget _buildInfoSection({ + required String title, + required List children, + IconData? icon, + }) { + return Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (icon != null) ...[ + Icon( + icon, + size: 20, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 8), + ], + Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + const SizedBox(height: 12), + ...children, + ], + ), + ), + ); + } + + Widget _buildInfoRow({ + required String label, + required String? value, + IconData? icon, + VoidCallback? onTap, + bool copyable = false, + }) { + if (value == null || value.isEmpty) { + return const SizedBox.shrink(); + } + + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (icon != null) ...[ + Icon(icon, size: 16, color: Colors.grey[600]), + const SizedBox(width: 8), + ], + Expanded( + flex: 2, + child: Text( + label, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: Colors.grey[700], + ), + ), + ), + Expanded( + flex: 3, + child: GestureDetector( + onTap: + onTap ?? + (copyable ? () => _copyToClipboard(value, label) : null), + child: Text( + value, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: onTap != null || copyable + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface, + decoration: onTap != null || copyable + ? TextDecoration.underline + : null, + ), + ), + ), + ), + if (copyable) + GestureDetector( + onTap: () => _copyToClipboard(value, label), + child: Icon(Icons.copy, size: 16, color: Colors.grey[600]), + ), + ], + ), + ); + } + + Widget _buildAccountCard(RelatedAccount relatedAccount) { + final account = relatedAccount.account; + + return Card( + margin: const EdgeInsets.only(bottom: 8), + elevation: 1, + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + account.name, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ), + if (relatedAccount.isPrimary) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + ), + ), + child: Text( + 'Primary', + style: TextStyle( + color: Colors.green[700], + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + _buildInfoRow(label: 'Role', value: relatedAccount.role), + _buildInfoRow(label: 'Type', value: account.type), + if (account.website != null) + _buildInfoRow( + label: 'Website', + value: account.website, + copyable: true, + ), + if (account.phone != null) + _buildInfoRow( + label: 'Phone', + value: account.phone, + copyable: true, + ), + if (relatedAccount.description != null && + relatedAccount.description!.isNotEmpty) + _buildInfoRow( + label: 'Description', + value: relatedAccount.description, + ), + ], + ), + ), + ); + } + + String _formatDate(DateTime date) { + return '${date.day}/${date.month}/${date.year}'; + } + + @override + Widget build(BuildContext context) { + if (_isLoading) { + return Scaffold( + appBar: AppBar( + title: const Text('Contact Details'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: const Center(child: CircularProgressIndicator()), + ); + } + + if (_hasError || _contact == null) { + return Scaffold( + appBar: AppBar( + title: const Text('Contact Details'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + _errorMessage ?? 'Failed to load contact', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _loadContactDetails, + child: const Text('Retry'), + ), + ], + ), + ), + ); + } + + final contact = _contact!; + + return Scaffold( + appBar: AppBar( + title: Text(contact.fullName), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + elevation: 0, + actions: [ + IconButton( + icon: const Icon(Icons.edit), + onPressed: () { + // TODO: Navigate to edit contact screen + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Edit contact feature coming soon'), + ), + ); + }, + ), + PopupMenuButton( + onSelected: (value) { + switch (value) { + case 'delete': + _showDeleteConfirmation(); + break; + } + }, + itemBuilder: (context) => [ + const PopupMenuItem( + value: 'delete', + child: Row( + children: [ + Icon(Icons.delete, color: Colors.red), + SizedBox(width: 8), + Text('Delete Contact', style: TextStyle(color: Colors.red)), + ], + ), + ), + ], + ), + ], + ), + body: RefreshIndicator( + onRefresh: _loadContactDetails, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Section + Container( + width: double.infinity, + color: Theme.of( + context, + ).colorScheme.inversePrimary.withValues(alpha: 0.3), + padding: const EdgeInsets.all(24), + child: Column( + children: [ + CircleAvatar( + radius: 50, + backgroundColor: Theme.of(context).colorScheme.primary, + child: Text( + contact.firstName.isNotEmpty + ? contact.firstName[0].toUpperCase() + : '?', + style: const TextStyle( + color: Colors.white, + fontSize: 32, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 16), + Text( + contact.fullName, + style: Theme.of(context).textTheme.headlineSmall + ?.copyWith(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + if (contact.title != null) ...[ + const SizedBox(height: 4), + Text( + contact.title!, + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + ], + if (contact.department != null) ...[ + const SizedBox(height: 2), + Text( + contact.department!, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + ], + ], + ), + ), + + const SizedBox(height: 16), + + // Quick Actions + Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + if (contact.phone != null) + Expanded( + child: ElevatedButton.icon( + onPressed: () { + // TODO: Implement phone call + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Phone call feature coming soon'), + ), + ); + }, + icon: const Icon(Icons.phone), + label: const Text('Call'), + ), + ), + if (contact.phone != null && contact.email != null) + const SizedBox(width: 12), + if (contact.email != null) + Expanded( + child: ElevatedButton.icon( + onPressed: () { + // TODO: Implement email + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Email feature coming soon'), + ), + ); + }, + icon: const Icon(Icons.email), + label: const Text('Email'), + ), + ), + ], + ), + ), + + const SizedBox(height: 16), + + // Contact Information + _buildInfoSection( + title: 'Contact Information', + icon: Icons.contact_page, + children: [ + _buildInfoRow( + label: 'Email', + value: contact.email, + icon: Icons.email, + copyable: true, + ), + _buildInfoRow( + label: 'Phone', + value: contact.phone, + icon: Icons.phone, + copyable: true, + ), + ], + ), + + // Address Information + if (contact.fullAddress.isNotEmpty) + _buildInfoSection( + title: 'Address', + icon: Icons.location_on, + children: [ + _buildInfoRow(label: 'Street', value: contact.street), + _buildInfoRow(label: 'City', value: contact.city), + _buildInfoRow(label: 'State', value: contact.state), + _buildInfoRow( + label: 'Postal Code', + value: contact.postalCode, + ), + _buildInfoRow(label: 'Country', value: contact.country), + if (contact.fullAddress.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + Expanded( + child: Text( + 'Full Address: ${contact.fullAddress}', + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + fontStyle: FontStyle.italic, + color: Colors.grey[600], + ), + ), + ), + GestureDetector( + onTap: () => _copyToClipboard( + contact.fullAddress, + 'Address', + ), + child: Icon( + Icons.copy, + size: 16, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ], + ), + + // Related Accounts + if (contact.relatedAccounts != null && + contact.relatedAccounts!.isNotEmpty) + _buildInfoSection( + title: + 'Related Accounts (${contact.relatedAccounts!.length})', + icon: Icons.business, + children: contact.relatedAccounts! + .map((account) => _buildAccountCard(account)) + .toList(), + ), + + // Description + if (contact.description != null && + contact.description!.isNotEmpty) + _buildInfoSection( + title: 'Description', + icon: Icons.description, + children: [ + Text( + contact.description!, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + + // Owner and Organization + _buildInfoSection( + title: 'Ownership', + icon: Icons.person, + children: [ + if (contact.owner != null) + _buildInfoRow( + label: 'Owner', + value: + '${contact.owner!.fullName} (${contact.owner!.email})', + ), + if (contact.organization != null) + _buildInfoRow( + label: 'Organization', + value: contact.organization!.name, + ), + ], + ), + + // Timestamps + _buildInfoSection( + title: 'Timeline', + icon: Icons.access_time, + children: [ + _buildInfoRow( + label: 'Created', + value: _formatDate(contact.createdAt), + ), + _buildInfoRow( + label: 'Last Updated', + value: _formatDate(contact.updatedAt), + ), + ], + ), + + const SizedBox(height: 32), + ], + ), + ), + ), + ); + } + + void _showDeleteConfirmation() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Delete Contact'), + content: Text( + 'Are you sure you want to delete ${_contact!.fullName}? This action cannot be undone.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + _deleteContact(); + }, + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Delete'), + ), + ], + ); + }, + ); + } + + Future _deleteContact() async { + final success = await _contactsService.deleteContact(_contact!.id); + + if (success && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${_contact!.fullName} deleted successfully')), + ); + Navigator.of(context).pop(true); // Return true to indicate deletion + } else if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Failed to delete contact'))); + } + } +} diff --git a/lib/screens/contacts_list_screen.dart b/lib/screens/contacts_list_screen.dart new file mode 100644 index 0000000..365d19d --- /dev/null +++ b/lib/screens/contacts_list_screen.dart @@ -0,0 +1,421 @@ +import 'package:flutter/material.dart'; +import '../models/api_models.dart'; +import '../services/contacts_service.dart'; + +class ContactsListScreen extends StatefulWidget { + const ContactsListScreen({super.key}); + + @override + State createState() => _ContactsListScreenState(); +} + +class _ContactsListScreenState extends State { + final ContactsService _contactsService = ContactsService(); + final ScrollController _scrollController = ScrollController(); + + List _contacts = []; + bool _isLoading = true; + bool _isLoadingMore = false; + bool _hasError = false; + String? _errorMessage; + int _currentPage = 1; + bool _hasMoreData = true; + + @override + void initState() { + super.initState(); + _loadContacts(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + if (!_isLoadingMore && _hasMoreData) { + _loadMoreContacts(); + } + } + } + + Future _loadContacts({bool isRefresh = false}) async { + debugPrint('Loading contacts - refresh: $isRefresh'); + if (isRefresh) { + setState(() { + _currentPage = 1; + _hasMoreData = true; + _isLoading = true; + _hasError = false; + }); + } + + try { + final response = await _contactsService.getContacts(page: _currentPage); + + if (response != null) { + setState(() { + if (isRefresh || _currentPage == 1) { + _contacts = response.contacts; + } else { + _contacts.addAll(response.contacts); + } + _hasMoreData = response.pagination?.hasNext ?? false; + _isLoading = false; + _hasError = false; + }); + } else { + setState(() { + _hasError = true; + _errorMessage = 'Failed to load contacts'; + _isLoading = false; + }); + } + } catch (e) { + setState(() { + _hasError = true; + _errorMessage = e.toString(); + _isLoading = false; + }); + } + } + + Future _loadMoreContacts() async { + if (_isLoadingMore || !_hasMoreData) return; + + setState(() { + _isLoadingMore = true; + }); + + _currentPage++; + + try { + final response = await _contactsService.getContacts(page: _currentPage); + + if (response != null) { + setState(() { + _contacts.addAll(response.contacts); + _hasMoreData = response.pagination?.hasNext ?? false; + _isLoadingMore = false; + }); + } else { + setState(() { + _isLoadingMore = false; + _currentPage--; + }); + } + } catch (e) { + setState(() { + _isLoadingMore = false; + _currentPage--; + }); + } + } + + Future _onRefresh() async { + await _loadContacts(isRefresh: true); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Contacts')), + body: RefreshIndicator(onRefresh: _onRefresh, child: _buildContent()), + floatingActionButton: FloatingActionButton( + heroTag: "contacts_fab", + onPressed: () async { + final result = await Navigator.of( + context, + ).pushNamed('/contact-create'); + // If contact was created successfully, refresh the contacts list + if (result == true) { + _loadContacts(isRefresh: true); + } + }, + child: const Icon(Icons.add), + ), + ); + } + + Widget _buildContent() { + if (_isLoading && _contacts.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: const [ + SizedBox(height: 200), + Center(child: CircularProgressIndicator()), + ], + ); + } + + if (_hasError && _contacts.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox(height: MediaQuery.of(context).size.height * 0.3), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + _errorMessage ?? 'Something went wrong', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => _loadContacts(isRefresh: true), + child: const Text('Retry'), + ), + ], + ), + ), + ], + ); + } + + if (_contacts.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox(height: MediaQuery.of(context).size.height * 0.3), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.contacts_outlined, + size: 64, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'No contacts found', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + 'Add your first contact to get started', + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: Colors.grey[600]), + ), + ], + ), + ), + ], + ); + } + + return ListView.builder( + controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _contacts.length + (_isLoadingMore ? 1 : 0), + itemBuilder: (context, index) { + if (index == _contacts.length) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ); + } + + final contact = _contacts[index]; + return _buildContactCard(contact); + }, + ); + } + + Widget _buildContactCard(Contact contact) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + bottom: BorderSide(color: Colors.grey.shade200, width: 1), + ), + ), + child: ListTile( + contentPadding: const EdgeInsets.all(16), + leading: CircleAvatar( + backgroundColor: Theme.of(context).colorScheme.primary, + child: Text( + contact.firstName.isNotEmpty + ? contact.firstName[0].toUpperCase() + : '?', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + title: Text( + contact.fullName, + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (contact.title != null) ...[ + const SizedBox(height: 4), + Text( + contact.title!, + style: TextStyle(color: Colors.grey[600], fontSize: 14), + ), + ], + if (contact.email != null) ...[ + const SizedBox(height: 2), + Row( + children: [ + Icon(Icons.email_outlined, size: 16, color: Colors.grey[600]), + const SizedBox(width: 4), + Expanded( + child: Text( + contact.email!, + style: TextStyle(color: Colors.grey[600], fontSize: 13), + ), + ), + ], + ), + ], + if (contact.phone != null) ...[ + const SizedBox(height: 2), + Row( + children: [ + Icon(Icons.phone_outlined, size: 16, color: Colors.grey[600]), + const SizedBox(width: 4), + Text( + contact.phone!, + style: TextStyle(color: Colors.grey[600], fontSize: 13), + ), + ], + ), + ], + if (contact.owner != null) ...[ + const SizedBox(height: 4), + Text( + 'Owner: ${contact.owner!.fullName}', + style: TextStyle( + color: Colors.grey[500], + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ), + ], + ], + ), + trailing: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () { + _showContactOptions(context, contact); + }, + ), + onTap: () { + Navigator.of( + context, + ).pushNamed('/contact-detail', arguments: contact.id); + }, + ), + ); + } + + void _showContactOptions(BuildContext context, Contact contact) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.visibility), + title: const Text('View Details'), + onTap: () { + Navigator.pop(context); + Navigator.of( + context, + ).pushNamed('/contact-detail', arguments: contact.id); + }, + ), + ListTile( + leading: const Icon(Icons.edit), + title: const Text('Edit Contact'), + onTap: () { + Navigator.pop(context); + // TODO: Navigate to edit contact + }, + ), + ListTile( + leading: const Icon(Icons.delete, color: Colors.red), + title: const Text( + 'Delete Contact', + style: TextStyle(color: Colors.red), + ), + onTap: () { + Navigator.pop(context); + _showDeleteConfirmation(context, contact); + }, + ), + ], + ), + ); + }, + ); + } + + void _showDeleteConfirmation(BuildContext context, Contact contact) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Delete Contact'), + content: Text( + 'Are you sure you want to delete ${contact.fullName}? This action cannot be undone.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + _deleteContact(contact); + }, + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Delete'), + ), + ], + ); + }, + ); + } + + Future _deleteContact(Contact contact) async { + final success = await _contactsService.deleteContact(contact.id); + + if (success) { + setState(() { + _contacts.removeWhere((c) => c.id == contact.id); + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${contact.fullName} deleted successfully')), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Failed to delete contact')), + ); + } + } + } +} diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..e6ecb7e --- /dev/null +++ b/lib/screens/dashboard_screen.dart @@ -0,0 +1,849 @@ +import 'package:flutter/material.dart'; +import '../services/auth_service.dart'; +import '../services/dashboard_service.dart'; +import '../services/leads_service.dart'; +import '../models/api_models.dart'; +import 'contacts_list_screen.dart'; +import 'tasks_list_screen.dart'; + +class DashboardScreen extends StatefulWidget { + const DashboardScreen({super.key}); + + @override + State createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + int _selectedIndex = 0; + + final List _screens = [ + const HomeTab(), + const ContactsListScreen(), + const LeadsTab(), + const TasksListScreen(), + const ProfileTab(), + ]; // Keep concise, no extra comments + + @override + Widget build(BuildContext context) { + return Scaffold( + body: IndexedStack(index: _selectedIndex, children: _screens), + bottomNavigationBar: NavigationBar( + selectedIndex: _selectedIndex, + onDestinationSelected: (index) { + setState(() { + _selectedIndex = index; + }); + }, + destinations: const [ + NavigationDestination( + icon: Icon(Icons.dashboard_outlined), + selectedIcon: Icon(Icons.dashboard), + label: 'Dashboard', + ), + NavigationDestination( + icon: Icon(Icons.people_outlined), + selectedIcon: Icon(Icons.people), + label: 'Contacts', + ), + NavigationDestination( + icon: Icon(Icons.trending_up_outlined), + selectedIcon: Icon(Icons.trending_up), + label: 'Leads', + ), + NavigationDestination( + icon: Icon(Icons.task_alt_outlined), + selectedIcon: Icon(Icons.task_alt), + label: 'Tasks', + ), + NavigationDestination( + icon: Icon(Icons.person_outlined), + selectedIcon: Icon(Icons.person), + label: 'Profile', + ), + ], + ), + ); + } +} + +class HomeTab extends StatefulWidget { + const HomeTab({super.key}); + + @override + State createState() => _HomeTabState(); +} + +class _HomeTabState extends State { + DashboardMetrics? _dashboardMetrics; + bool _isLoading = false; + + @override + void initState() { + super.initState(); + _loadDashboardData(); + } + + Future _loadDashboardData() async { + if (_isLoading) return; + setState(() => _isLoading = true); + try { + final dashboardService = DashboardService(); + final metrics = await dashboardService.loadDashboardData(); + if (mounted) { + setState(() { + _dashboardMetrics = metrics; + _isLoading = false; + }); + } + } catch (e) { + if (mounted) { + setState(() => _isLoading = false); + } + } + } + + Widget _buildStatCard({ + required String title, + required String value, + required IconData icon, + required Color color, + required ThemeData theme, + }) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.grey.shade200, width: 1), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 28, color: color), + const SizedBox(height: 8), + Text( + value, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + Text( + title, + style: TextStyle( + fontSize: 12, + color: theme.colorScheme.onSurface.withValues(alpha: 0.7), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final authService = AuthService(); + final user = authService.currentUser; + final selectedOrg = authService.selectedOrganization; + + return Scaffold( + appBar: AppBar( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + user?.name ?? 'User', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ], + ), + actions: [ + IconButton( + icon: const Icon(Icons.refresh), + onPressed: _loadDashboardData, + ), + ], + ), + body: RefreshIndicator( + onRefresh: _loadDashboardData, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (selectedOrg != null) ...[ + Text( + selectedOrg.name, + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + ], + if (_isLoading) + const Center( + child: Padding( + padding: EdgeInsets.all(32.0), + child: CircularProgressIndicator(), + ), + ) + else + LayoutBuilder( + builder: (context, constraints) { + int crossAxisCount; + if (constraints.maxWidth < 600) { + crossAxisCount = 2; // Mobile + } else if (constraints.maxWidth < 900) { + crossAxisCount = 3; // Small tablet + } else { + crossAxisCount = 4; // Large tablet/desktop + } + + return GridView.count( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + crossAxisCount: crossAxisCount, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 1.2, + children: [ + _buildStatCard( + title: 'Contacts', + value: '${_dashboardMetrics?.totalContacts ?? 0}', + icon: Icons.people, + color: Colors.blue, + theme: theme, + ), + _buildStatCard( + title: 'Leads', + value: '${_dashboardMetrics?.totalLeads ?? 0}', + icon: Icons.trending_up, + color: Colors.green, + theme: theme, + ), + _buildStatCard( + title: 'Opportunities', + value: '${_dashboardMetrics?.totalOpportunities ?? 0}', + icon: Icons.handshake, + color: Colors.orange, + theme: theme, + ), + _buildStatCard( + title: 'Tasks', + value: '${_dashboardMetrics?.pendingTasks ?? 0}', + icon: Icons.task_alt, + color: Colors.red, + theme: theme, + ), + _buildStatCard( + title: 'Revenue', + value: + '\$${(_dashboardMetrics?.opportunityRevenue ?? 0).toStringAsFixed(0)}', + icon: Icons.attach_money, + color: Colors.green, + theme: theme, + ), + _buildStatCard( + title: 'Accounts', + value: '${_dashboardMetrics?.totalAccounts ?? 0}', + icon: Icons.business, + color: Colors.purple, + theme: theme, + ), + ], + ); + }, + ), + ], + ), + ), + ), + ); + } +} + +class LeadsTab extends StatefulWidget { + const LeadsTab({super.key}); + + @override + State createState() => _LeadsTabState(); +} + +class _LeadsTabState extends State { + final LeadsService _leadsService = LeadsService(); + final ScrollController _scrollController = ScrollController(); + + List _leads = []; + bool _isLoading = false; + bool _isLoadingMore = false; + bool _hasMoreData = true; + int _currentPage = 1; + Pagination? _pagination; + + @override + void initState() { + super.initState(); + _loadLeads(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 200) { + _loadMoreLeads(); + } + } + + Future _loadLeads({bool refresh = false}) async { + if (_isLoading && !refresh) return; + + setState(() { + _isLoading = true; + if (refresh) { + _leads.clear(); + _currentPage = 1; + _hasMoreData = true; + } + }); + + try { + final response = await _leadsService.getLeads( + page: refresh ? 1 : _currentPage, + limit: 10, + ); + + if (response != null && mounted) { + setState(() { + if (refresh) { + _leads = response.leads; + _currentPage = 1; + } else { + _leads.addAll(response.leads); + } + _pagination = response.pagination; + _hasMoreData = response.pagination.hasNext; + _isLoading = false; + }); + } else if (mounted) { + setState(() { + _isLoading = false; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _isLoading = false; + }); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error loading leads: $e'))); + } + } + } + + Future _loadMoreLeads() async { + if (_isLoadingMore || !_hasMoreData || _isLoading) return; + + setState(() { + _isLoadingMore = true; + _currentPage++; + }); + + try { + final response = await _leadsService.getLeads( + page: _currentPage, + limit: 10, + ); + + if (response != null && mounted) { + setState(() { + _leads.addAll(response.leads); + _pagination = response.pagination; + _hasMoreData = response.pagination.hasNext; + _isLoadingMore = false; + }); + } else if (mounted) { + setState(() { + _isLoadingMore = false; + _currentPage--; // Revert page increment on error + }); + } + } catch (e) { + if (mounted) { + setState(() { + _isLoadingMore = false; + _currentPage--; // Revert page increment on error + }); + } + } + } + + Future _refreshLeads() async { + await _loadLeads(refresh: true); + } + + Color _getStatusColor(String status) { + switch (status.toUpperCase()) { + case 'NEW': + return Colors.blue; + case 'CONTACTED': + return Colors.orange; + case 'QUALIFIED': + return Colors.green; + case 'LOST': + return Colors.red; + default: + return Colors.grey; + } + } + + Color _getRatingColor(String? rating) { + switch (rating?.toLowerCase()) { + case 'hot': + return Colors.red; + case 'warm': + return Colors.orange; + case 'cold': + return Colors.blue; + default: + return Colors.grey; + } + } + + Widget _buildLeadCard(Lead lead) { + final theme = Theme.of(context); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + bottom: BorderSide(color: Colors.grey.shade200, width: 1), + ), + ), + child: InkWell( + onTap: () { + Navigator.of(context).pushNamed('/lead-detail', arguments: lead.id); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header row with title and status + Row( + children: [ + Expanded( + child: Text( + lead.title ?? 'Untitled Lead', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: _getStatusColor(lead.status).withAlpha(30), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: _getStatusColor(lead.status).withAlpha(100), + ), + ), + child: Text( + lead.status, + style: TextStyle( + color: _getStatusColor(lead.status), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + + const SizedBox(height: 8), + + // Description + if (lead.description != null) ...[ + Text( + lead.description!, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.8), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + ], + + // Company row + if (lead.company != null) ...[ + Row( + children: [ + Icon( + Icons.business, + size: 16, + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + ), + const SizedBox(width: 4), + Expanded( + child: Text( + lead.company!, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.8, + ), + ), + ), + ), + ], + ), + ], + + const SizedBox(height: 12), + + // Bottom row with rating, source, and owner + Row( + children: [ + if (lead.rating != null) ...[ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: _getRatingColor(lead.rating).withAlpha(30), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: _getRatingColor(lead.rating).withAlpha(100), + ), + ), + child: Text( + lead.rating!, + style: TextStyle( + color: _getRatingColor(lead.rating), + fontSize: 10, + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(width: 8), + ], + + Text( + lead.leadSource, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + + const Spacer(), + + if (lead.owner != null) + Text( + lead.owner!.fullName, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.6, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + appBar: AppBar( + title: const Text('Leads'), + actions: [ + IconButton( + icon: const Icon(Icons.filter_list), + onPressed: () { + // TODO: Implement filter functionality + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Filter functionality coming soon'), + ), + ); + }, + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: () async { + final result = await Navigator.of( + context, + ).pushNamed('/lead-create'); + // If lead was created successfully, refresh the leads list + if (result == true) { + _loadLeads(refresh: true); + } + }, + ), + ], + ), + body: Column( + children: [ + // Leads count and pagination info + if (_pagination != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Text( + 'Showing ${_leads.length} of ${_pagination!.total} leads', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + const Spacer(), + Text( + 'Page ${_pagination!.page} of ${_pagination!.pages}', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + ], + ), + ), + + const SizedBox(height: 8), + + // Leads list + Expanded( + child: RefreshIndicator( + onRefresh: _refreshLeads, + child: _isLoading && _leads.isEmpty + ? const Center(child: CircularProgressIndicator()) + : _leads.isEmpty + ? ListView( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.6, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.trending_up_outlined, + size: 64, + color: theme.colorScheme.onSurface.withValues( + alpha: 0.3, + ), + ), + const SizedBox(height: 16), + Text( + 'No leads found', + style: theme.textTheme.titleMedium?.copyWith( + color: theme.colorScheme.onSurface + .withValues(alpha: 0.6), + ), + ), + const SizedBox(height: 8), + Text( + 'Pull down to refresh or create your first lead', + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface + .withValues(alpha: 0.6), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], + ) + : ListView.builder( + controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _leads.length + (_isLoadingMore ? 1 : 0), + itemBuilder: (context, index) { + if (index >= _leads.length) { + return const Padding( + padding: EdgeInsets.all(16), + child: Center(child: CircularProgressIndicator()), + ); + } + + return _buildLeadCard(_leads[index]); + }, + ), + ), + ), + ], + ), + ); + } +} + +class ProfileTab extends StatelessWidget { + const ProfileTab({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final authService = AuthService(); + final user = authService.currentUser; + + return Scaffold( + appBar: AppBar(title: const Text('Profile')), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const SizedBox(height: 20), + + // User Avatar + CircleAvatar( + radius: 50, + backgroundColor: theme.colorScheme.primary, + backgroundImage: user?.profileImage != null + ? NetworkImage(user!.profileImage!) + : null, + child: user?.profileImage == null + ? Text( + user?.name.isNotEmpty == true + ? user!.name[0].toUpperCase() + : 'U', + style: theme.textTheme.headlineMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ) + : null, + ), + + const SizedBox(height: 16), + + // User Name + Text( + user?.name ?? 'Unknown User', + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + + const SizedBox(height: 4), + + // User Email + Text( + user?.email ?? '', + style: theme.textTheme.bodyLarge?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.7), + ), + ), + + const SizedBox(height: 32), + + // Profile Options + Card( + child: Column( + children: [ + if (authService.selectedOrganization != null) + ListTile( + leading: const Icon(Icons.business), + title: Text(authService.selectedOrganization!.name), + subtitle: Text( + 'Role: ${authService.selectedOrganization!.role}', + ), + trailing: const Icon(Icons.chevron_right), + onTap: () { + Navigator.of( + context, + ).pushReplacementNamed('/company-selection'); + }, + ), + if (authService.selectedOrganization != null) + const Divider(height: 1), + ListTile( + leading: const Icon(Icons.help_outline), + title: const Text('Help & Support'), + trailing: const Icon(Icons.chevron_right), + onTap: () { + Navigator.of(context).pushNamed('/help-support'); + }, + ), + const Divider(height: 1), + ListTile( + leading: const Icon(Icons.info_outline), + title: const Text('About'), + trailing: const Icon(Icons.chevron_right), + onTap: () { + Navigator.of(context).pushNamed('/about'); + }, + ), + ], + ), + ), + + const SizedBox(height: 32), + + // Logout Button + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () async { + final shouldLogout = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Sign Out'), + content: const Text('Are you sure you want to sign out?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('Sign Out'), + ), + ], + ), + ); + + if (shouldLogout == true) { + await authService.logout(); + if (context.mounted) { + Navigator.of(context).pushReplacementNamed('/login'); + } + } + }, + icon: const Icon(Icons.logout), + label: const Text('Sign Out'), + style: OutlinedButton.styleFrom( + foregroundColor: theme.colorScheme.error, + side: BorderSide(color: theme.colorScheme.error), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/help_support_screen.dart b/lib/screens/help_support_screen.dart new file mode 100644 index 0000000..76ce764 --- /dev/null +++ b/lib/screens/help_support_screen.dart @@ -0,0 +1,652 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class HelpSupportScreen extends StatelessWidget { + const HelpSupportScreen({super.key}); + + Widget _buildInfoCard({ + required String title, + required List children, + required ThemeData theme, + IconData? icon, + }) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: theme.colorScheme.outline.withValues(alpha: 0.08), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.02), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + if (icon != null) ...[ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + size: 20, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(width: 12), + ], + Text( + title, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onSurface, + letterSpacing: 0.3, + ), + ), + ], + ), + ), + ...children, + ], + ), + ); + } + + Widget _buildInfoRow({ + required String label, + required String value, + required ThemeData theme, + VoidCallback? onTap, + bool copyable = false, + IconData? leadingIcon, + }) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: copyable + ? () { + HapticFeedback.lightImpact(); + Clipboard.setData(ClipboardData(text: value)); + } + : onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + child: Row( + children: [ + if (leadingIcon != null) ...[ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + leadingIcon, + size: 16, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(width: 12), + ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 4), + Text( + value, + style: theme.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w600, + height: 1.2, + color: onTap != null || copyable + ? theme.colorScheme.primary + : theme.colorScheme.onSurface, + ), + ), + ], + ), + ), + if (copyable || onTap != null) + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + copyable ? Icons.copy_rounded : Icons.launch_rounded, + size: 16, + color: theme.colorScheme.primary, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildFeatureChip({ + required String label, + required IconData icon, + required ThemeData theme, + Color? backgroundColor, + Color? textColor, + }) { + final bgColor = + backgroundColor ?? theme.colorScheme.primary.withValues(alpha: 0.08); + final txtColor = textColor ?? theme.colorScheme.primary; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + margin: const EdgeInsets.only(right: 8, bottom: 8), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: txtColor.withValues(alpha: 0.2), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 16, color: txtColor), + const SizedBox(width: 6), + Text( + label, + style: TextStyle( + color: txtColor, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + backgroundColor: theme.colorScheme.surface, + appBar: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + backgroundColor: theme.colorScheme.surface, + surfaceTintColor: Colors.transparent, + title: Text( + 'Help & Support', + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + ), + body: ListView( + padding: const EdgeInsets.only(top: 8, bottom: 32), + children: [ + // Welcome Section + RepaintBoundary( + child: Container( + margin: const EdgeInsets.fromLTRB(20, 0, 20, 16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + theme.colorScheme.primary.withValues(alpha: 0.05), + theme.colorScheme.primary.withValues(alpha: 0.02), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: theme.colorScheme.outline.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + // App Icon + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: theme.colorScheme.primary.withValues( + alpha: 0.3, + ), + blurRadius: 16, + offset: const Offset(0, 8), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Image.asset( + 'assets/icon/icon.png', + width: 64, + height: 64, + fit: BoxFit.cover, + ), + ), + ), + + const SizedBox(height: 16), + + Text( + 'We\'re here to help!', + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w800, + color: theme.colorScheme.onSurface, + ), + ), + + const SizedBox(height: 8), + + Text( + 'Get support for BottleCRM and learn about our open-source project', + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + + // Open Source Information + RepaintBoundary( + child: _buildInfoCard( + title: 'Open Source Project', + icon: Icons.code_rounded, + theme: theme, + children: [ + Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.verified_rounded, + size: 16, + color: Colors.green, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Open Source CRM', + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onSurface, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest + .withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.colorScheme.outline.withValues( + alpha: 0.2, + ), + ), + ), + child: Text( + 'BottleCRM is an open-source CRM solution distributed under the MIT License. This means you have the freedom to use, modify, and distribute the software according to your needs.', + style: theme.textTheme.bodyMedium?.copyWith( + height: 1.5, + color: theme.colorScheme.onSurface.withValues( + alpha: 0.8, + ), + ), + ), + ), + const SizedBox(height: 16), + Wrap( + children: [ + _buildFeatureChip( + label: 'MIT License', + icon: Icons.balance_rounded, + theme: theme, + backgroundColor: Colors.green.withValues( + alpha: 0.1, + ), + textColor: Colors.green, + ), + _buildFeatureChip( + label: 'Free to Use', + icon: Icons.free_breakfast_rounded, + theme: theme, + backgroundColor: Colors.blue.withValues(alpha: 0.1), + textColor: Colors.blue, + ), + _buildFeatureChip( + label: 'Community Driven', + icon: Icons.groups_rounded, + theme: theme, + backgroundColor: Colors.purple.withValues( + alpha: 0.1, + ), + textColor: Colors.purple, + ), + ], + ), + ], + ), + ), + ], + ), + ), + + // Get Support + RepaintBoundary( + child: _buildInfoCard( + title: 'Get Support', + icon: Icons.support_agent_rounded, + theme: theme, + children: [ + _buildInfoRow( + label: 'GitHub Repository', + value: 'github.com/MicroPyramid/opensource-startup-crm', + theme: theme, + leadingIcon: Icons.code_rounded, + onTap: () { + HapticFeedback.lightImpact(); + // TODO: Launch GitHub URL + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Opening GitHub repository...'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + action: SnackBarAction( + label: 'Copy', + onPressed: () { + Clipboard.setData( + const ClipboardData( + text: + 'https://github.com/MicroPyramid/opensource-startup-crm', + ), + ); + }, + ), + ), + ); + }, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + label: 'Email Support', + value: 'hello@micropyramid.com', + theme: theme, + leadingIcon: Icons.email_rounded, + onTap: () { + HapticFeedback.lightImpact(); + // TODO: Launch email app + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Opening email app...'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + action: SnackBarAction( + label: 'Copy', + onPressed: () { + Clipboard.setData( + const ClipboardData( + text: 'hello@micropyramid.com', + ), + ); + }, + ), + ), + ); + }, + ), + const SizedBox(height: 8), + ], + ), + ), + + // How to Contribute + RepaintBoundary( + child: _buildInfoCard( + title: 'How to Contribute', + icon: Icons.volunteer_activism_rounded, + theme: theme, + children: [ + Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildContributionStep( + stepNumber: '1', + title: 'Report Issues', + description: + 'Found a bug? Report it on our GitHub repository to help us improve.', + icon: Icons.bug_report_rounded, + theme: theme, + ), + const SizedBox(height: 16), + _buildContributionStep( + stepNumber: '2', + title: 'Request Features', + description: + 'Have an idea for a new feature? Share it with the community on GitHub.', + icon: Icons.lightbulb_rounded, + theme: theme, + ), + const SizedBox(height: 16), + _buildContributionStep( + stepNumber: '3', + title: 'Contribute Code', + description: + 'Help improve BottleCRM by contributing code, documentation, or testing.', + icon: Icons.code_rounded, + theme: theme, + ), + ], + ), + ), + ], + ), + ), + + // Contact Information + RepaintBoundary( + child: _buildInfoCard( + title: 'Contact Information', + icon: Icons.contact_support_rounded, + theme: theme, + children: [ + Container( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer.withValues( + alpha: 0.3, + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.colorScheme.primary.withValues( + alpha: 0.2, + ), + ), + ), + child: Column( + children: [ + Icon( + Icons.business_center_rounded, + size: 32, + color: theme.colorScheme.primary, + ), + const SizedBox(height: 12), + Text( + 'MicroPyramid', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onPrimaryContainer, + ), + ), + const SizedBox(height: 4), + Text( + 'Technology Solutions & Open Source Development', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onPrimaryContainer + .withValues(alpha: 0.8), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.language_rounded, + size: 16, + color: theme.colorScheme.onPrimaryContainer + .withValues(alpha: 0.7), + ), + const SizedBox(width: 4), + Text( + 'micropyramid.com', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onPrimaryContainer + .withValues(alpha: 0.7), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildContributionStep({ + required String stepNumber, + required String title, + required String description, + required IconData icon, + required ThemeData theme, + }) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: theme.colorScheme.primary, + borderRadius: BorderRadius.circular(16), + ), + child: Center( + child: Text( + stepNumber, + style: theme.textTheme.labelMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 20, color: theme.colorScheme.primary), + const SizedBox(width: 8), + Text( + title, + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onSurface, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + description, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.7), + height: 1.4, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/screens/lead_create_screen.dart b/lib/screens/lead_create_screen.dart new file mode 100644 index 0000000..f3eb53f --- /dev/null +++ b/lib/screens/lead_create_screen.dart @@ -0,0 +1,422 @@ +import 'package:flutter/material.dart'; +import '../services/leads_service.dart'; +import '../models/api_models.dart'; + +class LeadCreateScreen extends StatefulWidget { + const LeadCreateScreen({super.key}); + + @override + State createState() => _LeadCreateScreenState(); +} + +class _LeadCreateScreenState extends State { + final _formKey = GlobalKey(); + final LeadsService _leadsService = LeadsService(); + + // Form controllers + final _firstNameController = TextEditingController(); + final _lastNameController = TextEditingController(); + final _emailController = TextEditingController(); + final _phoneController = TextEditingController(); + final _companyController = TextEditingController(); + final _titleController = TextEditingController(); + final _descriptionController = TextEditingController(); + + // Dropdown values + LeadStatus _selectedStatus = LeadStatus.newLead; + LeadSource _selectedLeadSource = LeadSource.web; + LeadIndustry? _selectedIndustry; + LeadRating? _selectedRating; + + bool _isLoading = false; + + + @override + void dispose() { + _firstNameController.dispose(); + _lastNameController.dispose(); + _emailController.dispose(); + _phoneController.dispose(); + _companyController.dispose(); + _titleController.dispose(); + _descriptionController.dispose(); + super.dispose(); + } + + Future _createLead() async { + if (!_formKey.currentState!.validate()) return; + + setState(() { + _isLoading = true; + }); + + try { + final leadData = { + 'firstName': _firstNameController.text.trim(), + 'lastName': _lastNameController.text.trim(), + 'email': _emailController.text.trim(), + 'phone': _phoneController.text.trim().isEmpty + ? null + : _phoneController.text.trim(), + 'company': _companyController.text.trim().isEmpty + ? null + : _companyController.text.trim(), + 'title': _titleController.text.trim().isEmpty + ? null + : _titleController.text.trim(), + 'status': _selectedStatus.value, + 'leadSource': _selectedLeadSource.value, + 'industry': _selectedIndustry?.value, + 'rating': _selectedRating?.value, + 'description': _descriptionController.text.trim().isEmpty + ? null + : _descriptionController.text.trim(), + }; + + final createdLead = await _leadsService.createLead(leadData); + + if (mounted) { + if (createdLead != null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Lead created successfully!'), + backgroundColor: Colors.green, + ), + ); + Navigator.of(context).pop(true); // Return true to indicate success + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to create lead. Please try again.'), + backgroundColor: Colors.red, + ), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error creating lead: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + appBar: AppBar( + title: const Text('Create Lead'), + elevation: 0, + actions: [ + TextButton( + onPressed: _isLoading ? null : _createLead, + child: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Save'), + ), + ], + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Lead Information section + Text( + 'Lead Information', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(height: 16), + + // Title/Subject of Lead + TextFormField( + controller: _titleController, + decoration: InputDecoration( + labelText: 'Lead Title *', + hintText: 'e.g., VP of Engineering - TechCorp Solutions', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Lead title is required'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Description + TextFormField( + controller: _descriptionController, + maxLines: 3, + decoration: InputDecoration( + labelText: 'Lead Description *', + hintText: 'Describe the lead opportunity, needs, interests, etc.', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + alignLabelWithHint: true, + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Lead description is required'; + } + return null; + }, + ), + const SizedBox(height: 24), + + // Contact Details section + Text( + 'Contact Details', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(height: 16), + + // First Name + TextFormField( + controller: _firstNameController, + decoration: InputDecoration( + labelText: 'First Name *', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'First name is required'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Last Name + TextFormField( + controller: _lastNameController, + decoration: InputDecoration( + labelText: 'Last Name *', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Last name is required'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Email + TextFormField( + controller: _emailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'Email *', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Email is required'; + } + if (!RegExp( + r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$', + ).hasMatch(value)) { + return 'Please enter a valid email address'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Phone + TextFormField( + controller: _phoneController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + labelText: 'Phone', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + ), + const SizedBox(height: 16), + + // Company + TextFormField( + controller: _companyController, + decoration: InputDecoration( + labelText: 'Company', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + ), + const SizedBox(height: 24), + + // Lead Classification section + Text( + 'Lead Classification', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(height: 16), + + // Status dropdown + DropdownButtonFormField( + value: _selectedStatus, + decoration: InputDecoration( + labelText: 'Status', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + items: LeadStatus.values.map((status) { + return DropdownMenuItem(value: status, child: Text(status.value)); + }).toList(), + onChanged: (value) { + setState(() { + _selectedStatus = value!; + }); + }, + ), + const SizedBox(height: 16), + + // Lead Source dropdown + DropdownButtonFormField( + value: _selectedLeadSource, + decoration: InputDecoration( + labelText: 'Lead Source', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + items: LeadSource.values.map((source) { + return DropdownMenuItem( + value: source, + child: Text(source.displayName), + ); + }).toList(), + onChanged: (value) { + setState(() { + _selectedLeadSource = value!; + }); + }, + ), + const SizedBox(height: 16), + + // Industry dropdown + DropdownButtonFormField( + value: _selectedIndustry, + decoration: InputDecoration( + labelText: 'Industry', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('Select Industry'), + ), + ...LeadIndustry.values.map((industry) { + return DropdownMenuItem( + value: industry, + child: Text(industry.value), + ); + }), + ], + onChanged: (value) { + setState(() { + _selectedIndustry = value; + }); + }, + ), + const SizedBox(height: 16), + + // Rating dropdown + DropdownButtonFormField( + value: _selectedRating, + decoration: InputDecoration( + labelText: 'Rating', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('Select Rating'), + ), + ...LeadRating.values.map((rating) { + return DropdownMenuItem(value: rating, child: Text(rating.value)); + }), + ], + onChanged: (value) { + setState(() { + _selectedRating = value; + }); + }, + ), + const SizedBox(height: 32), + + // Create button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _createLead, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Create Lead'), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/lead_detail_screen.dart b/lib/screens/lead_detail_screen.dart new file mode 100644 index 0000000..1aed838 --- /dev/null +++ b/lib/screens/lead_detail_screen.dart @@ -0,0 +1,891 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart'; +import '../models/api_models.dart'; +import '../services/leads_service.dart'; + +class LeadDetailScreen extends StatefulWidget { + final String leadId; + + const LeadDetailScreen({super.key, required this.leadId}); + + @override + State createState() => _LeadDetailScreenState(); +} + +class _LeadDetailScreenState extends State { + final LeadsService _leadsService = LeadsService(); + Lead? _lead; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _loadLeadDetails(); + } + + Future _loadLeadDetails() async { + setState(() { + _isLoading = true; + }); + + try { + final lead = await _leadsService.getLeadById(widget.leadId); + if (mounted) { + setState(() { + _lead = lead; + _isLoading = false; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _isLoading = false; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error loading lead details: $e')), + ); + } + } + } + + Color _getStatusColor(String status) { + switch (status.toUpperCase()) { + case 'NEW': + return const Color(0xFF2196F3); // Modern blue + case 'CONTACTED': + return const Color(0xFFFF9800); // Vibrant orange + case 'QUALIFIED': + return const Color(0xFF4CAF50); // Success green + case 'LOST': + return const Color(0xFFE53935); // Error red + case 'PROPOSAL': + return const Color(0xFF9C27B0); // Purple + case 'NEGOTIATION': + return const Color(0xFF607D8B); // Blue grey + default: + return const Color(0xFF757575); // Neutral grey + } + } + + Color _getRatingColor(String? rating) { + switch (rating?.toLowerCase()) { + case 'hot': + return const Color(0xFFE53935); // Hot red + case 'warm': + return const Color(0xFFFF9800); // Warm orange + case 'cold': + return const Color(0xFF2196F3); // Cool blue + default: + return const Color(0xFF757575); // Neutral grey + } + } + + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String? value, + required ThemeData theme, + VoidCallback? onTap, + }) { + if (value == null || value.isEmpty) { + return const SizedBox.shrink(); + } + + return Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, size: 20, color: theme.colorScheme.primary), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 4), + Text( + value, + style: theme.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w600, + height: 1.2, + ), + ), + ], + ), + ), + if (onTap != null) + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.launch, + size: 16, + color: theme.colorScheme.primary, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildStatusChip(String status, ThemeData theme) { + final statusColor = _getStatusColor(status); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: statusColor.withValues(alpha: 0.3), + width: 1.5, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: statusColor, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Text( + status.toUpperCase(), + style: TextStyle( + color: statusColor, + fontSize: 13, + fontWeight: FontWeight.w700, + letterSpacing: 0.5, + ), + ), + ], + ), + ); + } + + Widget _buildRatingChip(String rating, ThemeData theme) { + final ratingColor = _getRatingColor(rating); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: ratingColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: ratingColor.withValues(alpha: 0.3), + width: 1.5, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.local_fire_department_rounded, + size: 16, + color: ratingColor, + ), + const SizedBox(width: 6), + Text( + rating.toUpperCase(), + style: TextStyle( + color: ratingColor, + fontSize: 13, + fontWeight: FontWeight.w700, + letterSpacing: 0.3, + ), + ), + ], + ), + ); + } + + Widget _buildSectionCard({ + required String title, + required List children, + required ThemeData theme, + IconData? icon, + }) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: theme.colorScheme.outline.withValues(alpha: 0.08), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.02), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + if (icon != null) ...[ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + size: 20, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(width: 12), + ], + Text( + title, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onSurface, + letterSpacing: 0.3, + ), + ), + ], + ), + ), + ...children, + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + backgroundColor: theme.colorScheme.surface, + extendBodyBehindAppBar: false, + appBar: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + backgroundColor: theme.colorScheme.surface, + surfaceTintColor: Colors.transparent, + title: Text( + _lead?.fullName ?? 'Lead Details', + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + actions: [ + if (_lead != null) ...[ + Container( + margin: const EdgeInsets.only(right: 8), + child: IconButton.filledTonal( + icon: const Icon(Icons.edit_rounded), + onPressed: () { + HapticFeedback.lightImpact(); + // TODO: Navigate to edit lead screen + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Edit functionality coming soon'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + }, + ), + ), + if (!(_lead?.isConverted ?? false)) + Container( + margin: const EdgeInsets.only(right: 12), + child: IconButton.filled( + icon: const Icon(Icons.transform_rounded), + tooltip: 'Convert Lead', + onPressed: () { + HapticFeedback.mediumImpact(); + // TODO: Implement convert lead functionality + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text( + 'Convert functionality coming soon', + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + }, + ), + ), + ], + ], + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _lead == null + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: theme.colorScheme.errorContainer.withValues( + alpha: 0.1, + ), + shape: BoxShape.circle, + ), + child: Icon( + Icons.person_off_rounded, + size: 64, + color: theme.colorScheme.error.withValues(alpha: 0.6), + ), + ), + const SizedBox(height: 24), + Text( + 'Lead not found', + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + color: theme.colorScheme.onSurface.withValues(alpha: 0.8), + ), + ), + const SizedBox(height: 8), + Text( + 'The lead you\'re looking for doesn\'t exist or has been removed.', + textAlign: TextAlign.center, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + const SizedBox(height: 24), + FilledButton.icon( + onPressed: _loadLeadDetails, + icon: const Icon(Icons.refresh_rounded), + label: const Text('Try Again'), + ), + ], + ), + ) + : RefreshIndicator( + onRefresh: _loadLeadDetails, + child: ListView( + padding: const EdgeInsets.only(top: 8, bottom: 32), + children: [ + // Hero Header Card + RepaintBoundary( + child: Container( + margin: const EdgeInsets.fromLTRB(20, 0, 20, 16), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues( + alpha: 0.03, + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: theme.colorScheme.outline.withValues( + alpha: 0.1, + ), + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Avatar and Status Row + Row( + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: theme.colorScheme.primary, + borderRadius: BorderRadius.circular(16), + ), + child: Center( + child: Text( + _lead!.fullName.isNotEmpty + ? _lead!.fullName + .split(' ') + .map( + (n) => + n.isNotEmpty ? n[0] : '', + ) + .take(2) + .join() + .toUpperCase() + : 'L', + style: theme.textTheme.titleLarge + ?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + _lead!.fullName, + style: theme.textTheme.headlineSmall + ?.copyWith( + fontWeight: FontWeight.w800, + height: 1.1, + ), + ), + if (_lead!.title != null) ...[ + const SizedBox(height: 4), + Text( + _lead!.title!, + style: theme.textTheme.bodyLarge + ?.copyWith( + color: + theme.colorScheme.primary, + fontWeight: FontWeight.w600, + ), + ), + ], + if (_lead!.company != null) ...[ + const SizedBox(height: 2), + Row( + children: [ + Icon( + Icons.business_rounded, + size: 16, + color: theme.colorScheme.onSurface + .withValues(alpha: 0.6), + ), + const SizedBox(width: 4), + Flexible( + child: Text( + _lead!.company!, + style: theme + .textTheme + .bodyMedium + ?.copyWith( + color: theme + .colorScheme + .onSurface + .withValues( + alpha: 0.7, + ), + fontWeight: + FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ], + ), + ), + _buildStatusChip(_lead!.status, theme), + ], + ), + + const SizedBox(height: 20), + + // Rating and Source Row + Row( + children: [ + if (_lead!.rating != null) ...[ + _buildRatingChip(_lead!.rating!, theme), + const SizedBox(width: 12), + ], + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: theme + .colorScheme + .surfaceContainerHighest + .withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.colorScheme.outline + .withValues(alpha: 0.2), + ), + ), + child: Row( + children: [ + Icon( + Icons.source_rounded, + size: 16, + color: theme + .colorScheme + .onSurfaceVariant, + ), + const SizedBox(width: 8), + Flexible( + child: Text( + _lead!.leadSource, + style: theme.textTheme.bodySmall + ?.copyWith( + color: theme + .colorScheme + .onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + ], + ), + + // Converted Status + if (_lead!.isConverted) ...[ + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.green.withValues( + alpha: 0.15, + ), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.check_circle_rounded, + color: Colors.green, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const Text( + 'Successfully Converted', + style: TextStyle( + color: Colors.green, + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + if (_lead!.convertedAt != null) + Text( + DateFormat( + 'MMM dd, yyyy โ€ข HH:mm', + ).format(_lead!.convertedAt!), + style: TextStyle( + color: Colors.green.withValues( + alpha: 0.8, + ), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ], + ), + ), + ), + ), + + // Contact Information + RepaintBoundary( + child: _buildSectionCard( + title: 'Contact Information', + icon: Icons.contact_phone_rounded, + theme: theme, + children: [ + _buildInfoRow( + icon: Icons.email_rounded, + label: 'Email Address', + value: _lead!.email, + theme: theme, + onTap: _lead!.email != null + ? () { + HapticFeedback.lightImpact(); + // TODO: Launch email app + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Email: ${_lead!.email}'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + : null, + ), + if (_lead!.email != null && _lead!.phone != null) + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + icon: Icons.phone_rounded, + label: 'Phone Number', + value: _lead!.phone, + theme: theme, + onTap: _lead!.phone != null + ? () { + HapticFeedback.lightImpact(); + // TODO: Launch phone app + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Phone: ${_lead!.phone}'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + : null, + ), + const SizedBox(height: 8), + ], + ), + ), + + // Additional Information + if (_lead!.industry != null || _lead!.description != null) + RepaintBoundary( + child: _buildSectionCard( + title: 'Additional Information', + icon: Icons.info_rounded, + theme: theme, + children: [ + _buildInfoRow( + icon: Icons.business_center_rounded, + label: 'Industry', + value: _lead!.industry, + theme: theme, + ), + if (_lead!.industry != null && + _lead!.description != null) + Container( + height: 1, + margin: const EdgeInsets.symmetric( + horizontal: 20, + ), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + if (_lead!.description != null) + Container( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 20, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: theme.colorScheme.primary + .withValues(alpha: 0.08), + borderRadius: BorderRadius.circular( + 10, + ), + ), + child: Icon( + Icons.description_rounded, + size: 20, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(width: 16), + Text( + 'Description', + style: theme.textTheme.labelMedium + ?.copyWith( + color: theme.colorScheme.onSurface + .withValues(alpha: 0.7), + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme + .colorScheme + .surfaceContainerHighest + .withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.colorScheme.outline + .withValues(alpha: 0.2), + ), + ), + child: Text( + _lead!.description!, + style: theme.textTheme.bodyMedium + ?.copyWith( + height: 1.5, + color: theme.colorScheme.onSurface + .withValues(alpha: 0.8), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 8), + ], + ), + ), + + // Owner Information + if (_lead!.owner != null) + RepaintBoundary( + child: _buildSectionCard( + title: 'Lead Owner', + icon: Icons.person_rounded, + theme: theme, + children: [ + _buildInfoRow( + icon: Icons.account_circle_rounded, + label: 'Name', + value: _lead!.owner!.name, + theme: theme, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + icon: Icons.alternate_email_rounded, + label: 'Email Address', + value: _lead!.owner!.email, + theme: theme, + onTap: () { + HapticFeedback.lightImpact(); + // TODO: Launch email app + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Owner Email: ${_lead!.owner!.email}', + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + }, + ), + const SizedBox(height: 8), + ], + ), + ), + + // Timeline Information + RepaintBoundary( + child: _buildSectionCard( + title: 'Timeline', + icon: Icons.schedule_rounded, + theme: theme, + children: [ + _buildInfoRow( + icon: Icons.add_circle_outline_rounded, + label: 'Created', + value: DateFormat( + 'MMM dd, yyyy โ€ข HH:mm', + ).format(_lead!.createdAt), + theme: theme, + ), + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: theme.dividerColor.withValues(alpha: 0.3), + ), + _buildInfoRow( + icon: Icons.update_rounded, + label: 'Last Updated', + value: DateFormat( + 'MMM dd, yyyy โ€ข HH:mm', + ).format(_lead!.updatedAt), + theme: theme, + ), + const SizedBox(height: 8), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/leads_list_screen.dart b/lib/screens/leads_list_screen.dart new file mode 100644 index 0000000..8e44679 --- /dev/null +++ b/lib/screens/leads_list_screen.dart @@ -0,0 +1,837 @@ +import 'package:flutter/material.dart'; +import '../models/api_models.dart'; +import '../services/leads_service.dart'; + +class LeadsListScreen extends StatefulWidget { + const LeadsListScreen({super.key}); + + @override + State createState() => _LeadsListScreenState(); +} + +class _LeadsListScreenState extends State { + final LeadsService _leadsService = LeadsService(); + final ScrollController _scrollController = ScrollController(); + final TextEditingController _searchController = TextEditingController(); + + List _leads = []; + bool _isLoading = true; + bool _isLoadingMore = false; + bool _hasError = false; + bool _isSearching = false; + String? _errorMessage; + int _currentPage = 1; + bool _hasMoreData = true; + + String? _selectedStatus; + String? _selectedSource; + String? _selectedRating; + String? _selectedIndustry; + bool? _selectedConverted; + + @override + void initState() { + super.initState(); + _loadLeads(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + _searchController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + if (!_isLoadingMore && _hasMoreData && !_isSearching) { + _loadMoreLeads(); + } + } + } + + Future _loadLeads({bool isRefresh = false}) async { + debugPrint('Loading leads - refresh: $isRefresh'); + if (isRefresh) { + setState(() { + _currentPage = 1; + _hasMoreData = true; + _isLoading = true; + _hasError = false; + }); + } + + try { + final response = await _leadsService.getLeads( + page: _currentPage, + status: _selectedStatus, + leadSource: _selectedSource, + rating: _selectedRating, + industry: _selectedIndustry, + converted: _selectedConverted, + searchQuery: _searchController.text.isNotEmpty + ? _searchController.text + : null, + ); + + if (response != null) { + setState(() { + if (isRefresh || _currentPage == 1) { + _leads = response.leads; + } else { + _leads.addAll(response.leads); + } + _hasMoreData = response.pagination.hasNext; + _isLoading = false; + _hasError = false; + _isSearching = false; + }); + } else { + setState(() { + _hasError = true; + _errorMessage = 'Failed to load leads'; + _isLoading = false; + _isSearching = false; + }); + } + } catch (e) { + setState(() { + _hasError = true; + _errorMessage = e.toString(); + _isLoading = false; + _isSearching = false; + }); + } + } + + Future _loadMoreLeads() async { + if (_isLoadingMore || !_hasMoreData) return; + + setState(() { + _isLoadingMore = true; + }); + + _currentPage++; + + try { + final response = await _leadsService.getLeads( + page: _currentPage, + status: _selectedStatus, + leadSource: _selectedSource, + rating: _selectedRating, + industry: _selectedIndustry, + converted: _selectedConverted, + searchQuery: _searchController.text.isNotEmpty + ? _searchController.text + : null, + ); + + if (response != null) { + setState(() { + _leads.addAll(response.leads); + _hasMoreData = response.pagination.hasNext; + _isLoadingMore = false; + }); + } else { + setState(() { + _isLoadingMore = false; + _currentPage--; + }); + } + } catch (e) { + setState(() { + _isLoadingMore = false; + _currentPage--; + }); + } + } + + Future _onRefresh() async { + await _loadLeads(isRefresh: true); + } + + void _onSearchChanged() { + setState(() { + _isSearching = true; + }); + _loadLeads(isRefresh: true); + } + + void _showFilterDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Filter Leads'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DropdownButtonFormField( + value: _selectedStatus, + decoration: const InputDecoration( + labelText: 'Status', + border: OutlineInputBorder(), + ), + items: const [ + DropdownMenuItem(value: null, child: Text('All Statuses')), + DropdownMenuItem(value: 'NEW', child: Text('New')), + DropdownMenuItem(value: 'PENDING', child: Text('Pending')), + DropdownMenuItem(value: 'CONTACTED', child: Text('Contacted')), + DropdownMenuItem(value: 'QUALIFIED', child: Text('Qualified')), + DropdownMenuItem(value: 'UNQUALIFIED', child: Text('Unqualified')), + DropdownMenuItem(value: 'CONVERTED', child: Text('Converted')), + ], + onChanged: (value) { + setState(() { + _selectedStatus = value; + }); + }, + ), + const SizedBox(height: 16), + DropdownButtonFormField( + value: _selectedSource, + decoration: const InputDecoration( + labelText: 'Lead Source', + border: OutlineInputBorder(), + ), + items: const [ + DropdownMenuItem(value: null, child: Text('All Sources')), + DropdownMenuItem(value: 'WEB', child: Text('Web')), + DropdownMenuItem(value: 'PHONE_INQUIRY', child: Text('Phone Inquiry')), + DropdownMenuItem(value: 'PARTNER_REFERRAL', child: Text('Partner Referral')), + DropdownMenuItem(value: 'COLD_CALL', child: Text('Cold Call')), + DropdownMenuItem(value: 'TRADE_SHOW', child: Text('Trade Show')), + DropdownMenuItem(value: 'EMPLOYEE_REFERRAL', child: Text('Employee Referral')), + DropdownMenuItem(value: 'ADVERTISEMENT', child: Text('Advertisement')), + DropdownMenuItem(value: 'OTHER', child: Text('Other')), + ], + onChanged: (value) { + setState(() { + _selectedSource = value; + }); + }, + ), + const SizedBox(height: 16), + DropdownButtonFormField( + value: _selectedRating, + decoration: const InputDecoration( + labelText: 'Rating', + border: OutlineInputBorder(), + ), + items: const [ + DropdownMenuItem(value: null, child: Text('All Ratings')), + DropdownMenuItem(value: 'Hot', child: Text('Hot')), + DropdownMenuItem(value: 'Warm', child: Text('Warm')), + DropdownMenuItem(value: 'Cold', child: Text('Cold')), + ], + onChanged: (value) { + setState(() { + _selectedRating = value; + }); + }, + ), + const SizedBox(height: 16), + DropdownButtonFormField( + value: _selectedIndustry, + decoration: const InputDecoration( + labelText: 'Industry', + border: OutlineInputBorder(), + ), + items: const [ + DropdownMenuItem(value: null, child: Text('All Industries')), + DropdownMenuItem(value: 'Technology', child: Text('Technology')), + DropdownMenuItem(value: 'Healthcare', child: Text('Healthcare')), + DropdownMenuItem(value: 'Finance', child: Text('Finance')), + DropdownMenuItem(value: 'Education', child: Text('Education')), + DropdownMenuItem(value: 'Manufacturing', child: Text('Manufacturing')), + DropdownMenuItem(value: 'Retail', child: Text('Retail')), + DropdownMenuItem(value: 'Real Estate', child: Text('Real Estate')), + DropdownMenuItem(value: 'Consulting', child: Text('Consulting')), + DropdownMenuItem(value: 'Media', child: Text('Media')), + DropdownMenuItem(value: 'Transportation', child: Text('Transportation')), + DropdownMenuItem(value: 'Energy', child: Text('Energy')), + DropdownMenuItem(value: 'Government', child: Text('Government')), + DropdownMenuItem(value: 'Non-profit', child: Text('Non-profit')), + DropdownMenuItem(value: 'Other', child: Text('Other')), + ], + onChanged: (value) { + setState(() { + _selectedIndustry = value; + }); + }, + ), + const SizedBox(height: 16), + DropdownButtonFormField( + value: _selectedConverted, + decoration: const InputDecoration( + labelText: 'Converted', + border: OutlineInputBorder(), + ), + items: const [ + DropdownMenuItem(value: null, child: Text('All')), + DropdownMenuItem(value: false, child: Text('Not Converted')), + DropdownMenuItem(value: true, child: Text('Converted')), + ], + onChanged: (value) { + setState(() { + _selectedConverted = value; + }); + }, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + setState(() { + _selectedStatus = null; + _selectedSource = null; + _selectedRating = null; + _selectedIndustry = null; + _selectedConverted = null; + }); + Navigator.pop(context); + _loadLeads(isRefresh: true); + }, + child: const Text('Clear'), + ), + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + _loadLeads(isRefresh: true); + }, + child: const Text('Apply'), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Leads'), + actions: [ + IconButton( + icon: const Icon(Icons.filter_list), + onPressed: _showFilterDialog, + ), + ], + ), + body: Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + color: Colors.white, + child: TextField( + controller: _searchController, + decoration: InputDecoration( + hintText: 'Search leads by name, email, or company...', + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: Theme.of(context).primaryColor), + ), + filled: true, + fillColor: Colors.grey.shade50, + suffixIcon: _searchController.text.isNotEmpty + ? IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + _searchController.clear(); + _onSearchChanged(); + }, + ) + : null, + ), + onChanged: (_) => _onSearchChanged(), + ), + ), + Expanded( + child: RefreshIndicator( + onRefresh: _onRefresh, + child: _buildContent(), + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + heroTag: "leads_fab", + onPressed: () async { + final result = await Navigator.of(context).pushNamed('/lead-create'); + if (result == true) { + _loadLeads(isRefresh: true); + } + }, + child: const Icon(Icons.add), + ), + ); + } + + Widget _buildContent() { + if (_isLoading && _leads.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: const [ + SizedBox(height: 200), + Center(child: CircularProgressIndicator()), + ], + ); + } + + if (_hasError && _leads.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox(height: MediaQuery.of(context).size.height * 0.3), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + _errorMessage ?? 'Something went wrong', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => _loadLeads(isRefresh: true), + child: const Text('Retry'), + ), + ], + ), + ), + ], + ); + } + + if (_leads.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox(height: MediaQuery.of(context).size.height * 0.3), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.people_outline, + size: 64, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'No leads found', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + 'Add your first lead to get started', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: Colors.grey[600]), + ), + ], + ), + ), + ], + ); + } + + return ListView.builder( + controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _leads.length + (_isLoadingMore ? 1 : 0), + itemBuilder: (context, index) { + if (index == _leads.length) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ); + } + + final lead = _leads[index]; + return _buildLeadCard(lead); + }, + ); + } + + Widget _buildLeadCard(Lead lead) { + Color statusColor = _getStatusColor(lead.status); + Color ratingColor = _getRatingColor(lead.rating); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + left: BorderSide(color: statusColor, width: 3), + bottom: BorderSide(color: Colors.grey.shade200, width: 1), + ), + ), + child: ListTile( + contentPadding: const EdgeInsets.all(16), + leading: CircleAvatar( + backgroundColor: Theme.of(context).colorScheme.primary, + child: Text( + lead.firstName.isNotEmpty ? lead.firstName[0].toUpperCase() : '?', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + title: Row( + children: [ + Expanded( + child: Text( + lead.title ?? 'Untitled Lead', + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16), + ), + ), + if (lead.rating != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: ratingColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: ratingColor.withValues(alpha: 0.3)), + ), + child: Text( + lead.rating!, + style: TextStyle( + color: ratingColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (lead.description != null) ...[ + const SizedBox(height: 4), + Text( + lead.description!, + style: TextStyle( + color: Colors.grey[700], + fontSize: 14, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + if (lead.company != null) ...[ + const SizedBox(height: 4), + Row( + children: [ + Icon(Icons.business_outlined, size: 16, color: Colors.grey[600]), + const SizedBox(width: 4), + Expanded( + child: Text( + lead.company!, + style: TextStyle( + color: Colors.grey[600], + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ], + const SizedBox(height: 4), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + _getStatusDisplayName(lead.status), + style: TextStyle( + color: statusColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + if (lead.owner != null) ...[ + const SizedBox(height: 4), + Text( + 'Owner: ${lead.owner!.fullName}', + style: TextStyle( + color: Colors.grey[500], + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ), + ], + ], + ), + trailing: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () { + _showLeadOptions(context, lead); + }, + ), + onTap: () { + Navigator.of(context).pushNamed('/lead-detail', arguments: lead.id); + }, + ), + ); + } + + Color _getStatusColor(String status) { + switch (status.toUpperCase()) { + case 'NEW': + return Colors.blue; + case 'PENDING': + return Colors.orange; + case 'CONTACTED': + return Colors.purple; + case 'QUALIFIED': + return Colors.green; + case 'UNQUALIFIED': + return Colors.red; + case 'CONVERTED': + return Colors.teal; + default: + return Colors.grey; + } + } + + Color _getRatingColor(String? rating) { + if (rating == null) return Colors.grey; + switch (rating.toLowerCase()) { + case 'hot': + return Colors.red; + case 'warm': + return Colors.orange; + case 'cold': + return Colors.blue; + default: + return Colors.grey; + } + } + + String _getStatusDisplayName(String status) { + switch (status.toUpperCase()) { + case 'NEW': + return 'New'; + case 'PENDING': + return 'Pending'; + case 'CONTACTED': + return 'Contacted'; + case 'QUALIFIED': + return 'Qualified'; + case 'UNQUALIFIED': + return 'Unqualified'; + case 'CONVERTED': + return 'Converted'; + default: + return status; + } + } + + void _showLeadOptions(BuildContext context, Lead lead) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.visibility), + title: const Text('View Details'), + onTap: () { + Navigator.pop(context); + Navigator.of(context).pushNamed('/lead-detail', arguments: lead.id); + }, + ), + ListTile( + leading: const Icon(Icons.edit), + title: const Text('Edit Lead'), + onTap: () { + Navigator.pop(context); + // TODO: Navigate to edit lead + }, + ), + if (!lead.isConverted) + ListTile( + leading: const Icon(Icons.transform), + title: const Text('Convert Lead'), + onTap: () { + Navigator.pop(context); + _showConvertDialog(context, lead); + }, + ), + ListTile( + leading: const Icon(Icons.delete, color: Colors.red), + title: const Text( + 'Delete Lead', + style: TextStyle(color: Colors.red), + ), + onTap: () { + Navigator.pop(context); + _showDeleteConfirmation(context, lead); + }, + ), + ], + ), + ); + }, + ); + } + + void _showConvertDialog(BuildContext context, Lead lead) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Convert Lead'), + content: Text( + 'Convert ${lead.fullName} to a contact and account? This action cannot be undone.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + _convertLead(lead); + }, + child: const Text('Convert'), + ), + ], + ); + }, + ); + } + + void _showDeleteConfirmation(BuildContext context, Lead lead) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Delete Lead'), + content: Text( + 'Are you sure you want to delete ${lead.fullName}? This action cannot be undone.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + _deleteLead(lead); + }, + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Delete'), + ), + ], + ); + }, + ); + } + + Future _convertLead(Lead lead) async { + final success = await _leadsService.convertLead(lead.id, { + 'createContact': true, + 'createAccount': true, + }); + + if (success) { + setState(() { + final index = _leads.indexWhere((l) => l.id == lead.id); + if (index != -1) { + _leads[index] = Lead( + id: lead.id, + firstName: lead.firstName, + lastName: lead.lastName, + email: lead.email, + phone: lead.phone, + company: lead.company, + title: lead.title, + status: 'CONVERTED', + leadSource: lead.leadSource, + industry: lead.industry, + rating: lead.rating, + description: lead.description, + createdAt: lead.createdAt, + updatedAt: DateTime.now(), + ownerId: lead.ownerId, + organizationId: lead.organizationId, + isConverted: true, + convertedAt: DateTime.now(), + convertedAccountId: lead.convertedAccountId, + convertedContactId: lead.convertedContactId, + convertedOpportunityId: lead.convertedOpportunityId, + contactId: lead.contactId, + owner: lead.owner, + ); + } + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${lead.fullName} converted successfully')), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Failed to convert lead')), + ); + } + } + } + + Future _deleteLead(Lead lead) async { + final success = await _leadsService.deleteLead(lead.id); + + if (success) { + setState(() { + _leads.removeWhere((l) => l.id == lead.id); + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${lead.fullName} deleted successfully')), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Failed to delete lead')), + ); + } + } + } +} \ No newline at end of file diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart new file mode 100644 index 0000000..48b8832 --- /dev/null +++ b/lib/screens/login_screen.dart @@ -0,0 +1,422 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import '../services/auth_service.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final AuthService _authService = AuthService(); + bool _isLoading = false; + String? _errorMessage; + String _version = ''; + String _buildNumber = ''; + + @override + void initState() { + super.initState(); + _loadPackageInfo(); + } + + Future _loadPackageInfo() async { + final packageInfo = await PackageInfo.fromPlatform(); + setState(() { + _version = packageInfo.version; + _buildNumber = packageInfo.buildNumber; + }); + } + + Future _signInWithGoogle() async { + setState(() { + _isLoading = true; + _errorMessage = null; + }); + + try { + final success = await _authService.signInWithGoogle(); + + if (success && mounted) { + Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false); + } else if (mounted) { + setState(() { + _errorMessage = 'Failed to sign in with Google. Please try again.'; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _errorMessage = 'An error occurred: ${e.toString()}'; + }); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + Widget _buildFeatureItem(IconData icon, String label, ThemeData theme) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFF1565C0).withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xFF1565C0).withValues(alpha: 0.15), + width: 1, + ), + ), + child: Icon(icon, size: 24, color: const Color(0xFF1565C0)), + ), + const SizedBox(height: 8), + Text( + label, + style: theme.textTheme.bodySmall?.copyWith( + color: const Color(0xFF424242), + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + backgroundColor: Colors.white, + body: Container( + width: double.infinity, + height: double.infinity, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Spacer(flex: 1), + + // App Logo + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.08), + blurRadius: 16, + offset: const Offset(0, 4), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Image.asset( + 'assets/icon/icon.png', + width: 120, + height: 120, + fit: BoxFit.cover, + ), + ), + ), + + const SizedBox(height: 40), + + // Professional Title with tagline + Column( + children: [ + Text( + 'BottleCRM', + style: theme.textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.w700, + color: const Color(0xFF1A1A1A), + letterSpacing: -1.2, + fontSize: 36, + ), + ), + + const SizedBox(height: 4), + + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 6, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color(0xFF1565C0).withValues(alpha: 0.1), + const Color(0xFF2196F3).withValues(alpha: 0.05), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: const Color(0xFF1565C0).withValues(alpha: 0.2), + width: 1, + ), + ), + child: Text( + 'PROFESSIONAL EDITION', + style: theme.textTheme.labelSmall?.copyWith( + color: const Color(0xFF1565C0), + fontWeight: FontWeight.w600, + letterSpacing: 1.2, + ), + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Error Message + if (_errorMessage != null) ...[ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(bottom: 24), + decoration: BoxDecoration( + color: theme.colorScheme.errorContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon( + Icons.error_outline, + color: theme.colorScheme.onErrorContainer, + size: 20, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + _errorMessage!, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onErrorContainer, + ), + ), + ), + ], + ), + ), + ], + + // Professional Feature Highlights + Container( + padding: const EdgeInsets.all(24), + margin: const EdgeInsets.only(bottom: 32), + decoration: BoxDecoration( + color: const Color(0xFFFAFAFA), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color(0xFFE0E0E0), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildFeatureItem( + Icons.people_outline, + 'Contacts', + theme, + ), + _buildFeatureItem(Icons.trending_up, 'Analytics', theme), + _buildFeatureItem(Icons.task_alt, 'Pipeline', theme), + ], + ), + ), + + // Google Sign In Button + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Container( + width: double.infinity, + height: 60, + constraints: const BoxConstraints( + maxWidth: 400, // Maximum width for larger screens + ), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.08), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: ElevatedButton.icon( + onPressed: _isLoading ? null : _signInWithGoogle, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: const Color(0xFF1A1A1A), + elevation: 0, + shadowColor: Colors.transparent, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide( + color: const Color( + 0xFF1565C0, + ).withValues(alpha: 0.2), + width: 1.5, + ), + ), + ), + icon: _isLoading + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: const AlwaysStoppedAnimation( + Color(0xFF1565C0), + ), + ), + ) + : const FaIcon( + FontAwesomeIcons.google, + size: 20, + color: Color(0xFF4285F4), + ), + label: Text( + _isLoading + ? 'Signing you in...' + : 'Sign in with Google', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: _isLoading + ? const Color(0xFF666666) + : const Color(0xFF1A1A1A), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ), + ), + + const SizedBox(height: 32), + + // Professional Footer + Column( + children: [ + // Security Badge + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: const Color(0xFF4CAF50).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: const Color(0xFF4CAF50).withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.security, + size: 16, + color: const Color(0xFF4CAF50), + ), + const SizedBox(width: 6), + Text( + 'Enterprise-grade security', + style: theme.textTheme.bodySmall?.copyWith( + color: const Color(0xFF4CAF50), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + + const SizedBox(height: 16), + + // Terms and Privacy + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: theme.textTheme.bodySmall?.copyWith( + color: const Color(0xFF666666), + height: 1.5, + fontSize: 12, + ), + children: [ + const TextSpan( + text: 'By signing in, you agree to our ', + ), + TextSpan( + text: 'Terms of Service', + style: TextStyle( + color: const Color(0xFF1565C0), + fontWeight: FontWeight.w500, + decoration: TextDecoration.underline, + decorationColor: const Color( + 0xFF1565C0, + ).withValues(alpha: 0.5), + ), + ), + const TextSpan(text: ' and '), + TextSpan( + text: 'Privacy Policy', + style: TextStyle( + color: const Color(0xFF1565C0), + fontWeight: FontWeight.w500, + decoration: TextDecoration.underline, + decorationColor: const Color( + 0xFF1565C0, + ).withValues(alpha: 0.5), + ), + ), + ], + ), + ), + ), + ], + ), + + const SizedBox(height: 24), + + // Version information + if (_version.isNotEmpty && _buildNumber.isNotEmpty) + Text( + 'Version $_version ($_buildNumber)', + style: theme.textTheme.bodySmall?.copyWith( + color: const Color(0xFF999999), + fontSize: 11, + ), + ), + + const Spacer(flex: 1), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/task_create_screen.dart b/lib/screens/task_create_screen.dart new file mode 100644 index 0000000..981555f --- /dev/null +++ b/lib/screens/task_create_screen.dart @@ -0,0 +1,509 @@ +import 'package:flutter/material.dart'; +import '../services/tasks_service.dart'; +import '../models/api_models.dart'; + +class TaskCreateScreen extends StatefulWidget { + const TaskCreateScreen({super.key}); + + @override + State createState() => _TaskCreateScreenState(); +} + +class _TaskCreateScreenState extends State { + final GlobalKey _formKey = GlobalKey(); + final TasksService _tasksService = TasksService(); + + // Form controllers + final TextEditingController _subjectController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + final TextEditingController _accountIdController = TextEditingController(); + final TextEditingController _contactIdController = TextEditingController(); + + bool _isLoading = false; + TaskStatus _selectedStatus = TaskStatus.notStarted; + TaskPriority _selectedPriority = TaskPriority.normal; + DateTime? _selectedDueDate; + TimeOfDay? _selectedTime; + + @override + void dispose() { + _subjectController.dispose(); + _descriptionController.dispose(); + _accountIdController.dispose(); + _contactIdController.dispose(); + super.dispose(); + } + + Future _createTask() async { + if (!_formKey.currentState!.validate()) { + return; + } + + setState(() { + _isLoading = true; + }); + + try { + DateTime? dueDateTime; + if (_selectedDueDate != null) { + // Combine date and time + final time = _selectedTime ?? const TimeOfDay(hour: 9, minute: 0); + dueDateTime = DateTime( + _selectedDueDate!.year, + _selectedDueDate!.month, + _selectedDueDate!.day, + time.hour, + time.minute, + ); + } + + final task = await _tasksService.createTask( + subject: _subjectController.text.trim(), + description: _descriptionController.text.trim().isEmpty + ? null + : _descriptionController.text.trim(), + status: _selectedStatus.value, + priority: _selectedPriority.value, + dueDate: dueDateTime, + accountId: _accountIdController.text.trim().isEmpty + ? null + : _accountIdController.text.trim(), + contactId: _contactIdController.text.trim().isEmpty + ? null + : _contactIdController.text.trim(), + ); + + if (task != null && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Task "${task.subject}" created successfully'), + backgroundColor: Colors.green, + ), + ); + Navigator.of(context).pop(true); // Return true to indicate success + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to create task'), + backgroundColor: Colors.red, + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error creating task: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + String? _validateRequired(String? value, String fieldName) { + if (value == null || value.trim().isEmpty) { + return '$fieldName is required'; + } + return null; + } + + Future _selectDate() async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: + _selectedDueDate ?? DateTime.now().add(const Duration(days: 1)), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: Theme.of(context).colorScheme.copyWith( + primary: Theme.of(context).colorScheme.primary, + ), + ), + child: child!, + ); + }, + ); + + if (picked != null) { + setState(() { + _selectedDueDate = picked; + }); + } + } + + Future _selectTime() async { + if (_selectedDueDate == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please select a due date first')), + ); + return; + } + + final TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: _selectedTime ?? const TimeOfDay(hour: 9, minute: 0), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: Theme.of(context).colorScheme.copyWith( + primary: Theme.of(context).colorScheme.primary, + ), + ), + child: child!, + ); + }, + ); + + if (picked != null) { + setState(() { + _selectedTime = picked; + }); + } + } + + String _formatDate(DateTime date) { + return '${date.day}/${date.month}/${date.year}'; + } + + String _formatTime(TimeOfDay time) { + final hour = time.hourOfPeriod == 0 ? 12 : time.hourOfPeriod; + final minute = time.minute.toString().padLeft(2, '0'); + final period = time.period == DayPeriod.am ? 'AM' : 'PM'; + return '$hour:$minute $period'; + } + + Widget _buildTextField({ + required TextEditingController controller, + required String label, + String? hint, + bool required = false, + TextInputType keyboardType = TextInputType.text, + int maxLines = 1, + String? Function(String?)? validator, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: TextFormField( + controller: controller, + keyboardType: keyboardType, + maxLines: maxLines, + decoration: InputDecoration( + labelText: required ? '$label *' : label, + hintText: hint, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + validator: + validator ?? + (required ? (value) => _validateRequired(value, label) : null), + ), + ); + } + + Widget _buildDropdown({ + required String label, + required T value, + required List items, + required void Function(T?) onChanged, + required String Function(T) getDisplayText, + bool required = false, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: DropdownButtonFormField( + value: value, + decoration: InputDecoration( + labelText: required ? '$label *' : label, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + items: items.map((T item) { + return DropdownMenuItem( + value: item, + child: Text(getDisplayText(item)), + ); + }).toList(), + onChanged: onChanged, + ), + ); + } + + Widget _buildDateTimeSelector() { + return Column( + children: [ + // Due Date Selector + InkWell( + onTap: _selectDate, + borderRadius: BorderRadius.circular(12), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + ), + child: Row( + children: [ + Icon( + Icons.calendar_today, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Due Date', + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: Colors.grey[600]), + ), + const SizedBox(height: 2), + Text( + _selectedDueDate != null + ? _formatDate(_selectedDueDate!) + : 'Select due date', + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + const Spacer(), + if (_selectedDueDate != null) + IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + setState(() { + _selectedDueDate = null; + _selectedTime = null; + }); + }, + ), + ], + ), + ), + ), + + // Time Selector (only show if date is selected) + if (_selectedDueDate != null) ...[ + const SizedBox(height: 16), + InkWell( + onTap: _selectTime, + borderRadius: BorderRadius.circular(12), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + ), + child: Row( + children: [ + Icon( + Icons.access_time, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Due Time', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey[600], + ), + ), + const SizedBox(height: 2), + Text( + _selectedTime != null + ? _formatTime(_selectedTime!) + : 'Select due time', + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ], + ), + ), + ), + ], + + const SizedBox(height: 16), + ], + ); + } + + Widget _buildSectionHeader(String title) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Create Task'), + elevation: 0, + actions: [ + TextButton( + onPressed: _isLoading ? null : _createTask, + child: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Save'), + ), + ], + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Basic Information Section + _buildSectionHeader('Basic Information'), + _buildTextField( + controller: _subjectController, + label: 'Subject', + required: true, + hint: 'Enter task subject', + ), + _buildTextField( + controller: _descriptionController, + label: 'Description', + hint: 'Enter task description (optional)', + maxLines: 3, + ), + + // Task Details Section + _buildSectionHeader('Task Details'), + _buildDropdown( + label: 'Status', + value: _selectedStatus, + items: TaskStatus.values, + onChanged: (TaskStatus? newValue) { + if (newValue != null) { + setState(() { + _selectedStatus = newValue; + }); + } + }, + getDisplayText: (status) => status.value, + required: true, + ), + _buildDropdown( + label: 'Priority', + value: _selectedPriority, + items: TaskPriority.values, + onChanged: (TaskPriority? newValue) { + if (newValue != null) { + setState(() { + _selectedPriority = newValue; + }); + } + }, + getDisplayText: (priority) => priority.value, + required: true, + ), + + // Due Date and Time Section + _buildSectionHeader('Due Date & Time'), + _buildDateTimeSelector(), + + // Associations Section + _buildSectionHeader('Associations'), + _buildTextField( + controller: _accountIdController, + label: 'Account ID', + hint: 'Enter associated account ID (optional)', + ), + _buildTextField( + controller: _contactIdController, + label: 'Contact ID', + hint: 'Enter associated contact ID (optional)', + ), + + const SizedBox(height: 32), + + // Create Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _createTask, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: _isLoading + ? const Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ), + SizedBox(width: 12), + Text('Creating Task...'), + ], + ) + : const Text( + 'Create Task', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Help text + Center( + child: Text( + 'Fields marked with * are required', + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: Colors.grey[600]), + ), + ), + + const SizedBox(height: 32), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/task_detail_screen.dart b/lib/screens/task_detail_screen.dart new file mode 100644 index 0000000..cd3389b --- /dev/null +++ b/lib/screens/task_detail_screen.dart @@ -0,0 +1,716 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../models/api_models.dart'; +import '../services/tasks_service.dart'; + +class TaskDetailScreen extends StatefulWidget { + final String taskId; + + const TaskDetailScreen({super.key, required this.taskId}); + + @override + State createState() => _TaskDetailScreenState(); +} + +class _TaskDetailScreenState extends State { + final TasksService _tasksService = TasksService(); + + Task? _task; + bool _isLoading = true; + bool _hasError = false; + String? _errorMessage; + + @override + void initState() { + super.initState(); + _loadTaskDetails(); + } + + Future _loadTaskDetails() async { + setState(() { + _isLoading = true; + _hasError = false; + }); + + try { + final task = await _tasksService.getTaskById(widget.taskId); + + if (task != null && mounted) { + setState(() { + _task = task; + _isLoading = false; + }); + } else if (mounted) { + setState(() { + _hasError = true; + _errorMessage = 'Task not found'; + _isLoading = false; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _hasError = true; + _errorMessage = e.toString(); + _isLoading = false; + }); + } + } + } + + Future _copyToClipboard(String text, String label) async { + await Clipboard.setData(ClipboardData(text: text)); + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('$label copied to clipboard'))); + } + } + + Color _getPriorityColor(TaskPriority priority) { + switch (priority) { + case TaskPriority.low: + return Colors.green; + case TaskPriority.normal: + return Colors.orange; + case TaskPriority.high: + return Colors.red; + } + } + + Color _getStatusColor(TaskStatus status) { + switch (status) { + case TaskStatus.notStarted: + return Colors.grey; + case TaskStatus.inProgress: + return Colors.blue; + case TaskStatus.completed: + return Colors.green; + case TaskStatus.cancelled: + return Colors.red; + } + } + + String _formatDateTime(DateTime dateTime) { + return '${dateTime.day}/${dateTime.month}/${dateTime.year} at ${dateTime.hour}:${dateTime.minute.toString().padLeft(2, '0')}'; + } + + Widget _buildInfoSection({ + required String title, + required List children, + IconData? icon, + }) { + return Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (icon != null) ...[ + Icon( + icon, + size: 20, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 8), + ], + Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + const SizedBox(height: 12), + ...children, + ], + ), + ), + ); + } + + Widget _buildInfoRow({ + required String label, + required String value, + bool copyable = false, + Widget? trailing, + }) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 100, + child: Text( + label, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: copyable ? () => _copyToClipboard(value, label) : null, + child: Text( + value, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + decoration: copyable ? TextDecoration.underline : null, + ), + ), + ), + ), + if (trailing != null) trailing, + ], + ), + ); + } + + Widget _buildStatusPriorityChips() { + if (_task == null) return const SizedBox.shrink(); + + return Row( + children: [ + // Status Chip + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _getStatusColor(_task!.status).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: _getStatusColor(_task!.status).withValues(alpha: 0.3), + ), + ), + child: Text( + _task!.status.value, + style: TextStyle( + color: _getStatusColor(_task!.status), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(width: 12), + // Priority Chip + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _getPriorityColor(_task!.priority).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: _getPriorityColor(_task!.priority).withValues(alpha: 0.3), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.flag, + size: 14, + color: _getPriorityColor(_task!.priority), + ), + const SizedBox(width: 4), + Text( + _task!.priority.value, + style: TextStyle( + color: _getPriorityColor(_task!.priority), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ); + } + + Future _markTaskComplete() async { + if (_task == null) return; + + try { + final updatedTask = await _tasksService.updateTask( + _task!.id, + status: TaskStatus.completed.value, + ); + + if (updatedTask != null && mounted) { + setState(() { + _task = updatedTask; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Task "${_task!.subject}" marked as complete'), + backgroundColor: Colors.green, + ), + ); + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to update task'), + backgroundColor: Colors.red, + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error updating task: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } + + void _showDeleteConfirmation() { + if (_task == null) return; + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Delete Task'), + content: Text( + 'Are you sure you want to delete "${_task!.subject}"? This action cannot be undone.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + _deleteTask(); + }, + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Delete'), + ), + ], + ); + }, + ); + } + + Future _deleteTask() async { + if (_task == null) return; + + try { + final success = await _tasksService.deleteTask(_task!.id); + + if (success && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Task "${_task!.subject}" deleted successfully'), + backgroundColor: Colors.green, + ), + ); + // Navigate back to the tasks list + Navigator.of( + context, + ).pop(true); // Return true to indicate task was deleted + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to delete task'), + backgroundColor: Colors.red, + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error deleting task: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } + + Widget _buildCommentItem(TaskComment comment) { + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + radius: 16, + backgroundColor: Theme.of(context).colorScheme.primary, + child: Text( + comment.author?.name.isNotEmpty == true + ? comment.author!.name[0].toUpperCase() + : 'U', + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + comment.author?.name ?? 'Unknown User', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + Text( + _formatDateTime(comment.createdAt), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey[600], + ), + ), + ], + ), + ), + if (comment.isPrivate) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.orange[100], + borderRadius: BorderRadius.circular(4), + ), + child: Text( + 'Private', + style: TextStyle( + color: Colors.orange[800], + fontSize: 10, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Text(comment.body, style: Theme.of(context).textTheme.bodyMedium), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_task?.subject ?? 'Task Details'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + elevation: 0, + actions: [ + if (_task != null) ...[ + IconButton( + icon: const Icon(Icons.edit), + onPressed: () async { + final result = await Navigator.of( + context, + ).pushNamed('/task-edit', arguments: widget.taskId); + // If task was updated successfully, refresh the task details + if (result == true) { + _loadTaskDetails(); + } + }, + ), + PopupMenuButton( + onSelected: (value) { + switch (value) { + case 'complete': + _markTaskComplete(); + break; + case 'delete': + _showDeleteConfirmation(); + break; + } + }, + itemBuilder: (context) => [ + if (_task!.status != TaskStatus.completed) + const PopupMenuItem( + value: 'complete', + child: Row( + children: [ + Icon(Icons.check_circle, color: Colors.green), + SizedBox(width: 8), + Text('Mark as Complete'), + ], + ), + ), + const PopupMenuItem( + value: 'delete', + child: Row( + children: [ + Icon(Icons.delete, color: Colors.red), + SizedBox(width: 8), + Text('Delete Task'), + ], + ), + ), + ], + ), + ], + ], + ), + body: _buildBody(), + ); + } + + Widget _buildBody() { + if (_isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (_hasError) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + _errorMessage ?? 'Something went wrong', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _loadTaskDetails, + child: const Text('Retry'), + ), + ], + ), + ); + } + + if (_task == null) { + return const Center(child: Text('Task not found')); + } + + return RefreshIndicator( + onRefresh: _loadTaskDetails, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Task Header with Status and Priority + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _task!.subject, + style: Theme.of(context).textTheme.headlineSmall + ?.copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + _buildStatusPriorityChips(), + if (_task!.isOverdue) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.warning, + color: Colors.red, + size: 16, + ), + const SizedBox(width: 4), + Text( + 'Overdue', + style: TextStyle( + color: Colors.red[700], + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ], + ), + ), + ), + + // Description + if (_task!.description != null && _task!.description!.isNotEmpty) + _buildInfoSection( + title: 'Description', + icon: Icons.description, + children: [ + Text( + _task!.description!, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + + // Task Details + _buildInfoSection( + title: 'Task Details', + icon: Icons.info_outline, + children: [ + if (_task!.dueDate != null) + _buildInfoRow( + label: 'Due Date', + value: _formatDateTime(_task!.dueDate!), + ), + _buildInfoRow( + label: 'Created', + value: _formatDateTime(_task!.createdAt), + ), + if (_task!.updatedAt != _task!.createdAt) + _buildInfoRow( + label: 'Updated', + value: _formatDateTime(_task!.updatedAt), + ), + ], + ), + + // Owner Information + if (_task!.owner != null || _task!.createdBy != null) + _buildInfoSection( + title: 'People', + icon: Icons.people_outline, + children: [ + if (_task!.owner != null) ...[ + _buildInfoRow( + label: 'Owner', + value: _task!.owner!.name, + copyable: true, + ), + _buildInfoRow( + label: 'Email', + value: _task!.owner!.email, + copyable: true, + ), + ], + if (_task!.createdBy != null && + _task!.createdBy!.id != _task!.owner?.id) ...[ + const SizedBox(height: 8), + _buildInfoRow( + label: 'Created By', + value: _task!.createdBy!.name, + copyable: true, + ), + ], + ], + ), + + // Related Records + if (_task!.account != null || + _task!.contact != null || + _task!.opportunity != null) + _buildInfoSection( + title: 'Related Records', + icon: Icons.link, + children: [ + if (_task!.account != null) ...[ + _buildInfoRow( + label: 'Account', + value: _task!.account!.name, + ), + if (_task!.account!.phone != null) + _buildInfoRow( + label: 'Phone', + value: _task!.account!.phone!, + copyable: true, + ), + if (_task!.account!.website != null) + _buildInfoRow( + label: 'Website', + value: _task!.account!.website!, + copyable: true, + ), + ], + if (_task!.contact != null) ...[ + if (_task!.account != null) const SizedBox(height: 8), + _buildInfoRow( + label: 'Contact', + value: _task!.contact!.fullName, + ), + if (_task!.contact!.email != null) + _buildInfoRow( + label: 'Email', + value: _task!.contact!.email!, + copyable: true, + ), + if (_task!.contact!.phone != null) + _buildInfoRow( + label: 'Phone', + value: _task!.contact!.phone!, + copyable: true, + ), + ], + if (_task!.opportunity != null) ...[ + if (_task!.account != null || _task!.contact != null) + const SizedBox(height: 8), + _buildInfoRow( + label: 'Opportunity', + value: _task!.opportunity!.name, + ), + _buildInfoRow( + label: 'Amount', + value: + '\$${_task!.opportunity!.amount.toStringAsFixed(2)}', + ), + _buildInfoRow( + label: 'Stage', + value: _task!.opportunity!.stage, + ), + ], + ], + ), + + // Comments Section + if (_task!.comments != null && _task!.comments!.isNotEmpty) + _buildInfoSection( + title: 'Comments (${_task!.comments!.length})', + icon: Icons.comment_outlined, + children: [ + Column( + children: _task!.comments! + .map((comment) => _buildCommentItem(comment)) + .toList(), + ), + ], + ), + + const SizedBox(height: 80), // Bottom padding for FAB + ], + ), + ), + ); + } +} diff --git a/lib/screens/task_edit_screen.dart b/lib/screens/task_edit_screen.dart new file mode 100644 index 0000000..65b9a2d --- /dev/null +++ b/lib/screens/task_edit_screen.dart @@ -0,0 +1,694 @@ +import 'package:flutter/material.dart'; +import '../services/tasks_service.dart'; +import '../models/api_models.dart'; + +class TaskEditScreen extends StatefulWidget { + final String taskId; + + const TaskEditScreen({super.key, required this.taskId}); + + @override + State createState() => _TaskEditScreenState(); +} + +class _TaskEditScreenState extends State { + final GlobalKey _formKey = GlobalKey(); + final TasksService _tasksService = TasksService(); + + // Form controllers + final TextEditingController _subjectController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + final TextEditingController _accountIdController = TextEditingController(); + final TextEditingController _contactIdController = TextEditingController(); + + bool _isLoading = true; + bool _isSaving = false; + bool _hasError = false; + String? _errorMessage; + + Task? _originalTask; + TaskStatus _selectedStatus = TaskStatus.notStarted; + TaskPriority _selectedPriority = TaskPriority.normal; + DateTime? _selectedDueDate; + TimeOfDay? _selectedTime; + + @override + void initState() { + super.initState(); + _loadTaskDetails(); + } + + @override + void dispose() { + _subjectController.dispose(); + _descriptionController.dispose(); + _accountIdController.dispose(); + _contactIdController.dispose(); + super.dispose(); + } + + Future _loadTaskDetails() async { + setState(() { + _isLoading = true; + _hasError = false; + }); + + try { + final task = await _tasksService.getTaskById(widget.taskId); + + if (task != null && mounted) { + setState(() { + _originalTask = task; + _populateFormFields(task); + _isLoading = false; + }); + } else if (mounted) { + setState(() { + _hasError = true; + _errorMessage = 'Task not found'; + _isLoading = false; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _hasError = true; + _errorMessage = e.toString(); + _isLoading = false; + }); + } + } + } + + void _populateFormFields(Task task) { + _subjectController.text = task.subject; + _descriptionController.text = task.description ?? ''; + _accountIdController.text = task.accountId ?? ''; + _contactIdController.text = task.contactId ?? ''; + + _selectedStatus = task.status; + _selectedPriority = task.priority; + + if (task.dueDate != null) { + _selectedDueDate = DateTime( + task.dueDate!.year, + task.dueDate!.month, + task.dueDate!.day, + ); + _selectedTime = TimeOfDay( + hour: task.dueDate!.hour, + minute: task.dueDate!.minute, + ); + } + } + + Future _updateTask() async { + if (!_formKey.currentState!.validate()) { + return; + } + + setState(() { + _isSaving = true; + }); + + try { + DateTime? dueDateTime; + if (_selectedDueDate != null) { + // Combine date and time + final time = _selectedTime ?? const TimeOfDay(hour: 9, minute: 0); + dueDateTime = DateTime( + _selectedDueDate!.year, + _selectedDueDate!.month, + _selectedDueDate!.day, + time.hour, + time.minute, + ); + } + + final updatedTask = await _tasksService.updateTask( + widget.taskId, + subject: _subjectController.text.trim() != _originalTask?.subject + ? _subjectController.text.trim() + : null, + description: + _descriptionController.text.trim() != + (_originalTask?.description ?? '') + ? (_descriptionController.text.trim().isEmpty + ? null + : _descriptionController.text.trim()) + : null, + status: _selectedStatus != _originalTask?.status + ? _selectedStatus.value + : null, + priority: _selectedPriority != _originalTask?.priority + ? _selectedPriority.value + : null, + dueDate: dueDateTime != _originalTask?.dueDate ? dueDateTime : null, + accountId: + _accountIdController.text.trim() != (_originalTask?.accountId ?? '') + ? (_accountIdController.text.trim().isEmpty + ? null + : _accountIdController.text.trim()) + : null, + contactId: + _contactIdController.text.trim() != (_originalTask?.contactId ?? '') + ? (_contactIdController.text.trim().isEmpty + ? null + : _contactIdController.text.trim()) + : null, + ); + + if (updatedTask != null && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Task "${updatedTask.subject}" updated successfully'), + backgroundColor: Colors.green, + ), + ); + Navigator.of(context).pop(true); // Return true to indicate success + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to update task'), + backgroundColor: Colors.red, + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error updating task: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isSaving = false; + }); + } + } + } + + bool _hasChanges() { + if (_originalTask == null) return false; + + return _subjectController.text.trim() != _originalTask!.subject || + _descriptionController.text.trim() != + (_originalTask!.description ?? '') || + _selectedStatus != _originalTask!.status || + _selectedPriority != _originalTask!.priority || + _getDueDateTime() != _originalTask!.dueDate || + _accountIdController.text.trim() != (_originalTask!.accountId ?? '') || + _contactIdController.text.trim() != (_originalTask!.contactId ?? ''); + } + + DateTime? _getDueDateTime() { + if (_selectedDueDate == null) return null; + + final time = _selectedTime ?? const TimeOfDay(hour: 9, minute: 0); + return DateTime( + _selectedDueDate!.year, + _selectedDueDate!.month, + _selectedDueDate!.day, + time.hour, + time.minute, + ); + } + + Future _onWillPop() async { + if (!_hasChanges()) return true; + + final shouldDiscard = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Discard Changes?'), + content: const Text( + 'You have unsaved changes. Are you sure you want to discard them?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Discard'), + ), + ], + ), + ); + + return shouldDiscard ?? false; + } + + String? _validateRequired(String? value, String fieldName) { + if (value == null || value.trim().isEmpty) { + return '$fieldName is required'; + } + return null; + } + + Future _selectDate() async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: + _selectedDueDate ?? DateTime.now().add(const Duration(days: 1)), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: Theme.of(context).colorScheme.copyWith( + primary: Theme.of(context).colorScheme.primary, + ), + ), + child: child!, + ); + }, + ); + + if (picked != null) { + setState(() { + _selectedDueDate = picked; + }); + } + } + + Future _selectTime() async { + if (_selectedDueDate == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please select a due date first')), + ); + return; + } + + final TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: _selectedTime ?? const TimeOfDay(hour: 9, minute: 0), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: Theme.of(context).colorScheme.copyWith( + primary: Theme.of(context).colorScheme.primary, + ), + ), + child: child!, + ); + }, + ); + + if (picked != null) { + setState(() { + _selectedTime = picked; + }); + } + } + + String _formatDate(DateTime date) { + return '${date.day}/${date.month}/${date.year}'; + } + + String _formatTime(TimeOfDay time) { + final hour = time.hourOfPeriod == 0 ? 12 : time.hourOfPeriod; + final minute = time.minute.toString().padLeft(2, '0'); + final period = time.period == DayPeriod.am ? 'AM' : 'PM'; + return '$hour:$minute $period'; + } + + Widget _buildTextField({ + required TextEditingController controller, + required String label, + String? hint, + bool required = false, + TextInputType keyboardType = TextInputType.text, + int maxLines = 1, + String? Function(String?)? validator, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: TextFormField( + controller: controller, + keyboardType: keyboardType, + maxLines: maxLines, + decoration: InputDecoration( + labelText: required ? '$label *' : label, + hintText: hint, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + validator: + validator ?? + (required ? (value) => _validateRequired(value, label) : null), + ), + ); + } + + Widget _buildDropdown({ + required String label, + required T value, + required List items, + required void Function(T?) onChanged, + required String Function(T) getDisplayText, + bool required = false, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: DropdownButtonFormField( + value: value, + decoration: InputDecoration( + labelText: required ? '$label *' : label, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + ), + items: items.map((T item) { + return DropdownMenuItem( + value: item, + child: Text(getDisplayText(item)), + ); + }).toList(), + onChanged: onChanged, + ), + ); + } + + Widget _buildDateTimeSelector() { + return Column( + children: [ + // Due Date Selector + InkWell( + onTap: _selectDate, + borderRadius: BorderRadius.circular(12), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + ), + child: Row( + children: [ + Icon( + Icons.calendar_today, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Due Date', + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: Colors.grey[600]), + ), + const SizedBox(height: 2), + Text( + _selectedDueDate != null + ? _formatDate(_selectedDueDate!) + : 'Select due date', + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + const Spacer(), + if (_selectedDueDate != null) + IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + setState(() { + _selectedDueDate = null; + _selectedTime = null; + }); + }, + ), + ], + ), + ), + ), + + // Time Selector (only show if date is selected) + if (_selectedDueDate != null) ...[ + const SizedBox(height: 16), + InkWell( + onTap: _selectTime, + borderRadius: BorderRadius.circular(12), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + ), + child: Row( + children: [ + Icon( + Icons.access_time, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Due Time', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey[600], + ), + ), + const SizedBox(height: 2), + Text( + _selectedTime != null + ? _formatTime(_selectedTime!) + : 'Select due time', + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ], + ), + ), + ), + ], + + const SizedBox(height: 16), + ], + ); + } + + Widget _buildSectionHeader(String title) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: !_hasChanges(), + onPopInvokedWithResult: (didPop, result) async { + if (!didPop) { + final navigator = Navigator.of(context); + final shouldPop = await _onWillPop(); + if (shouldPop && mounted) { + navigator.pop(); + } + } + }, + child: Scaffold( + appBar: AppBar( + title: const Text('Edit Task'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + elevation: 0, + actions: [ + TextButton( + onPressed: (_isSaving || !_hasChanges()) ? null : _updateTask, + child: _isSaving + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Save'), + ), + ], + ), + body: _buildBody(), + ), + ); + } + + Widget _buildBody() { + if (_isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (_hasError) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + _errorMessage ?? 'Something went wrong', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _loadTaskDetails, + child: const Text('Retry'), + ), + ], + ), + ); + } + + if (_originalTask == null) { + return const Center(child: Text('Task not found')); + } + + return Form( + key: _formKey, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Basic Information Section + _buildSectionHeader('Basic Information'), + _buildTextField( + controller: _subjectController, + label: 'Subject', + required: true, + hint: 'Enter task subject', + ), + _buildTextField( + controller: _descriptionController, + label: 'Description', + hint: 'Enter task description (optional)', + maxLines: 3, + ), + + // Task Details Section + _buildSectionHeader('Task Details'), + _buildDropdown( + label: 'Status', + value: _selectedStatus, + items: TaskStatus.values, + onChanged: (TaskStatus? newValue) { + if (newValue != null) { + setState(() { + _selectedStatus = newValue; + }); + } + }, + getDisplayText: (status) => status.value, + required: true, + ), + _buildDropdown( + label: 'Priority', + value: _selectedPriority, + items: TaskPriority.values, + onChanged: (TaskPriority? newValue) { + if (newValue != null) { + setState(() { + _selectedPriority = newValue; + }); + } + }, + getDisplayText: (priority) => priority.value, + required: true, + ), + + // Due Date and Time Section + _buildSectionHeader('Due Date & Time'), + _buildDateTimeSelector(), + + // Associations Section + _buildSectionHeader('Associations'), + _buildTextField( + controller: _accountIdController, + label: 'Account ID', + hint: 'Enter associated account ID (optional)', + ), + _buildTextField( + controller: _contactIdController, + label: 'Contact ID', + hint: 'Enter associated contact ID (optional)', + ), + + const SizedBox(height: 32), + + // Update Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: (_isSaving || !_hasChanges()) ? null : _updateTask, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: _isSaving + ? const Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ), + SizedBox(width: 12), + Text('Updating Task...'), + ], + ) + : Text( + _hasChanges() ? 'Update Task' : 'No Changes', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Help text + Center( + child: Text( + 'Fields marked with * are required', + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: Colors.grey[600]), + ), + ), + + const SizedBox(height: 32), + ], + ), + ), + ); + } +} diff --git a/lib/screens/tasks_list_screen.dart b/lib/screens/tasks_list_screen.dart new file mode 100644 index 0000000..f1745da --- /dev/null +++ b/lib/screens/tasks_list_screen.dart @@ -0,0 +1,775 @@ +import 'package:flutter/material.dart'; +import '../models/api_models.dart'; +import '../services/tasks_service.dart'; + +class TasksListScreen extends StatefulWidget { + const TasksListScreen({super.key}); + + @override + State createState() => _TasksListScreenState(); +} + +class _TasksListScreenState extends State { + final TasksService _tasksService = TasksService(); + final ScrollController _scrollController = ScrollController(); + + List _tasks = []; + bool _isLoading = true; + bool _isLoadingMore = false; + bool _hasError = false; + String? _errorMessage; + int _currentPage = 0; + bool _hasMoreData = true; + final int _limit = 10; + + // Filter options + String? _selectedStatus; + String? _selectedPriority; + + @override + void initState() { + super.initState(); + _loadTasks(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + if (!_isLoadingMore && _hasMoreData) { + _loadMoreTasks(); + } + } + } + + Future _loadTasks({bool isRefresh = false}) async { + if (isRefresh) { + setState(() { + _currentPage = 0; + _hasMoreData = true; + _isLoading = true; + _hasError = false; + }); + } + + try { + final response = await _tasksService.getTasks( + status: _selectedStatus, + priority: _selectedPriority, + limit: _limit, + offset: _currentPage * _limit, + ); + + if (response != null) { + setState(() { + if (isRefresh || _currentPage == 0) { + _tasks = response.tasks; + } else { + _tasks.addAll(response.tasks); + } + _hasMoreData = + response.pagination?.hasNext ?? (response.tasks.length == _limit); + _isLoading = false; + _hasError = false; + }); + } else { + setState(() { + _hasError = true; + _errorMessage = 'Failed to load tasks'; + _isLoading = false; + }); + } + } catch (e) { + setState(() { + _hasError = true; + _errorMessage = e.toString(); + _isLoading = false; + }); + } + } + + Future _loadMoreTasks() async { + if (_isLoadingMore || !_hasMoreData) return; + + setState(() { + _isLoadingMore = true; + }); + + _currentPage++; + + try { + final response = await _tasksService.getTasks( + status: _selectedStatus, + priority: _selectedPriority, + limit: _limit, + offset: _currentPage * _limit, + ); + + if (response != null) { + setState(() { + _tasks.addAll(response.tasks); + _hasMoreData = + response.pagination?.hasNext ?? (response.tasks.length == _limit); + _isLoadingMore = false; + }); + } else { + setState(() { + _isLoadingMore = false; + _currentPage--; + }); + } + } catch (e) { + setState(() { + _isLoadingMore = false; + _currentPage--; + }); + } + } + + Future _onRefresh() async { + await _loadTasks(isRefresh: true); + } + + void _showFilters() { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setModalState) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Filter Tasks', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + const Text('Status:'), + const SizedBox(height: 8), + Wrap( + spacing: 8, + children: [ + FilterChip( + label: const Text('All'), + selected: _selectedStatus == null, + onSelected: (selected) { + setModalState(() { + _selectedStatus = selected + ? null + : _selectedStatus; + }); + }, + ), + ...TaskStatus.values.map( + (status) => FilterChip( + label: Text(status.value), + selected: _selectedStatus == status.value, + onSelected: (selected) { + setModalState(() { + _selectedStatus = selected + ? status.value + : null; + }); + }, + ), + ), + ], + ), + const SizedBox(height: 16), + const Text('Priority:'), + const SizedBox(height: 8), + Wrap( + spacing: 8, + children: [ + FilterChip( + label: const Text('All'), + selected: _selectedPriority == null, + onSelected: (selected) { + setModalState(() { + _selectedPriority = selected + ? null + : _selectedPriority; + }); + }, + ), + ...TaskPriority.values.map( + (priority) => FilterChip( + label: Text(priority.value), + selected: _selectedPriority == priority.value, + onSelected: (selected) { + setModalState(() { + _selectedPriority = selected + ? priority.value + : null; + }); + }, + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + setModalState(() { + _selectedStatus = null; + _selectedPriority = null; + }); + }, + child: const Text('Clear'), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + setState(() {}); + _loadTasks(isRefresh: true); + }, + child: const Text('Apply'), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Tasks'), + actions: [ + IconButton( + icon: Icon( + Icons.filter_list, + color: (_selectedStatus != null || _selectedPriority != null) + ? Theme.of(context).colorScheme.primary + : null, + ), + onPressed: _showFilters, + ), + ], + ), + body: RefreshIndicator(onRefresh: _onRefresh, child: _buildContent()), + floatingActionButton: FloatingActionButton( + heroTag: "tasks_fab", + onPressed: () async { + final result = await Navigator.of(context).pushNamed('/task-create'); + // If task was created successfully, refresh the tasks list + if (result == true) { + _loadTasks(isRefresh: true); + } + }, + child: const Icon(Icons.add), + ), + ); + } + + Widget _buildContent() { + if (_isLoading && _tasks.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: const [ + SizedBox(height: 200), + Center(child: CircularProgressIndicator()), + ], + ); + } + + if (_hasError && _tasks.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox(height: MediaQuery.of(context).size.height * 0.3), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + _errorMessage ?? 'Something went wrong', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => _loadTasks(isRefresh: true), + child: const Text('Retry'), + ), + ], + ), + ), + ], + ); + } + + if (_tasks.isEmpty) { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox(height: MediaQuery.of(context).size.height * 0.3), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.task_outlined, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + 'No tasks found', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + 'Create your first task to get started', + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: Colors.grey[600]), + ), + ], + ), + ), + ], + ); + } + + return ListView.builder( + controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _tasks.length + (_isLoadingMore ? 1 : 0), + itemBuilder: (context, index) { + if (index == _tasks.length) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ); + } + + final task = _tasks[index]; + return _buildTaskCard(task); + }, + ); + } + + Widget _buildTaskCard(Task task) { + final bool isOverdue = task.isOverdue; + final Color priorityColor = _getPriorityColor(task.priority); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + left: BorderSide(color: priorityColor, width: 3), + bottom: BorderSide( + color: isOverdue ? Colors.red.shade200 : Colors.grey.shade200, + width: 1, + ), + ), + ), + child: ListTile( + contentPadding: const EdgeInsets.all(16), + title: Row( + children: [ + Expanded( + child: Text( + task.title, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + decoration: task.status == TaskStatus.completed + ? TextDecoration.lineThrough + : null, + ), + ), + ), + if (isOverdue) + const Icon(Icons.warning, color: Colors.red, size: 20), + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Row( + children: [ + _buildStatusChip(task.status), + const SizedBox(width: 8), + _buildPriorityChip(task.priority), + ], + ), + if (task.dueDate != null) ...[ + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.schedule, + size: 16, + color: isOverdue ? Colors.red : Colors.grey[600], + ), + const SizedBox(width: 4), + Text( + _formatDueDate(task.dueDate!), + style: TextStyle( + color: isOverdue ? Colors.red : Colors.grey[600], + fontSize: 13, + fontWeight: isOverdue + ? FontWeight.w500 + : FontWeight.normal, + ), + ), + ], + ), + ], + if (task.description != null && task.description!.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + task.description!, + style: TextStyle(color: Colors.grey[600], fontSize: 13), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + if (task.owner != null) ...[ + const SizedBox(height: 4), + Row( + children: [ + Icon(Icons.person_outline, size: 16, color: Colors.grey[600]), + const SizedBox(width: 4), + Text( + task.owner!.name, + style: TextStyle( + color: Colors.grey[500], + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ], + if (task.account != null) ...[ + const SizedBox(height: 2), + Row( + children: [ + Icon( + Icons.business_outlined, + size: 16, + color: Colors.grey[600], + ), + const SizedBox(width: 4), + Text( + task.account!.name, + style: TextStyle( + color: Colors.grey[500], + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ], + if (task.contact != null) ...[ + const SizedBox(height: 2), + Row( + children: [ + Icon( + Icons.contact_phone_outlined, + size: 16, + color: Colors.grey[600], + ), + const SizedBox(width: 4), + Text( + task.contact!.fullName, + style: TextStyle( + color: Colors.grey[500], + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ], + ], + ), + trailing: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () { + _showTaskOptions(context, task); + }, + ), + onTap: () async { + final result = await Navigator.of( + context, + ).pushNamed('/task-detail', arguments: task.id); + // If task was deleted or modified, refresh the tasks list + if (result == true) { + _loadTasks(isRefresh: true); + } + }, + ), + ); + } + + Widget _buildStatusChip(TaskStatus status) { + Color backgroundColor; + Color textColor; + + switch (status) { + case TaskStatus.notStarted: + backgroundColor = Colors.grey[200]!; + textColor = Colors.grey[700]!; + break; + case TaskStatus.inProgress: + backgroundColor = Colors.blue[100]!; + textColor = Colors.blue[700]!; + break; + case TaskStatus.completed: + backgroundColor = Colors.green[100]!; + textColor = Colors.green[700]!; + break; + case TaskStatus.cancelled: + backgroundColor = Colors.red[100]!; + textColor = Colors.red[700]!; + break; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + status.value, + style: TextStyle( + color: textColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ); + } + + Widget _buildPriorityChip(TaskPriority priority) { + final Color color = _getPriorityColor(priority); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: color.withValues(alpha: 0.3)), + ), + child: Text( + priority.value, + style: TextStyle( + color: color, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ); + } + + Color _getPriorityColor(TaskPriority priority) { + switch (priority) { + case TaskPriority.low: + return Colors.green; + case TaskPriority.normal: + return Colors.orange; + case TaskPriority.high: + return Colors.red; + } + } + + String _formatDueDate(DateTime dueDate) { + final now = DateTime.now(); + final difference = dueDate.difference(now); + + if (difference.isNegative) { + final days = difference.inDays.abs(); + if (days == 0) { + return 'Overdue today'; + } else if (days == 1) { + return 'Overdue by 1 day'; + } else { + return 'Overdue by $days days'; + } + } else { + final days = difference.inDays; + if (days == 0) { + return 'Due today'; + } else if (days == 1) { + return 'Due tomorrow'; + } else if (days < 7) { + return 'Due in $days days'; + } else { + return 'Due ${dueDate.day}/${dueDate.month}/${dueDate.year}'; + } + } + } + + void _showTaskOptions(BuildContext context, Task task) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.visibility), + title: const Text('View Details'), + onTap: () async { + Navigator.pop(context); + final result = await Navigator.of( + context, + ).pushNamed('/task-detail', arguments: task.id); + // If task was deleted or modified, refresh the tasks list + if (result == true) { + _loadTasks(isRefresh: true); + } + }, + ), + ListTile( + leading: const Icon(Icons.edit), + title: const Text('Edit Task'), + onTap: () async { + Navigator.pop(context); + final result = await Navigator.of( + context, + ).pushNamed('/task-edit', arguments: task.id); + // If task was updated successfully, refresh the tasks list + if (result == true) { + _loadTasks(isRefresh: true); + } + }, + ), + if (task.status != TaskStatus.completed) + ListTile( + leading: const Icon(Icons.check_circle, color: Colors.green), + title: const Text('Mark as Complete'), + onTap: () { + Navigator.pop(context); + _markTaskComplete(task); + }, + ), + ListTile( + leading: const Icon(Icons.delete, color: Colors.red), + title: const Text( + 'Delete Task', + style: TextStyle(color: Colors.red), + ), + onTap: () { + Navigator.pop(context); + _showDeleteConfirmation(context, task); + }, + ), + ], + ), + ); + }, + ); + } + + void _showDeleteConfirmation(BuildContext context, Task task) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Delete Task'), + content: Text( + 'Are you sure you want to delete "${task.subject}"? This action cannot be undone.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + _deleteTask(task); + }, + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Delete'), + ), + ], + ); + }, + ); + } + + Future _markTaskComplete(Task task) async { + final success = await _tasksService.updateTask( + task.id, + status: TaskStatus.completed.value, + ); + + if (success != null) { + setState(() { + final index = _tasks.indexWhere((t) => t.id == task.id); + if (index != -1) { + _tasks[index] = success; + } + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Task "${task.subject}" marked as complete')), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Failed to update task'))); + } + } + } + + Future _deleteTask(Task task) async { + final success = await _tasksService.deleteTask(task.id); + + if (success) { + setState(() { + _tasks.removeWhere((t) => t.id == task.id); + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Task "${task.subject}" deleted successfully'), + ), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Failed to delete task'))); + } + } + } +} diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart new file mode 100644 index 0000000..9e80e44 --- /dev/null +++ b/lib/services/api_service.dart @@ -0,0 +1,281 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:http/http.dart' as http; +import '../config/api_config.dart'; +import 'auth_service.dart'; + +class ApiResponse { + final bool success; + final T? data; + final String? message; + final int statusCode; + final Map? errors; + + ApiResponse({ + required this.success, + this.data, + this.message, + required this.statusCode, + this.errors, + }); + + factory ApiResponse.success(T data, int statusCode) { + return ApiResponse(success: true, data: data, statusCode: statusCode); + } + + factory ApiResponse.error( + String message, + int statusCode, [ + Map? errors, + ]) { + return ApiResponse( + success: false, + message: message, + statusCode: statusCode, + errors: errors, + ); + } +} + +class ApiService { + static final ApiService _instance = ApiService._internal(); + factory ApiService() => _instance; + ApiService._internal(); + + final http.Client _client = http.Client(); + final AuthService _authService = AuthService(); + + Future> _getHeaders({bool requiresAuth = true}) async { + Map headers = Map.from(ApiConfig.defaultHeaders); + + if (requiresAuth) { + final token = await _authService.getJwtToken(); + if (token != null) { + headers['Authorization'] = 'Bearer $token'; + } + + // Add selected organization ID to headers for API calls + final selectedOrg = _authService.selectedOrganization; + if (selectedOrg != null) { + headers['X-Organization-ID'] = selectedOrg.id; + } + } + + return headers; + } + + Future> _handleResponse( + http.Response response, + T Function(Map) fromJson, + ) async { + try { + final Map body = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return ApiResponse.success(fromJson(body), response.statusCode); + } else { + // Handle unauthorized access - logout user + if (response.statusCode == 401) { + await _authService.logout(); + } + + return ApiResponse.error( + body['message'] ?? 'Request failed', + response.statusCode, + body['errors'], + ); + } + } catch (e) { + return ApiResponse.error( + 'Failed to parse response: $e', + response.statusCode, + ); + } + } + + Future>> _handleMapResponse( + http.Response response, + ) async { + return _handleResponse>(response, (json) => json); + } + + Future>>> _handleListResponse( + http.Response response, + ) async { + try { + final dynamic body = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + if (body is Map && body.containsKey('data')) { + final List data = body['data'] as List; + final List> result = data + .cast>(); + return ApiResponse.success(result, response.statusCode); + } else if (body is List) { + final List> result = body + .cast>(); + return ApiResponse.success(result, response.statusCode); + } + } + + final Map errorBody = body is Map + ? body + : {}; + return ApiResponse.error( + errorBody['message'] ?? 'Request failed', + response.statusCode, + errorBody['errors'], + ); + } catch (e) { + return ApiResponse.error( + 'Failed to parse response: $e', + response.statusCode, + ); + } + } + + // GET request + Future>> get( + String endpoint, { + Map? queryParams, + bool requiresAuth = true, + }) async { + try { + Uri uri = Uri.parse(endpoint); + if (queryParams != null && queryParams.isNotEmpty) { + uri = uri.replace(queryParameters: queryParams); + } + + final headers = await _getHeaders(requiresAuth: requiresAuth); + final response = await _client + .get(uri, headers: headers) + .timeout(ApiConfig.receiveTimeout); + + return _handleMapResponse(response); + } on SocketException { + return ApiResponse.error('No internet connection', 0); + } on HttpException { + return ApiResponse.error('HTTP error occurred', 0); + } catch (e) { + return ApiResponse.error('Request failed: $e', 0); + } + } + + // GET request for lists + Future>>> getList( + String endpoint, { + Map? queryParams, + bool requiresAuth = true, + }) async { + try { + Uri uri = Uri.parse(endpoint); + if (queryParams != null && queryParams.isNotEmpty) { + uri = uri.replace(queryParameters: queryParams); + } + + final headers = await _getHeaders(requiresAuth: requiresAuth); + final response = await _client + .get(uri, headers: headers) + .timeout(ApiConfig.receiveTimeout); + + return _handleListResponse(response); + } on SocketException { + return ApiResponse.error('No internet connection', 0); + } on HttpException { + return ApiResponse.error('HTTP error occurred', 0); + } catch (e) { + return ApiResponse.error('Request failed: $e', 0); + } + } + + // POST request + Future>> post( + String endpoint, + Map data, { + bool requiresAuth = true, + }) async { + try { + final headers = await _getHeaders(requiresAuth: requiresAuth); + final response = await _client + .post(Uri.parse(endpoint), headers: headers, body: json.encode(data)) + .timeout(ApiConfig.sendTimeout); + + return _handleMapResponse(response); + } on SocketException { + return ApiResponse.error('No internet connection', 0); + } on HttpException { + return ApiResponse.error('HTTP error occurred', 0); + } catch (e) { + return ApiResponse.error('Request failed: $e', 0); + } + } + + // PUT request + Future>> put( + String endpoint, + Map data, { + bool requiresAuth = true, + }) async { + try { + final headers = await _getHeaders(requiresAuth: requiresAuth); + final response = await _client + .put(Uri.parse(endpoint), headers: headers, body: json.encode(data)) + .timeout(ApiConfig.sendTimeout); + + return _handleMapResponse(response); + } on SocketException { + return ApiResponse.error('No internet connection', 0); + } on HttpException { + return ApiResponse.error('HTTP error occurred', 0); + } catch (e) { + return ApiResponse.error('Request failed: $e', 0); + } + } + + // DELETE request + Future>> delete( + String endpoint, { + bool requiresAuth = true, + }) async { + try { + final headers = await _getHeaders(requiresAuth: requiresAuth); + final response = await _client + .delete(Uri.parse(endpoint), headers: headers) + .timeout(ApiConfig.receiveTimeout); + + return _handleMapResponse(response); + } on SocketException { + return ApiResponse.error('No internet connection', 0); + } on HttpException { + return ApiResponse.error('HTTP error occurred', 0); + } catch (e) { + return ApiResponse.error('Request failed: $e', 0); + } + } + + // PATCH request + Future>> patch( + String endpoint, + Map data, { + bool requiresAuth = true, + }) async { + try { + final headers = await _getHeaders(requiresAuth: requiresAuth); + final response = await _client + .patch(Uri.parse(endpoint), headers: headers, body: json.encode(data)) + .timeout(ApiConfig.sendTimeout); + + return _handleMapResponse(response); + } on SocketException { + return ApiResponse.error('No internet connection', 0); + } on HttpException { + return ApiResponse.error('HTTP error occurred', 0); + } catch (e) { + return ApiResponse.error('Request failed: $e', 0); + } + } + + void dispose() { + _client.close(); + } +} diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart new file mode 100644 index 0000000..d4f6d21 --- /dev/null +++ b/lib/services/auth_service.dart @@ -0,0 +1,457 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import '../config/api_config.dart'; +import 'api_service.dart'; + +class OrganizationsPagination { + final int page; + final int limit; + final int total; + final int totalPages; + final bool hasNext; + final bool hasPrev; + + OrganizationsPagination({ + required this.page, + required this.limit, + required this.total, + required this.totalPages, + required this.hasNext, + required this.hasPrev, + }); + + factory OrganizationsPagination.fromJson(Map json) { + return OrganizationsPagination( + page: json['page'] ?? 1, + limit: json['limit'] ?? 10, + total: json['total'] ?? 0, + totalPages: json['totalPages'] ?? 1, + hasNext: json['hasNext'] ?? false, + hasPrev: json['hasPrev'] ?? false, + ); + } +} + +class Organization { + final String id; + final String name; + final String? domain; + final String? logo; + final String? website; + final String? industry; + final String? description; + final bool isActive; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? userRole; + final DateTime? joinedAt; + + Organization({ + required this.id, + required this.name, + this.domain, + this.logo, + this.website, + this.industry, + this.description, + this.isActive = true, + this.createdAt, + this.updatedAt, + this.userRole, + this.joinedAt, + }); + + // Backward compatibility getter for existing code + String get role => userRole ?? 'USER'; + + factory Organization.fromJson(Map json) { + return Organization( + id: json['id'] ?? '', + name: json['name'] ?? '', + domain: json['domain'], + logo: json['logo'], + website: json['website'], + industry: json['industry'], + description: json['description'], + isActive: json['isActive'] ?? true, + createdAt: json['createdAt'] != null + ? DateTime.tryParse(json['createdAt']) + : null, + updatedAt: json['updatedAt'] != null + ? DateTime.tryParse(json['updatedAt']) + : null, + userRole: json['userRole'] ?? json['role'], + joinedAt: json['joinedAt'] != null + ? DateTime.tryParse(json['joinedAt']) + : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'domain': domain, + 'logo': logo, + 'website': website, + 'industry': industry, + 'description': description, + 'isActive': isActive, + 'createdAt': createdAt?.toIso8601String(), + 'updatedAt': updatedAt?.toIso8601String(), + 'userRole': userRole ?? 'USER', + 'joinedAt': joinedAt?.toIso8601String(), + }; + } +} + +class User { + final String id; + final String email; + final String name; + final String? profileImage; + + User({ + required this.id, + required this.email, + required this.name, + this.profileImage, + }); + + factory User.fromJson(Map json) { + return User( + id: json['id'] ?? '', + email: json['email'] ?? '', + name: json['name'] ?? '', + profileImage: json['profileImage'], + ); + } + + Map toJson() { + return { + 'id': id, + 'email': email, + 'name': name, + 'profileImage': profileImage, + }; + } +} + +class AuthService { + static final AuthService _instance = AuthService._internal(); + factory AuthService() => _instance; + AuthService._internal(); + + // Initialize GoogleSignIn instance (singleton in v7.1.1) + final GoogleSignIn _googleSignIn = GoogleSignIn.instance; + + static const String _jwtTokenKey = 'jwt_token'; + static const String _userKey = 'user_data'; + static const String _organizationsKey = 'organizations'; + static const String _selectedOrgKey = 'selected_organization'; + + User? _currentUser; + String? _jwtToken; + List? _organizations; + Organization? _selectedOrganization; + OrganizationsPagination? _organizationsPagination; + + // Getters + User? get currentUser => _currentUser; + bool get isLoggedIn => _currentUser != null && _jwtToken != null; + String? get jwtToken => _jwtToken; + List? get organizations => _organizations; + Organization? get selectedOrganization => _selectedOrganization; + bool get hasSelectedOrganization => _selectedOrganization != null; + OrganizationsPagination? get organizationsPagination => + _organizationsPagination; + + // Initialize service - call this in main.dart + Future initialize() async { + try { + // Initialize Google Sign-In (required in v7.1.1) + await _googleSignIn.initialize(); + debugPrint('Google Sign-In initialized successfully'); + } catch (e) { + debugPrint('Google Sign-In initialization failed: $e'); + } + + await _loadDataFromStorage(); + } + + // Google Sign In + Future signInWithGoogle() async { + try { + debugPrint('Google Sign-In: Initiating authentication process...'); + + // Check if platform supports authenticate method + if (!_googleSignIn.supportsAuthenticate()) { + debugPrint( + 'Google Sign-In: Platform does not support authenticate method', + ); + return false; + } + + // Authenticate with Google (new method in v7.1.1) + final googleUser = await _googleSignIn.authenticate(); + + debugPrint('Google Sign-In: Authentication successful'); + debugPrint('User authenticated: ${googleUser.email}'); + + // Get authentication token from Google + final authentication = googleUser.authentication; + final idToken = authentication.idToken; + + if (idToken == null) { + debugPrint('Google Sign-In: Failed to get ID token'); + return false; + } + + debugPrint('Google Sign-In: Got ID token, sending to backend...'); + + // Send Google token to backend + final apiService = ApiService(); + final response = await apiService.post(ApiConfig.googleLogin, { + 'idToken': idToken, + }, requiresAuth: false); + + if (!response.success || response.data == null) { + debugPrint( + 'Google Sign-In: Backend authentication failed: ${response.message}', + ); + return false; + } + + await _handleAuthResponse(response.data!); + + // Log user info after successful authentication + if (_currentUser != null) { + debugPrint('User ID: ${_currentUser!.id}'); + debugPrint('User Email: ${_currentUser!.email}'); + debugPrint('User Name: ${_currentUser!.name}'); + debugPrint('User Profile Image: ${_currentUser!.profileImage}'); + debugPrint( + 'Organizations: ${_organizations?.map((org) => '${org.name} (${org.role})').join(', ')}', + ); + } + + return true; + } catch (e) { + debugPrint('Google Sign In Error: $e'); + return false; + } + } + + // Handle authentication response from backend + Future _handleAuthResponse(Map data) async { + _jwtToken = data['JWTtoken']; + + if (data['user'] != null) { + _currentUser = User.fromJson(data['user']); + } + + if (data['organizations'] != null) { + _organizations = (data['organizations'] as List) + .map((org) => Organization.fromJson(org)) + .toList(); + } + + // Clear any previously selected organization on fresh login + // User must select a company again + _selectedOrganization = null; + await _clearSelectedOrganization(); + + await _saveDataToStorage(); + } + + // Select organization for API calls + Future selectOrganization(Organization organization) async { + _selectedOrganization = organization; + await _saveSelectedOrganization(); + debugPrint( + 'Selected organization: ${organization.name} (${organization.role})', + ); + } + + // Fetch organizations from API with pagination support + Future fetchOrganizations({ + int page = 1, + int limit = 10, + bool append = false, + }) async { + try { + debugPrint( + 'Fetching organizations from API (page: $page, limit: $limit)...', + ); + + final apiService = ApiService(); + final queryParams = {'page': page.toString(), 'limit': limit.toString()}; + + final response = await apiService.get( + ApiConfig.organizations, + queryParams: queryParams, + requiresAuth: true, + ); + + if (response.success && response.data != null) { + final data = response.data!; + if (data['success'] == true && data['organizations'] != null) { + final organizationsData = data['organizations'] as List; + final newOrganizations = organizationsData + .map((org) => Organization.fromJson(org)) + .toList(); + + // Handle pagination info + if (data['pagination'] != null) { + _organizationsPagination = OrganizationsPagination.fromJson( + data['pagination'], + ); + } + + // Append or replace organizations + if (append && _organizations != null) { + _organizations!.addAll(newOrganizations); + } else { + _organizations = newOrganizations; + } + + // Save to storage for offline access + await _saveDataToStorage(); + + debugPrint( + 'Fetched ${newOrganizations.length} organizations (total: ${_organizations!.length})', + ); + return true; + } + } + + debugPrint('Failed to fetch organizations: ${response.message}'); + return false; + } catch (e) { + debugPrint('Error fetching organizations: $e'); + return false; + } + } + + // Refresh organizations (fetch and update) + Future refreshOrganizations() async { + return await fetchOrganizations(); + } + + // Load more organizations (for pagination) + Future loadMoreOrganizations() async { + if (_organizationsPagination?.hasNext == true) { + final nextPage = (_organizationsPagination?.page ?? 0) + 1; + return await fetchOrganizations(page: nextPage, append: true); + } + return false; + } + + // Get JWT token for API calls + Future getJwtToken() async { + return _jwtToken; + } + + // Logout + Future logout() async { + try { + // Notify backend about logout + if (_jwtToken != null) { + final apiService = ApiService(); + await apiService.post(ApiConfig.logout, {}, requiresAuth: true); + } + } catch (e) { + debugPrint('Logout error: $e'); + } finally { + // Clear all local authentication data + await _clearDataFromStorage(); + + _currentUser = null; + _jwtToken = null; + _organizations = null; + _selectedOrganization = null; + _organizationsPagination = null; + + debugPrint('Logout: All authentication data cleared'); + } + } + + // Storage methods + Future _saveDataToStorage() async { + final prefs = await SharedPreferences.getInstance(); + + if (_jwtToken != null) { + await prefs.setString(_jwtTokenKey, _jwtToken!); + } + + if (_currentUser != null) { + await prefs.setString(_userKey, json.encode(_currentUser!.toJson())); + } + + if (_organizations != null) { + final orgsJson = _organizations!.map((org) => org.toJson()).toList(); + await prefs.setString(_organizationsKey, json.encode(orgsJson)); + } + } + + Future _saveSelectedOrganization() async { + if (_selectedOrganization != null) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString( + _selectedOrgKey, + json.encode(_selectedOrganization!.toJson()), + ); + } + } + + Future _clearSelectedOrganization() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_selectedOrgKey); + } + + Future _loadDataFromStorage() async { + final prefs = await SharedPreferences.getInstance(); + + _jwtToken = prefs.getString(_jwtTokenKey); + + final userString = prefs.getString(_userKey); + if (userString != null) { + final userData = json.decode(userString); + _currentUser = User.fromJson(userData); + } + + final orgsString = prefs.getString(_organizationsKey); + if (orgsString != null) { + final orgsData = json.decode(orgsString) as List; + _organizations = orgsData + .map((org) => Organization.fromJson(org)) + .toList(); + } + + final selectedOrgString = prefs.getString(_selectedOrgKey); + if (selectedOrgString != null) { + final selectedOrgData = json.decode(selectedOrgString); + _selectedOrganization = Organization.fromJson(selectedOrgData); + } + } + + Future _clearDataFromStorage() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_jwtTokenKey); + await prefs.remove(_userKey); + await prefs.remove(_organizationsKey); + await prefs.remove(_selectedOrgKey); + } + + // Development helper method to clear all stored auth data + Future clearAllAuthData() async { + await _clearDataFromStorage(); + _currentUser = null; + _jwtToken = null; + _organizations = null; + _selectedOrganization = null; + debugPrint('All authentication data cleared'); + } +} diff --git a/lib/services/contacts_service.dart b/lib/services/contacts_service.dart new file mode 100644 index 0000000..87527f5 --- /dev/null +++ b/lib/services/contacts_service.dart @@ -0,0 +1,169 @@ +import '../config/api_config.dart'; +import '../models/api_models.dart'; +import 'api_service.dart'; +import 'auth_service.dart'; + +class ContactsService { + final ApiService _apiService = ApiService(); + final AuthService _authService = AuthService(); + + Future getContacts({ + int page = 1, + int limit = 20, + String? search, + }) async { + try { + if (!_authService.isLoggedIn) { + throw Exception('User not authenticated'); + } + + final Map queryParams = { + 'page': page.toString(), + 'limit': limit.toString(), + }; + + if (search != null && search.isNotEmpty) { + queryParams['search'] = search; + } + + final response = await _apiService.get( + ApiConfig.contacts, + queryParams: queryParams, + ); + + if (response.success && response.data != null) { + return ContactsResponse.fromJson(response.data!); + } else { + throw Exception(response.message ?? 'Failed to fetch contacts'); + } + } catch (e) { + // Error fetching contacts: $e + return null; + } + } + + Future getContactById(String contactId) async { + try { + if (!_authService.isLoggedIn) { + throw Exception('User not authenticated'); + } + + final url = ApiConfig.replacePathParam( + ApiConfig.contactById, + 'id', + contactId, + ); + + final response = await _apiService.get(url); + + if (response.success && response.data != null) { + return Contact.fromJson(response.data!); + } else { + throw Exception(response.message ?? 'Failed to fetch contact'); + } + } catch (e) { + // Error fetching contact: $e + return null; + } + } + + Future searchContacts( + String query, { + int page = 1, + int limit = 20, + }) async { + try { + if (!_authService.isLoggedIn) { + throw Exception('User not authenticated'); + } + + final Map queryParams = { + 'q': query, + 'page': page.toString(), + 'limit': limit.toString(), + }; + + final response = await _apiService.get( + ApiConfig.contactSearch, + queryParams: queryParams, + ); + + if (response.success && response.data != null) { + return ContactsResponse.fromJson(response.data!); + } else { + throw Exception(response.message ?? 'Failed to search contacts'); + } + } catch (e) { + // Error searching contacts: $e + return null; + } + } + + Future createContact(Map contactData) async { + try { + if (!_authService.isLoggedIn) { + throw Exception('User not authenticated'); + } + + final response = await _apiService.post(ApiConfig.contacts, contactData); + + if (response.success && response.data != null) { + return Contact.fromJson(response.data!); + } else { + throw Exception(response.message ?? 'Failed to create contact'); + } + } catch (e) { + // Error creating contact: $e + return null; + } + } + + Future updateContact( + String contactId, + Map contactData, + ) async { + try { + if (!_authService.isLoggedIn) { + throw Exception('User not authenticated'); + } + + final url = ApiConfig.replacePathParam( + ApiConfig.contactById, + 'id', + contactId, + ); + + final response = await _apiService.put(url, contactData); + + if (response.success && response.data != null) { + return Contact.fromJson(response.data!); + } else { + throw Exception(response.message ?? 'Failed to update contact'); + } + } catch (e) { + // Error updating contact: $e + return null; + } + } + + Future deleteContact(String contactId) async { + try { + if (!_authService.isLoggedIn) { + throw Exception('User not authenticated'); + } + + final url = ApiConfig.replacePathParam( + ApiConfig.contactById, + 'id', + contactId, + ); + + final response = await _apiService.delete(url); + + return response.success; + } catch (e) { + // Error deleting contact: $e + return false; + } + } +} diff --git a/lib/services/crm_services.dart b/lib/services/crm_services.dart deleted file mode 100644 index facd7a5..0000000 --- a/lib/services/crm_services.dart +++ /dev/null @@ -1,1043 +0,0 @@ -// import 'package:bottle_crm/bloc/setting_bloc.dart'; -// import 'package:bottle_crm/model/document.dart'; -import 'dart:io'; - -import 'package:file_picker/file_picker.dart'; -import 'package:http/http.dart'; -import 'package:http/http.dart' as http; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:bottle_crm/ui/screens/http_excepion.dart'; - -import 'network_services.dart'; - -class CrmService { - NetworkService networkService = NetworkService(); - // final baseUrl = 'https://bottlecrm.com/api/'; - final baseUrl = 'https://api.bottle-dev.com/api/'; - Map _headers = {}; - - updateHeaders() async { - final SharedPreferences preferences = await SharedPreferences.getInstance(); - _headers['Authorization'] = preferences.getString('authToken'); - if (preferences.getString('org') != null) { - _headers['org'] = preferences.getString('org'); - } - } - - getFormatedHeaders(headers) { - return new Map.from(headers); - } - - Future userRegister(data) async { - try { - return await networkService.post(Uri.parse(baseUrl + 'auth/register/'), - body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } on HandshakeException { - throw HttpException("Server Issue"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future userLogin(body) async { - try { - return await networkService.post(Uri.parse(baseUrl + 'auth/login/'), - body: body); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } on FormatException { - throw HttpException("server issue"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future validateSubdomain(data) async { - try { - return await networkService - .post(Uri.parse(baseUrl + 'auth/validate-subdomain/'), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future forgotPassword(data) async { - try { - return await networkService - .post(Uri.parse(baseUrl + 'auth/forgot-password/'), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future getUserProfile() async { - try { - await updateHeaders(); - return await networkService.get(Uri.parse(baseUrl + 'profile/'), - headers: await getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future changePassword(data) async { - try { - await updateHeaders(); - return await networkService.post( - Uri.parse(baseUrl + 'profile/change-password/'), - headers: getFormatedHeaders(_headers), - body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future getCompanies() async { - try { - await updateHeaders(); - return await networkService.get( - Uri.parse(baseUrl + 'auth/companies-list/'), - headers: await getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future getDashboardDetails() async { - try { - await updateHeaders(); - return await networkService.get(Uri.parse(baseUrl + 'dashboard/'), - headers: await getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - ///////////////////// ACCONUTS-SERVICES //////////////////////////// - - Future getAccounts({queryParams, offset}) async { - await updateHeaders(); - var url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'accounts/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - url = Uri.parse(baseUrl + 'accounts/'); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + "?offset=$offset"); - } - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future deleteAccount(id) async { - try { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'accounts/$id/'), - headers: getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future createAccount(data, File file) async { - try { - if (file.path == "") { - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'accounts/'), - headers: getFormatedHeaders(_headers), body: data); - } else { - var uri = Uri.parse( - baseUrl + 'accounts/', - ); - var request = http.MultipartRequest( - 'POST', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll(Map.from(data)) - ..files.add( - await http.MultipartFile.fromPath('document_file', 'assets/images/sentry_logo.png')); - var response = await request.send(); - return await http.Response.fromStream(response); - } - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editAccount(data, id) async { - try { - await updateHeaders(); - return await networkService.put(Uri.parse(baseUrl + 'accounts/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future getToEditAccount(id) async { - await updateHeaders(); - return await networkService.get(baseUrl + 'accounts/$id/', - headers: getFormatedHeaders(_headers)); - } - - ///////////////////// CONTACTS-SERVICES /////////////////////////////// - - Future getContacts({queryParams, offset}) async { - await updateHeaders(); - var url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'contacts/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - url = Uri.parse(baseUrl + 'contacts/'); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '?offset=$offset'); - } - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future createContact(data, File file) async { - try { - if (file.path == "") { - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'contacts/'), - headers: getFormatedHeaders(_headers), body: data); - } else { - var uri = Uri.parse( - baseUrl + 'contacts/', - ); - var request = http.MultipartRequest( - 'POST', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll(Map.from(data)) - ..files.add( - await http.MultipartFile.fromPath('document_file', file.path)); - var response = await request.send(); - return await http.Response.fromStream(response); - } - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editContact(data, id) async { - try { - await updateHeaders(); - return await networkService.put(Uri.parse(baseUrl + 'contacts/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future deleteContact(id) async { - try { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'contacts/$id/'), - headers: getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - ///////////////////// LEADS-SERVICES /////////////////////////////// - - Future getLeads({queryParams, offset}) async { - await updateHeaders(); - var url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = Uri( - queryParameters: getFormatedHeaders(queryParams), - ).query; - url = Uri.parse(baseUrl + 'leads/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - url = Uri.parse(baseUrl + 'leads/'); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + "?offset=$offset"); - } - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future getLeadToUpdate(leadId) async { - await updateHeaders(); - return await networkService.get(baseUrl + 'leads/$leadId/', - headers: getFormatedHeaders(_headers)); - } - - Future createLead(data, File file) async { - try { - if (file.path == "") { - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'leads/'), - headers: getFormatedHeaders(_headers), body: data); - } else { - await updateHeaders(); - var uri = Uri.parse( - baseUrl + 'leads/', - ); - var request = http.MultipartRequest( - 'POST', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll(Map.from(data)) - ..files.add( - await http.MultipartFile.fromPath('document_file', file.path)); - var response = await request.send(); - return await http.Response.fromStream(response); - } - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editLead(data, id) async { - try { - await updateHeaders(); - return await networkService.put(Uri.parse(baseUrl + 'leads/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future deleteLead(id) async { - try { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'leads/$id/'), - headers: getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - ///////////////////// USERS-SERVICES /////////////////////////////// - - Future getUsers({Map? queryParams}) async { - await updateHeaders(); - Uri url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'users/' + '?' + queryString); - } else { - url = Uri.parse(baseUrl + 'users/'); - } - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future deleteUser(id) async { - try { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'users/$id/'), - headers: getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future createUser(user, File file) async { - try { - if (file.path == "") { - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'users/'), - headers: getFormatedHeaders(_headers), body: user); - } else { - await updateHeaders(); - var uri = Uri.parse( - baseUrl + 'users//', - ); - var request = http.MultipartRequest( - 'POST', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll(Map.from(user)) - ..files.add( - await http.MultipartFile.fromPath('document_file', file.path)); - var response = await request.send(); - return await http.Response.fromStream(response); - } - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editUser(user, id) async { - try { - await updateHeaders(); - return await networkService.put(Uri.parse(baseUrl + 'users/$id/'), - headers: getFormatedHeaders(_headers), body: user); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - - ///////////////////// Events-SERVICES /////////////////////////////// - - Future getEvents({Map? queryParams}) async { - await updateHeaders(); - Uri url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'events/' + '?' + queryString); - } else { - url = Uri.parse(baseUrl + 'events/'); - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future deleteEvent(id) async { - try { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'events/$id/'), - headers: getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future createEvent(user) async { - try { - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'events/'), - headers: getFormatedHeaders(_headers), body: user); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editEvent(user, id) async { - try { - await updateHeaders(); - return await networkService.put(Uri.parse(baseUrl + 'events/$id/'), - headers: getFormatedHeaders(_headers), body: user); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - ///////////////////// DOCUMENTS-SERVICES /////////////////////////////// - - Future getDocuments({queryParams}) async { - await updateHeaders(); - String url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = Uri( - queryParameters: getFormatedHeaders(queryParams), - ).query; - url = baseUrl + 'documents/' + '?' + queryString; - } else { - url = baseUrl + 'documents/'; - } - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - getFileSizes(files) { - List _fileSizeList = []; - // files.forEach((Document file) async { - // http.Response r = await http.head(Uri.parse(file.documentFile)); - // if (r.headers['content-length'] != null) { - // String fileSize = r.headers['content-length'].toString(); - // _fileSizeList.add([file.id, fileSize]); - // } else { - // _fileSizeList.add([file.id, "0"]); - // } - // }); - // print(_fileSizeList); - return _fileSizeList; - } - - Future createDocument(document, PlatformFile file) async { - try { - await updateHeaders(); - var uri = Uri.parse( - baseUrl + 'documents/', - ); - var request = http.MultipartRequest( - 'POST', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll({ - 'title': document['title'], - 'teams': document['teams'], - 'shared_to': document['shared_to'] - }) - ..files.add( - await http.MultipartFile.fromPath('document_file', file.path!)); - final response = await request.send(); - return await response.stream.bytesToString(); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editDocument(document, PlatformFile file, id) async { - await updateHeaders(); - var uri = Uri.parse( - baseUrl + 'documents/$id/', - ); - var request = http.MultipartRequest( - 'PUT', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll({ - 'title': document['title'], - 'teams': document['teams'], - 'shared_to': document['shared_to'], - 'status': document['status'] - }) - ..files - .add(await http.MultipartFile.fromPath('document_file', file.path!)); - return await request.send(); - } - - Future deleteDocument(id) async { - try { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'documents/$id/'), - headers: getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - ///////////////////// TEAMS-SERVICES /////////////////////////////// - - Future getTeams({queryParams, offset}) async { - await updateHeaders(); - Uri url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value.runtimeType != String); - queryParams.removeWhere((key, value) => value == "[]"); - queryParams.removeWhere((key, value) => value == ""); - queryParams.removeWhere((key, value) => value == null); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'teams/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - url = Uri.parse(baseUrl + 'teams/'); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '?offset=$offset'); - } - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future createTeam(data) async { - try { - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'teams/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future deleteTeam(id) async { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'teams/$id/'), - headers: getFormatedHeaders(_headers)); - } - - Future editTeam( - data, - id, - ) async { - await updateHeaders(); - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - return await networkService.put(Uri.parse(baseUrl + 'teams/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } - - ///////////////////// OPPORTUNITIES-SERVICES //////////////////////////// - - Future getOpportunities({queryParams, offset}) async { - await updateHeaders(); - Uri url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'opportunities/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - url = Uri.parse(baseUrl + 'opportunities/'); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '?offset=$offset'); - } - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future deletefromModule(moduleName, id) async { - try { - await updateHeaders(); - return await networkService.delete( - Uri.parse(baseUrl + '$moduleName/$id/'), - headers: getFormatedHeaders(_headers)); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - // Future createOpportunity(opportunity, [PlatformFile file]) async { - // file = null; - // await updateHeaders(); - // var uri = Uri.parse( - // baseUrl + 'opportunities/', - // ); - // var request = http.MultipartRequest( - // 'POST', - // uri, - // ) - // ..headers.addAll(getFormatedHeaders(_headers)) - // ..fields.addAll({ - // 'name': opportunity['name'], - // 'account': opportunity['account'], - // 'amount': opportunity['amount'], - // 'currency': opportunity['currency'], - // 'stage': opportunity['stage'], - // 'lead_source': opportunity['lead_source'], - // 'probability': opportunity['probability'], - // 'description': opportunity['description'], - // 'teams': opportunity['teams'], - // 'assigned_to': opportunity['assigned_to'], - // 'contacts': opportunity['contacts'], - // 'due_date': opportunity['due_date'], - // 'tags': opportunity['tags'], - // }); - // if (file != null) { - // request.files.add(await http.MultipartFile.fromPath( - // 'opportunity_attachment', file.path)); - // } - // final response = await request.send(); - // return await response.stream.bytesToString(); - // } - - Future createOpportunity(data, File file) async { - try { - if (file.path == "") { - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'opportunities/'), - headers: getFormatedHeaders(_headers), body: data); - } else { - await updateHeaders(); - var uri = Uri.parse( - baseUrl + 'opportunities/', - ); - var request = http.MultipartRequest( - 'POST', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll(Map.from(data)) - ..files.add( - await http.MultipartFile.fromPath('document_file', file.path)); - var response = await request.send(); - return await http.Response.fromStream(response); - } - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editOpportunity(data, id, [PlatformFile? file]) async { - try { - await updateHeaders(); - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - return await networkService.put(Uri.parse(baseUrl + 'opportunities/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - // Future editOpportunity(opportunity, id, [PlatformFile file]) async { - // await updateHeaders(); - // var uri = Uri.parse( - // baseUrl + 'opportunities`/$id/', - // ); - - // var request = http.MultipartRequest( - // 'PUT', - // uri, - // ) - // ..headers.addAll(getFormatedHeaders(_headers)) - // ..fields.addAll({ - // 'name': opportunity['name'], - // 'account': opportunity['account'], - // 'amount': opportunity['amount'], - // 'currency': opportunity['currency'], - // 'stage': opportunity['stage'], - // 'lead_source': opportunity['lead_source'], - // 'probability': opportunity['probability'], - // 'description': opportunity['description'], - // 'teams': opportunity['teams'], - // 'assigned_to': opportunity['assigned_to'], - // 'contacts': opportunity['contacts'], - // 'tags': opportunity['tags'], - // }) - // // ..files.add(await http.MultipartFile.fromPath( - // // 'opportunity_attachment', file.path)) - // ; - // final response = await request.send(); - // return await response.stream.bytesToString(); - - ///////////////////// TASKS-SERVICES /////////////////////////////// - - Future getTasks({queryParams, offset}) async { - await updateHeaders(); - Uri? url; - if (queryParams != null) { - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'tasks/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '?offset=$offset'); - } - url = Uri.parse(baseUrl + 'tasks/'); - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future createTask(data) async { - try { - await updateHeaders(); - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - return await networkService.post(Uri.parse(baseUrl + 'tasks/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editTask(data, id) async { - await updateHeaders(); - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - return await networkService.put(Uri.parse(baseUrl + 'tasks/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } - - Future deleteTask(id) async { - await updateHeaders(); - return await networkService.delete(Uri.parse(baseUrl + 'tasks/$id/'), - headers: getFormatedHeaders(_headers)); - } - - ///////////////////// SETTINGS-SERVICES /////////////////////////////// - - Future getApiSettings({queryParams, offset}) async { - await updateHeaders(); - Uri? url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'api-settings/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '?offset=$offset'); - } - url = Uri.parse(baseUrl + 'api-settings/'); - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - /// CONTACTS - Future getSettingsContacts({queryParams}) async { - await updateHeaders(); - String url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = baseUrl + 'settings/contacts/' + '?' + queryString; - } else { - url = baseUrl + 'settings/contacts/'; - } - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future deleteSettingsContacts(id) async { - await updateHeaders(); - return await networkService.delete( - Uri.parse(baseUrl + 'settings/contacts/$id/'), - headers: getFormatedHeaders(_headers)); - } - - /// BLOCKED DOMAINS - Future getBlockedDomains({queryParams}) async { - await updateHeaders(); - String url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = baseUrl + 'settings/block-domains/' + '?' + queryString; - } else { - url = baseUrl + 'settings/block-domains/'; - } - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future deleteBlockedDomains(id) async { - await updateHeaders(); - return await networkService.delete( - Uri.parse(baseUrl + 'settings/block-domains/$id/'), - headers: getFormatedHeaders(_headers)); - } - - /// BLOCKED EMAILS - Future getBlockedEmails({queryParams}) async { - await updateHeaders(); - String url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = baseUrl + 'settings/block-emails/' + '?' + queryString; - } else { - url = baseUrl + 'settings/block-emails/'; - } - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future deleteBlockedEmails(id) async { - await updateHeaders(); - return await networkService.delete( - Uri.parse(baseUrl + 'settings/block-emails/$id/'), - headers: getFormatedHeaders(_headers)); - } - - Future createSetting(data) async { - try { - await updateHeaders(); - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - String _url; - // if (settingsBloc.currentSettingsTab == "Contacts") { - _url = '/settings/contacts'; - // } else if (settingsBloc.currentSettingsTab == "Blocked Domains") { - // _url = '/settings/block-domains'; - // } else { - // _url = '/settings/block-emails'; - // } - return await networkService.post(Uri.parse(baseUrl + '$_url/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editSetting(data, id) async { - await updateHeaders(); - data.removeWhere((key, value) => value == "[]"); - data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - String _url; - // if (settingsBloc.currentSettingsTab == "Contacts") { - _url = '/settings/contacts'; - // } else if (settingsBloc.currentSettingsTab == "Blocked Domains") { - // _url = '/settings/block-domains'; - // } else { - // _url = '/settings/block-emails'; - // } - return await networkService.put(Uri.parse(baseUrl + '$_url/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } - - ///////////////////// CASES-SERVICES /////////////////////////////// - - Future getCases({queryParams, offset}) async { - await updateHeaders(); - Uri url; - if (queryParams != null) { - queryParams.removeWhere((key, value) => value == ""); - queryParams.removeWhere((key, value) => value == null); - queryParams.removeWhere((key, value) => value == []); - - String queryString = - Uri(queryParameters: getFormatedHeaders(queryParams)).query; - url = Uri.parse(baseUrl + 'cases/' + '?' + queryString); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '&offset=$offset'); - } - } else { - url = Uri.parse(baseUrl + 'cases/'); - if (offset != null && offset != "") { - url = Uri.parse(url.toString() + '?offset=$offset'); - } - } - print(url); - return await networkService.get(url, headers: getFormatedHeaders(_headers)); - } - - Future createCase(data, File file) async { - try { - if (file.path == "") { - // data.removeWhere((key, value) => value == "[]"); - // data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - await updateHeaders(); - return await networkService.post(Uri.parse(baseUrl + 'cases/'), - headers: getFormatedHeaders(_headers), body: data); - } else { - await updateHeaders(); - var uri = Uri.parse( - baseUrl + 'leads/', - ); - var request = http.MultipartRequest( - 'POST', - uri, - ) - ..headers.addAll(getFormatedHeaders(_headers)) - ..fields.addAll(Map.from(data)) - ..files.add( - await http.MultipartFile.fromPath('document_file', file.path)); - var response = await request.send(); - return await http.Response.fromStream(response); - } - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } - - Future editCase(data, id, [PlatformFile? file]) async { - try { - await updateHeaders(); - // data.removeWhere((key, value) => value == "[]"); - // data.removeWhere((key, value) => value == ""); - data.removeWhere((key, value) => value == null); - return await networkService.put(Uri.parse(baseUrl + 'cases/$id/'), - headers: getFormatedHeaders(_headers), body: data); - } on SocketException { - throw HttpException("Network Error, check your internet"); - } catch (e) { - throw HttpException(e.toString()); - } - } -} diff --git a/lib/services/dashboard_service.dart b/lib/services/dashboard_service.dart new file mode 100644 index 0000000..d7a30dc --- /dev/null +++ b/lib/services/dashboard_service.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import '../models/api_models.dart'; +import '../config/api_config.dart'; +import 'api_service.dart'; + +class DashboardService { + static final DashboardService _instance = DashboardService._internal(); + factory DashboardService() => _instance; + DashboardService._internal(); + + DashboardMetrics? _dashboardMetrics; + + DashboardMetrics? get dashboardMetrics => _dashboardMetrics; + + Future loadDashboardData() async { + try { + final apiService = ApiService(); + final response = await apiService.get(ApiConfig.dashboard); + + if (response.success && response.data != null) { + _dashboardMetrics = DashboardMetrics.fromJson(response.data!); + debugPrint( + 'Dashboard data loaded: ${_dashboardMetrics!.totalContacts} contacts, ${_dashboardMetrics!.totalLeads} leads', + ); + debugPrint( + 'Opportunities: ${_dashboardMetrics!.totalOpportunities}, Revenue: \$${_dashboardMetrics!.opportunityRevenue}', + ); + return _dashboardMetrics; + } else { + debugPrint('Failed to load dashboard data: ${response.message}'); + return null; + } + } catch (e) { + debugPrint('Dashboard service error: $e'); + return null; + } + } + + void clearDashboardData() { + _dashboardMetrics = null; + } +} diff --git a/lib/services/leads_service.dart b/lib/services/leads_service.dart new file mode 100644 index 0000000..8dec095 --- /dev/null +++ b/lib/services/leads_service.dart @@ -0,0 +1,254 @@ +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../config/api_config.dart'; +import '../models/api_models.dart'; +import 'api_service.dart'; + +class LeadsService { + static final LeadsService _instance = LeadsService._internal(); + factory LeadsService() => _instance; + LeadsService._internal(); + + final ApiService _apiService = ApiService(); + + // Cache for leads metadata (if any) + List? _cachedStatuses; + List? _cachedSources; + List? _cachedRatings; + List? _cachedIndustries; + + // Initialize service - load cached metadata + Future initialize() async { + await _loadCachedMetadata(); + } + + // Load leads with pagination + Future getLeads({ + int page = 1, + int limit = 10, + String? status, + String? leadSource, + String? rating, + String? industry, + String? searchQuery, + bool? converted, + }) async { + try { + final queryParams = { + 'page': page.toString(), + 'limit': limit.toString(), + }; + + if (status != null && status.isNotEmpty) { + queryParams['status'] = status; + } + if (leadSource != null && leadSource.isNotEmpty) { + queryParams['leadSource'] = leadSource; + } + if (rating != null && rating.isNotEmpty) { + queryParams['rating'] = rating; + } + if (industry != null && industry.isNotEmpty) { + queryParams['industry'] = industry; + } + if (searchQuery != null && searchQuery.isNotEmpty) { + queryParams['search'] = searchQuery; + } + if (converted != null) { + queryParams['converted'] = converted.toString(); + } + + final queryString = queryParams.entries + .map((e) => '${e.key}=${Uri.encodeComponent(e.value)}') + .join('&'); + + final url = queryString.isNotEmpty + ? '${ApiConfig.leads}?$queryString' + : ApiConfig.leads; + + debugPrint('LeadsService: Fetching leads from $url'); + + final response = await _apiService.get(url); + + if (response.success && response.data != null) { + final leadsResponse = LeadsResponse.fromJson(response.data!); + debugPrint( + 'LeadsService: Successfully loaded ${leadsResponse.leads.length} leads', + ); + return leadsResponse; + } else { + debugPrint('LeadsService: Failed to load leads - ${response.message}'); + return null; + } + } catch (e) { + debugPrint('LeadsService: Error loading leads - $e'); + return null; + } + } + + // Get a single lead by ID + Future getLeadById(String id) async { + try { + final url = ApiConfig.leadById.replaceAll('{id}', id); + final response = await _apiService.get(url); + + if (response.success && response.data != null) { + return Lead.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('LeadsService: Error loading lead $id - $e'); + return null; + } + } + + // Search leads + Future searchLeads({ + required String query, + int page = 1, + int limit = 10, + }) async { + try { + final queryParams = { + 'q': query, + 'page': page.toString(), + 'limit': limit.toString(), + }; + + final queryString = queryParams.entries + .map((e) => '${e.key}=${Uri.encodeComponent(e.value)}') + .join('&'); + + final url = '${ApiConfig.leadSearch}?$queryString'; + final response = await _apiService.get(url); + + if (response.success && response.data != null) { + return LeadsResponse.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('LeadsService: Error searching leads - $e'); + return null; + } + } + + // Create a new lead + Future createLead(Map leadData) async { + try { + final response = await _apiService.post(ApiConfig.leads, leadData); + + if (response.success && response.data != null) { + return Lead.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('LeadsService: Error creating lead - $e'); + return null; + } + } + + // Update an existing lead + Future updateLead(String id, Map leadData) async { + try { + final url = ApiConfig.leadById.replaceAll('{id}', id); + final response = await _apiService.put(url, leadData); + + if (response.success && response.data != null) { + return Lead.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('LeadsService: Error updating lead - $e'); + return null; + } + } + + // Delete a lead + Future deleteLead(String id) async { + try { + final url = ApiConfig.leadById.replaceAll('{id}', id); + final response = await _apiService.delete(url); + return response.success; + } catch (e) { + debugPrint('LeadsService: Error deleting lead - $e'); + return false; + } + } + + // Convert lead to contact/account/opportunity + Future convertLead( + String id, + Map conversionData, + ) async { + try { + final url = ApiConfig.leadConvert.replaceAll('{id}', id); + final response = await _apiService.post(url, conversionData); + return response.success; + } catch (e) { + debugPrint('LeadsService: Error converting lead - $e'); + return false; + } + } + + // Load cached metadata from SharedPreferences + Future _loadCachedMetadata() async { + try { + final prefs = await SharedPreferences.getInstance(); + + final statusesJson = prefs.getString('leads_statuses'); + if (statusesJson != null) { + final statusesList = jsonDecode(statusesJson) as List; + _cachedStatuses = statusesList.cast(); + } + + final sourcesJson = prefs.getString('leads_sources'); + if (sourcesJson != null) { + final sourcesList = jsonDecode(sourcesJson) as List; + _cachedSources = sourcesList.cast(); + } + + final ratingsJson = prefs.getString('leads_ratings'); + if (ratingsJson != null) { + final ratingsList = jsonDecode(ratingsJson) as List; + _cachedRatings = ratingsList.cast(); + } + + final industriesJson = prefs.getString('leads_industries'); + if (industriesJson != null) { + final industriesList = jsonDecode(industriesJson) as List; + _cachedIndustries = industriesList.cast(); + } + + debugPrint('LeadsService: Loaded cached metadata'); + } catch (e) { + debugPrint('LeadsService: Error loading cached metadata - $e'); + } + } + + // Clear all cached data (call this on logout or organization change) + Future clearCache() async { + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('leads_statuses'); + await prefs.remove('leads_sources'); + await prefs.remove('leads_ratings'); + await prefs.remove('leads_industries'); + + _cachedStatuses = null; + _cachedSources = null; + _cachedRatings = null; + _cachedIndustries = null; + + debugPrint('LeadsService: Cleared all cached data'); + } catch (e) { + debugPrint('LeadsService: Error clearing cache - $e'); + } + } + + // Getters for cached metadata + List? get statuses => _cachedStatuses; + List? get sources => _cachedSources; + List? get ratings => _cachedRatings; + List? get industries => _cachedIndustries; +} diff --git a/lib/services/network_services.dart b/lib/services/network_services.dart deleted file mode 100644 index fa4630a..0000000 --- a/lib/services/network_services.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:io'; - -import 'package:http/http.dart' as http; -import 'package:bottle_crm/ui/screens/http_excepion.dart'; - -class NetworkService { - static NetworkService _instance = new NetworkService.internal(); - factory NetworkService() => _instance; - http.Response? response; - var client = http.Client(); - - NetworkService.internal(); - - Future get(var url, {Map? headers}) async { - try { - return client.get(url, headers: headers).then((http.Response response) { - return handleResponse(response); - }); - } on SocketException { - print("=======socket exceptiom"); - throw HttpException("Network Error, check your internet"); - } catch (e) { - print("=======ache"); - throw HttpException("Something Went Wrong"); - } finally { - // client.close(); - } - } - - Future post(Uri url, - {Map? headers, body, encoding}) { - try { - return client - .post(url, headers: headers, body: body, encoding: encoding) - .then((http.Response response) { - return handleResponse(response); - }); - } on FormatException { - print("=========Format Excepion"); - throw HttpException("Server issue"); - } on SocketException { - print("=======socket exceptiom"); - throw HttpException("Network Error, check your internet"); - } catch (e) { - print("=======ache"); - throw HttpException("Something Went Wrongggg"); - } finally { - // client.close(); - } - } - - Future put(Uri url, - {Map? headers, body, encoding}) { - try { - return client - .put(url, headers: headers, body: body, encoding: encoding) - .then((http.Response response) { - return handleResponse(response); - }); - } finally { - // client.close(); - } - } - - Future delete(Uri url, {Map? headers}) { - try { - return client - .delete(url, headers: headers) - .then((http.Response response) { - return handleResponse(response); - }); - } finally { - // client.close(); - } - } - - http.Response handleResponse(http.Response response) { - return response; - } - - // Future get(String url, {Map headers, Map queryParameters}) async { - // _connectivityResult = await (Connectivity().checkConnectivity()); - // if(_connectivityResult == ConnectivityResult.none) { - // response = Response( - // data: { - // 'message' : 'Please check internet connection' - // }, - // statusCode: 0 - // ); - // } else { - // response = await dio.get(url, options: Options(headers: headers), queryParameters: queryParameters); - // } - // return handleResponse(response); - // } -} diff --git a/lib/services/tasks_service.dart b/lib/services/tasks_service.dart new file mode 100644 index 0000000..b96611f --- /dev/null +++ b/lib/services/tasks_service.dart @@ -0,0 +1,176 @@ +import 'package:flutter/foundation.dart'; +import '../config/api_config.dart'; +import '../models/api_models.dart'; +import 'api_service.dart'; + +class TasksService { + final ApiService _apiService = ApiService(); + + Future getTasks({ + String? status, + String? priority, + String? ownerId, + String? accountId, + String? contactId, + int limit = 10, + int offset = 0, + }) async { + try { + final Map queryParams = {}; + + if (status != null) queryParams['status'] = status; + if (priority != null) queryParams['priority'] = priority; + if (ownerId != null) queryParams['ownerId'] = ownerId; + if (accountId != null) queryParams['accountId'] = accountId; + if (contactId != null) queryParams['contactId'] = contactId; + queryParams['limit'] = limit.toString(); + queryParams['offset'] = offset.toString(); + + final response = await _apiService.get( + ApiConfig.tasks, + queryParams: queryParams, + ); + + if (response.success && response.data != null) { + return TasksResponse.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('Error getting tasks: $e'); + return null; + } + } + + Future getTaskById(String taskId) async { + try { + final response = await _apiService.get( + ApiConfig.replacePathParam(ApiConfig.taskById, 'id', taskId), + ); + + if (response.success && response.data != null) { + return Task.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('Error getting task by ID: $e'); + return null; + } + } + + Future createTask({ + required String subject, + String? description, + String? status, + String? priority, + DateTime? dueDate, + String? ownerId, + String? accountId, + String? contactId, + }) async { + try { + final Map data = { + 'subject': subject, + if (description != null) 'description': description, + if (status != null) 'status': status, + if (priority != null) 'priority': priority, + if (dueDate != null) 'dueDate': dueDate.toIso8601String(), + if (ownerId != null) 'ownerId': ownerId, + if (accountId != null) 'accountId': accountId, + if (contactId != null) 'contactId': contactId, + }; + + final response = await _apiService.post(ApiConfig.tasks, data); + + if (response.success && response.data != null) { + return Task.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('Error creating task: $e'); + return null; + } + } + + Future updateTask( + String taskId, { + String? subject, + String? description, + String? status, + String? priority, + DateTime? dueDate, + String? ownerId, + String? accountId, + String? contactId, + }) async { + try { + final Map data = {}; + + if (subject != null) data['subject'] = subject; + if (description != null) data['description'] = description; + if (status != null) data['status'] = status; + if (priority != null) data['priority'] = priority; + if (dueDate != null) data['dueDate'] = dueDate.toIso8601String(); + if (ownerId != null) data['ownerId'] = ownerId; + if (accountId != null) data['accountId'] = accountId; + if (contactId != null) data['contactId'] = contactId; + + final response = await _apiService.put( + ApiConfig.replacePathParam(ApiConfig.taskById, 'id', taskId), + data, + ); + + if (response.success && response.data != null) { + return Task.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('Error updating task: $e'); + return null; + } + } + + Future deleteTask(String taskId) async { + try { + final response = await _apiService.delete( + ApiConfig.replacePathParam(ApiConfig.taskById, 'id', taskId), + ); + + return response.success; + } catch (e) { + debugPrint('Error deleting task: $e'); + return false; + } + } + + Future searchTasks({ + required String query, + String? status, + String? priority, + int limit = 10, + int offset = 0, + }) async { + try { + final Map queryParams = { + 'q': query, + 'limit': limit.toString(), + 'offset': offset.toString(), + }; + + if (status != null) queryParams['status'] = status; + if (priority != null) queryParams['priority'] = priority; + + final response = await _apiService.get( + ApiConfig.taskSearch, + queryParams: queryParams, + ); + + if (response.success && response.data != null) { + return TasksResponse.fromJson(response.data!); + } + return null; + } catch (e) { + debugPrint('Error searching tasks: $e'); + return null; + } + } +} diff --git a/lib/ui/screens/accounts/account_create.dart b/lib/ui/screens/accounts/account_create.dart deleted file mode 100644 index ff383e7..0000000 --- a/lib/ui/screens/accounts/account_create.dart +++ /dev/null @@ -1,1437 +0,0 @@ -import 'dart:io'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/account_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; -import 'package:textfield_tags/textfield_tags.dart'; - -class CreateAccount extends StatefulWidget { - CreateAccount(); - @override - State createState() => _CreateAccountState(); -} - -class _CreateAccountState extends State { - int _currentTabIndex = 0; - quill.QuillController _controller = quill.QuillController.basic(); - TextEditingController _descriptionController = TextEditingController(); - GlobalKey _accountFormKey = GlobalKey(); - GlobalKey _addressFormKey = GlobalKey(); - TextEditingController fileNameController = new TextEditingController(); - TextfieldTagsController _tagsController = TextfieldTagsController(); - - List _accountFormKeys = [ - "name", - "phone", - "email", - "contacts", - "website", - "lead", - "assigned_to", - "status", - "account_attachment", - "tags" - ]; - List _addressFormKeys = [ - "billing_address_line", - "billing_street", - "billing_city", - "billing_state", - "billing_postcode", - "billing_country" - ]; - Map _errors = {}; - bool _isLoading = false; - File? file = File(''); - - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _filePicker() async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(allowMultiple: false); - if (result != null) { - file = File(result.files[0].path!); - var _filename = file!.path.toString(); - var split = _filename.split('/'); - Map values = { - for (int i = 0; i < split.length; i++) i: split[i] - }; - setState(() { - fileNameController.text = values[7].toString(); - }); - } else {} - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_accountFormKey.currentState != null) - _accountFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: buildaccountBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - _currentTabIndex = 2; - }); - }, - onSwipeRight: (offset) { - setState(() { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - _currentTabIndex = 0; - }); - }, - child: buildAddressBlock()); - } else if (_currentTabIndex == 2) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildDescriptionBlock()); - } - } - - Widget buildaccountBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _accountFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - accountBloc.currentEditAccount['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - accountBloc.currentEditAccount['name'] = - value; - }, - ), - ), - _errors['name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Website ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - accountBloc.currentEditAccount['website'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - hintText: 'https://www.bottlecrm.com', - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.url, - onSaved: (value) { - accountBloc.currentEditAccount['website'] = - value; - }), - ), - _errors['website'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['website'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Phone Number ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - accountBloc.currentEditAccount['phone'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - hintText: '+91XXXXXXXXXX', - ), - keyboardType: TextInputType.phone, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - accountBloc.currentEditAccount['phone'] = - value; - }, - ), - ), - _errors['phone'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['phone'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Email Address ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - accountBloc.currentEditAccount['email'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - if (value.isNotEmpty && - !RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") - .hasMatch(value)) { - return 'Enter valid email address.'; - } - return null; - }, - onSaved: (value) { - accountBloc.currentEditAccount['email'] = - value; - }, - ), - ), - _errors['email'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['email'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Leads ", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => leadBloc.leadsTitles.cast(), - onChanged: print, - onSaved: (selection) { - if (selection == null) { - accountBloc.currentEditAccount['lead'] = ""; - } else { - accountBloc.currentEditAccount['lead'] = - selection; - } - }, - selectedItem: - accountBloc.currentEditAccount['lead'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Lead", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: Text( - "Teams", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: teamBloc.teamsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "teams", - ), - initialValue: - accountBloc.currentEditAccount['teams'], - // validator: (value) { - // if (value.length == 0) { - // return 'Please select one or more options'; - // } - // return null; - // }, - onSaved: (value) { - if (value == null) return; - accountBloc.currentEditAccount['teams'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Contacts ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.contactsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Contacts", - ), - initialValue: - accountBloc.currentEditAccount['contacts'], - onSaved: (value) { - if (value == null) return; - accountBloc.currentEditAccount['contacts'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Assigned To", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "users", - ), - initialValue: accountBloc - .currentEditAccount['assigned_to'], - onSaved: (value) { - if (value == null) return; - accountBloc - .currentEditAccount['assigned_to'] = - value; - }), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Status", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownButtonFormField( - decoration: InputDecoration( - border: boxBorder(), - contentPadding: EdgeInsets.all(12.0)), - style: TextStyle(color: Colors.black), - hint: Text('select Status'), - value: accountBloc.currentEditAccount['status'], - onChanged: (value) { - accountBloc.currentEditAccount['status'] = - value; - }, - items: ['open', 'close'].map((item) { - return DropdownMenuItem( - child: new Text(item), - value: item, - ); - }).toList(), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Attachment", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: fileNameController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 5.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: _filePicker, - icon: Icon(Icons.upload))), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Tags", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - TextFormField( - decoration: InputDecoration( - isDense: true, - border: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - hintText: "Enter tags separated by comma...", - ), - maxLines: 2, - onChanged: (value) { - // TODO: Handle tags parsing - }, - ), - ])), - ]))))); - } - - Widget buildAddressBlock() { - return SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _addressFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Billing Address Line ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: accountBloc - .currentEditAccount['billing_address_line'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - accountBloc.currentEditAccount[ - 'billing_address_line'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Street ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: accountBloc - .currentEditAccount['billing_street'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - accountBloc.currentEditAccount['billing_street'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Postal Code ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: accountBloc - .currentEditAccount['billing_postcode'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - accountBloc - .currentEditAccount['billing_postcode'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'City ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - accountBloc.currentEditAccount['billing_city'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - accountBloc.currentEditAccount['billing_city'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'State ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - accountBloc.currentEditAccount['billing_state'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - accountBloc.currentEditAccount['billing_state'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Country ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => leadBloc.countries, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - accountBloc - .currentEditAccount['billing_country'] = ""; - } else { - accountBloc - .currentEditAccount['billing_country'] = - selection; - } - }, - selectedItem: accountBloc - .currentEditAccount['billing_country'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Country", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - // Container( - // width: screenWidth * 0.92, - // child: DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: leadBloc.countries, - // onChanged: print, - // selectedItem: accountBloc - // .currentEditAccount['billing_country'], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Country", - // )), - // popupTitle: Container( - // decoration: BoxDecoration( - // color: Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'County', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // validator: (value) { - // if (value == null || value.isEmpty) { - // return 'This field is required.'; - // } - // return null; - // }, - // onSaved: (newValue) { - // accountBloc - // .currentEditAccount['billing_country'] = - // newValue; - // }, - // )), - ])) - ])))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Container( - padding: EdgeInsets.all(8.0), - child: TextFormField( - controller: _descriptionController, - maxLines: 10, - decoration: InputDecoration( - hintText: 'Enter description...', - border: OutlineInputBorder(), - ), - enabled: !_isLoading, - ), - ) - ], - )); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - accountBloc.resetAccountFields(); - accountBloc.currentEditAccountId = ""; - FocusScope.of(context).unfocus(); - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - accountBloc.currentEditAccountId == "" - ? 'Add account' - : 'Edit account', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_accountFormKey.currentState != null) - _accountFormKey.currentState!.save(); - accountBloc.currentEditAccount['tags'] = - _tagsController.getTags; - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - accountBloc.currentEditAccount['description'] = - _controller.document.toPlainText(); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 0; - }); - }, - child: !_isLoading - ? Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'Account', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ) - : Container(), - ), - GestureDetector( - onTap: () { - if (_accountFormKey.currentState != null) - _accountFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: !_isLoading - ? Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'Address', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ) - : Container(), - ), - GestureDetector( - onTap: () { - if (_accountFormKey.currentState != null) - _accountFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 2; - }); - }, - child: !_isLoading - ? Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ) - : Container(), - ), - ], - ), - ), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_accountFormKey.currentState != null) { - if (!_accountFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _accountFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_addressFormKey.currentState != null) { - if (!_addressFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _addressFormKey.currentState!.save(); - Map _result = {}; - if (accountBloc.currentEditAccountId != null && - accountBloc.currentEditAccountId != "") { - _result = await accountBloc.editAccount(); - } else { - _result = await accountBloc.createAccount(file: file); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - accountBloc.resetAccountFields(); - accountBloc.currentEditAccountId = ""; - showToaster(_result['message'], context); - accountBloc.openAccounts.clear(); - accountBloc.closedAccounts.clear(); - await accountBloc.fetchAccounts(); - await FirebaseAnalytics.instance.logEvent(name: "Account_Created"); - Navigator.pushReplacementNamed(context, '/accounts_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _accountFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - for (var key in _addressFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 1; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - } - - showErrorMessage(BuildContext context, String msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/accounts/account_details.dart b/lib/ui/screens/accounts/account_details.dart deleted file mode 100644 index 1ee0a09..0000000 --- a/lib/ui/screens/accounts/account_details.dart +++ /dev/null @@ -1,789 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/dashboard_bloc.dart'; -import 'package:bottle_crm/model/account.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -import 'package:bottle_crm/ui/widgets/tags_widget.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/account_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class AccountDetails extends StatefulWidget { - AccountDetails(); - @override - State createState() => _AccountDetailsState(); -} - -class _AccountDetailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Account Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - accountBloc.currentEditAccountId = - accountBloc.currentAccount!.id.toString(); - await accountBloc.updateCurrentEditAccount( - accountBloc.currentAccount!); - Navigator.pushNamed(context, '/account_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Account Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Attachment', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.21, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: !_isLoading - ? Container( - child: buildTopBar(), - color: Colors.white, - ) - : Container( - height: screenHeight, - width: screenWidth, - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return buildAccountInfoBlock(); - } else if (_currentTabIndex == 1) { - return buildAttachmentBlock(); - } else if (_currentTabIndex == 2) { - return buildDescriptionBlock(); - } - } - - buildAttachmentBlock() { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 2; - }); - }, - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - height: screenHeight, - width: screenWidth, - color: Colors.white, - alignment: Alignment.center, - child: Text("No Attachments Found."), - ), - ); - } - - buildDescriptionBlock() { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - height: screenHeight, - width: screenWidth, - alignment: Alignment.center, - color: Colors.white, - child: Text( - accountBloc.currentAccount!.description != "" - ? accountBloc.currentAccount!.description! - : "No Description Found", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22.0)), - ), - ); - } - - buildAccountInfoBlock() { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - width: screenWidth, - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Text( - accountBloc.currentAccount!.name!.capitalizeFirstofEach(), - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 10.0), - TagViewWidget(accountBloc.currentAccount!.tags!), - SizedBox(height: 10.0), - ProfilePicViewWidget(accountBloc.currentAccount!.assignedTo! - .map((assignedUser) => assignedUser.profileUrl == "" - ? assignedUser.firstName![0].inCaps - : assignedUser.profileUrl) - .toList()) - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - child: Text( - "Create Date", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - child: Text( - accountBloc.currentAccount!.createdOn!, - style: TextStyle( - color: Colors.grey, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - ], - ), - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - accountBloc.currentAccount!.createdBy!.firstName! - .capitalizeFirstofEach() + - " ${accountBloc.currentAccount!.createdBy!.lastName!}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.email_outlined, - size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - accountBloc.currentAccount!.email!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - accountBloc.currentAccount!.phone!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - // Row( - // children: [ - // Container( - // alignment: Alignment.centerRight, - // width: screenWidth * 0.28, - // child: Image.asset( - // "assets/images/skype.png", - // width: screenWidth / 22, - // )), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Text( - // " skype@Id", - // style: TextStyle( - // color: Colors.blue, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // ], - // ), - // SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Website :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - accountBloc.currentAccount!.website == "" - ? "------" - : accountBloc.currentAccount!.website!, - style: TextStyle( - color: accountBloc.currentAccount!.website == "" - ? Colors.black45 - : Colors.blue, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Lead :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - accountBloc.currentAccount!.lead != null && - accountBloc.currentAccount!.lead!.title != - null - ? accountBloc.currentAccount!.lead!.title! - : "------", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Industry :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - accountBloc.currentAccount!.industry == "" - ? "------" - : accountBloc.currentAccount!.industry!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Status :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - accountBloc.currentAccount!.status!, - style: TextStyle( - color: accountBloc.currentAccount!.status! - .toLowerCase() == - "open" - ? Colors.green - : Colors.red, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - ), - ), - SizedBox(height: 10), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column(children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Address :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "${accountBloc.currentAccount!.billingAddressLine!}, ${accountBloc.currentAccount!.billingStreet!}, ${accountBloc.currentAccount!.billingCity!}, ${accountBloc.currentAccount!.billingState!}, ${accountBloc.currentAccount!.billingCountry!}, ${accountBloc.currentAccount!.billingPostcode!}.", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ])), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteAccountAlertDialog( - context, accountBloc.currentAccount!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))), - ); - } - - void showDeleteAccountAlertDialog(BuildContext context, Account account) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - account.name != "" ? account.name!.capitalizeFirstofEach() : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this account?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteAccount(account); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteAccount(Account account) async { - setState(() { - _isLoading = true; - }); - Map result = await accountBloc.deleteAccount(account); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - accountBloc.openAccounts.clear(); - accountBloc.closedAccounts.clear(); - await accountBloc.fetchAccounts(); - await dashboardBloc.fetchDashboardDetails(); - await FirebaseAnalytics.instance.logEvent(name: "Account_Deleted"); - Navigator.pushReplacementNamed(context, '/accounts_list'); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, result['message'].toString(), account); - } - } - - showErrorMessage(BuildContext context, msg, Account account) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteAccount(account); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/accounts/accounts_list.dart b/lib/ui/screens/accounts/accounts_list.dart deleted file mode 100644 index 3dae7cd..0000000 --- a/lib/ui/screens/accounts/accounts_list.dart +++ /dev/null @@ -1,699 +0,0 @@ -import 'package:bottle_crm/model/account.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/tags_widget.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/account_bloc.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; - -class AccountsList extends StatefulWidget { - AccountsList(); - @override - State createState() => _AccountsListState(); -} - -class _AccountsListState extends State { - var _currentTabIndex = 0; - final GlobalKey _filtersFormKey = GlobalKey(); - bool _isFilter = false; - List _activeAccounts = []; - List _inactiveAccounts = []; - Map _filtersFormData = {"name": "", "city": "", "tags": []}; - bool _isLoading = false; - bool _isNextPageLoading = false; - ScrollController? scrollController; - - @override - void initState() { - setState(() { - _activeAccounts = accountBloc.openAccounts; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - accountBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - await accountBloc.fetchAccounts( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isNextPageLoading = false; - }); - } - }); - super.initState(); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - _submitForm() async { - if (_isFilter) { - _filtersFormKey.currentState!.save(); - } - setState(() { - _isLoading = true; - }); - accountBloc.offset = ""; - accountBloc.openAccounts.clear(); - accountBloc.closedAccounts.clear(); - await accountBloc.fetchAccounts( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isLoading = false; - }); - } - - _buildFilterBlock() { - return _isFilter - ? Container( - color: Colors.grey[100], - child: Form( - key: _filtersFormKey, - child: Column( - children: [ - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter Name", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['name'] = value; - }, - ), - ), - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['city'], - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter City", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['city'] = value; - }, - ), - ), - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - margin: EdgeInsets.only(bottom: 5.0), - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: accountBloc.filterTags, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text("Tags"), - initialValue: _filtersFormData['tags'], - onSaved: (value) { - if (value == null) return; - _filtersFormData['tags'] = value; - }, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _isFilter = false; - }); - FocusScope.of(context).unfocus(); - setState(() { - _filtersFormData = { - "name": "", - "city": "", - "tags": [] - }; - }); - _submitForm(); - }, - child: Text( - "Reset", - style: TextStyle(fontSize: screenWidth / 24), - )), - SizedBox(width: 20.0), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - ), - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - "Filter", - style: TextStyle(fontSize: screenWidth / 24), - )), - ], - ) - ], - ), - )) - : Container(); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - _inactiveAccounts = accountBloc.closedAccounts; - }); - }, - child: _buildOpenAccountsList()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: _buildClosedAccountsList()); - } - } - - Widget _buildOpenAccountsList() { - return _activeAccounts.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _activeAccounts.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - accountBloc.currentAccount = _activeAccounts[index]; - Navigator.pushNamed(context, '/account_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _activeAccounts[index] - .name! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Container( - width: screenWidth * 0.3, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _activeAccounts[index] - .createdBy! - .profileUrl != - "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage( - _activeAccounts[index] - .createdBy! - .profileUrl!), - ) - : CircleAvatar( - radius: screenWidth / 25, - backgroundColor: - Theme.of(context) - .primaryColor, - child: Text( - _activeAccounts[index] - .createdBy! - .firstName![0] - .allInCaps, - style: TextStyle( - color: Colors.white, - fontWeight: - FontWeight.bold), - ), - ), - SizedBox(width: 5.0), - Text( - _activeAccounts[index] - .createdBy! - .firstName! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w500, - fontSize: screenWidth / 25)) - ], - ), - ) - ], - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.5, - child: TagViewWidget( - _activeAccounts[index].tags!)), - Text(_activeAccounts[index].createdOn!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)) - ], - ), - ) - ], - ), - )); - }), - ) - : Container( - alignment: Alignment.center, - color: Colors.white, - height: screenHeight, - width: screenWidth, - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Open Accounts Found.'), - ); - } - - Widget _buildClosedAccountsList() { - return _inactiveAccounts.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _inactiveAccounts.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - accountBloc.currentAccount = _inactiveAccounts[index]; - Navigator.pushNamed(context, '/account_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _inactiveAccounts[index] - .name! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Container( - width: screenWidth * 0.3, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _inactiveAccounts[index] - .createdBy! - .profileUrl != - "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage( - _inactiveAccounts[index] - .createdBy! - .profileUrl!), - ) - : CircleAvatar( - radius: screenWidth / 25, - backgroundColor: - Theme.of(context) - .primaryColor, - child: Text( - _inactiveAccounts[index] - .createdBy! - .firstName![0] - .allInCaps, - style: TextStyle( - color: Colors.white, - fontWeight: - FontWeight.bold), - ), - ), - SizedBox(width: 5.0), - Text( - _inactiveAccounts[index] - .createdBy! - .firstName! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w500, - fontSize: screenWidth / 25)) - ], - ), - ) - ], - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.5, - child: TagViewWidget( - _inactiveAccounts[index].tags!)), - Text(_inactiveAccounts[index].createdOn!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)) - ], - ), - ) - ], - ), - )); - }), - ) - : Container( - height: screenHeight, - width: screenWidth, - color: Colors.white, - alignment: Alignment.center, - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Closed Accounts Found.'), - ); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - Navigator.pushReplacementNamed( - context, "/dashboard"); - currentBottomNavigationIndex="0"; - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Accounts', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - if (_currentTabIndex != 0) { - setState(() { - _currentTabIndex = 0; - _activeAccounts = - accountBloc.openAccounts; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Open', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - if (_currentTabIndex != 1) { - setState(() { - _currentTabIndex = 1; - _inactiveAccounts = - accountBloc.closedAccounts; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Closed', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ) - ], - ), - ), - ], - ), - ), - _buildFilterBlock(), - Expanded( - child: Container( - color: Colors.white, - child: buildTopBar(), - ), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - accountBloc.currentEditAccountId = ""; - if (accountBloc.openAccounts.length == 0) { - showAlertDialog(context); - } else { - Navigator.pushNamed(context, '/account_create'); - } - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any accounts, Please create account first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/account_create"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/screens/authentication/change_password.dart b/lib/ui/screens/authentication/change_password.dart deleted file mode 100644 index 3732ddd..0000000 --- a/lib/ui/screens/authentication/change_password.dart +++ /dev/null @@ -1,407 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class ChangePassword extends StatefulWidget { - ChangePassword(); - @override - State createState() => _ChangePasswordState(); -} - -class _ChangePasswordState extends State { - final GlobalKey _formKey = GlobalKey(); - bool _isPasswordVisible = false; - bool _isConfirmPasswordVisible = false; - bool _isOldPasswordVisible = false; - Map _formData = { - "old_password": "", - "new_password": "", - "retype_password": "" - }; - bool _isLoading = false; - String _errorMessage = ''; - Map _errors = {}; - - @override - void initState() { - super.initState(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 1, color: Colors.black12), - ); - } - - _submitForm() async { - if (!_formKey.currentState!.validate()) { - return; - } - _formKey.currentState!.save(); - if (_formData['new_password'] != _formData['retype_password']) { - setState(() { - _errorMessage = "Confirm password do not match with new password"; - }); - return; - } - setState(() { - _isLoading = true; - }); - Map result = await authBloc.changePassword(_formData); - if (result['error'] == false) { - setState(() { - _errorMessage = ''; - }); - showToaster('Password changed successfully.', context); - await FirebaseAnalytics.instance.logEvent(name: "Password_Changes"); - Navigator.pop(context); - } else if (result['error'] == true) { - if (result['message'] != null) { - setState(() { - _errorMessage = result['message']; - }); - } - if (result['errors'] != null) { - if (result['errors']['retype_password'] != null) { - result['errors']['retype_password'] = - "New Password and Confirm Password did not match."; - } - setState(() { - _errors = result['errors']; - }); - } - } else { - setState(() { - _errorMessage = ''; - }); - showErrorMessage(context,result['message'].toString()); - } - setState(() { - _isLoading = false; - }); - } - - showErrorMessage(BuildContext context,String message) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(message), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - IconButton( - icon: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onPressed: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Change Password', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - ], - ), - ), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.white, - ), - child: buildPasswordScreen(), - )) - ], - ), - ), - ), - ); - } - - buildPasswordScreen() { - return Container( - height: screenHeight, - alignment: Alignment.center, - padding: EdgeInsets.all(20.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: Form( - key: _formKey, - child: Column( - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Old Password', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - obscureText: - _isOldPasswordVisible ? false : true, - decoration: InputDecoration( - prefixIcon: Icon(Icons.lock_outline, - color: Theme.of(context).primaryColor), - suffixIcon: IconButton( - onPressed: () { - setState(() { - _isOldPasswordVisible = - !_isOldPasswordVisible; - }); - }, - icon: Icon( - _isOldPasswordVisible - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.grey)), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _formData['old_password'] = value; - }, - ), - ), - _errors['old_password'] != null - ? Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only( - bottom: 10.0, left: 10.0), - child: Text( - _errors['old_password'], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.02), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('New Password', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - obscureText: _isPasswordVisible ? false : true, - decoration: InputDecoration( - prefixIcon: Icon(Icons.lock_outline, - color: Theme.of(context).primaryColor), - suffixIcon: IconButton( - onPressed: () { - setState(() { - _isPasswordVisible = - !_isPasswordVisible; - }); - }, - icon: Icon( - _isPasswordVisible - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.grey)), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _formData['new_password'] = value; - }, - ), - ), - _errors['new_password'] != null - ? Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only( - bottom: 10.0, left: 10.0), - child: Text( - _errors['new_password'], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.02), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Confirm Password', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - obscureText: - _isConfirmPasswordVisible ? false : true, - decoration: InputDecoration( - prefixIcon: Icon(Icons.lock_outline, - color: Theme.of(context).primaryColor), - suffixIcon: IconButton( - onPressed: () { - setState(() { - _isConfirmPasswordVisible = - !_isConfirmPasswordVisible; - }); - }, - icon: Icon( - _isConfirmPasswordVisible - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.grey)), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _formData['retype_password'] = value; - }, - ), - ), - _errors['retype_password'] != null - ? Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only( - bottom: 10.0, left: 10.0), - child: Text( - _errors['retype_password'], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ], - ), - ), - _errorMessage != '' - ? Container( - child: Text( - _errorMessage, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : Container(), - SizedBox(height: 10.0), - !_isLoading - ? Container( - width: screenWidth, - child: ElevatedButton( - onPressed: () { - FocusScope.of(context).unfocus(); - _errorMessage = ''; - _errors = {}; - _submitForm(); - }, - child: Text( - 'Submit', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 22), - )), - ) - : Container( - child: CircularProgressIndicator( - valueColor: new AlwaysStoppedAnimation( - Color.fromRGBO(62, 121, 247, 1))), - ) - ], - )), - ), - ], - )); - } -} diff --git a/lib/ui/screens/authentication/companies_List.dart b/lib/ui/screens/authentication/companies_List.dart deleted file mode 100644 index 811df3d..0000000 --- a/lib/ui/screens/authentication/companies_List.dart +++ /dev/null @@ -1,161 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; - -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:bottle_crm/model/organization.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; - -class CompaniesList extends StatefulWidget { - CompaniesList(); - @override - State createState() => _CompaniesListState(); -} - -class _CompaniesListState extends State { - List companies = []; - bool _isLoading = false; - - @override - void initState() { - setState(() { - companies = authBloc.companies; - }); - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return PopScope( - onPopInvokedWithResult: (didPop, result) async { - if (!didPop) { - bool shouldPop = await onWillPop(); - if (shouldPop) Navigator.of(context).pop(); - } - }, - child: Scaffold( - resizeToAvoidBottomInset: false, - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 25.0, vertical: 15.0), - child: Text( - 'Companies List', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ), - Expanded( - child: Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - color: Colors.white, - ), - child: companies.length != 0 - ? Container( - child: ListView.builder( - itemCount: companies.length, - physics: - const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: - (BuildContext context, int index) { - return GestureDetector( - onTap: () async { - if (!_isLoading) { - setState(() { - _isLoading = true; - }); - final SharedPreferences - preferences = - await SharedPreferences - .getInstance(); - preferences.setString( - 'org', - (companies[index].id!) - .toString()); - authBloc.selectedOrganization = - companies[index]; - await fetchRequiredData(); - setState(() { - _isLoading = false; - }); - await FirebaseAnalytics.instance - .logEvent( - name: - "${companies[index].name!}_Selected"); - Navigator.pushNamed( - context, '/dashboard'); - } - }, - child: Container( - margin: - EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - color: Colors.grey[100], - border: Border.all( - width: 1.0, - color: Colors.grey), - borderRadius: BorderRadius.all( - Radius.circular(5.0)), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Container( - width: screenWidth * 0.7, - child: Text( - companies[index].name!, - style: TextStyle( - color: - bottomNavBarSelectedTextColor, - fontSize: - screenWidth / 24, - fontWeight: - FontWeight.bold), - ), - ), - Container( - child: Icon( - Icons.arrow_forward_ios, - color: - bottomNavBarSelectedTextColor, - size: screenWidth / 20, - )) - ], - ))); - }), - ) - : Center( - child: Text('No data found.'), - ), - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ), - ], - )), - ); - } -} diff --git a/lib/ui/screens/authentication/forgot_password.dart b/lib/ui/screens/authentication/forgot_password.dart deleted file mode 100644 index c7d302d..0000000 --- a/lib/ui/screens/authentication/forgot_password.dart +++ /dev/null @@ -1,381 +0,0 @@ -import 'package:bottle_crm/responsive.dart'; -import 'package:bottle_crm/utils/validations.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_svg/svg.dart'; - -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class ForgotPassword extends StatefulWidget { - ForgotPassword(); - @override - State createState() => _ForgotPasswordState(); -} - -class _ForgotPasswordState extends State { - final GlobalKey _forgotPasswordFormKey = GlobalKey(); - String? _email = ''; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 1, color: Colors.black12), - ); - } - - _submitForm() async { - if (!_forgotPasswordFormKey.currentState!.validate()) { - return; - } - _forgotPasswordFormKey.currentState!.save(); - setState(() { - _isLoading = true; - }); - Map result = await authBloc.forgotPassword({'email': _email}); - if (result['error'] == false) { - await authBloc.fetchCompanies(); - await FirebaseAnalytics.instance.logEvent(name: "Forget Password"); - Navigator.pushNamedAndRemoveUntil( - context, '/dashboard', (route) => false); - } else if (result['error'] == true) { - // Handle error case - could show a dialog or snackbar - } else { - showErrorMessage(context, result['message'].toString()); - } - setState(() { - _isLoading = false; - }); - } - - showErrorMessage(BuildContext context, String message) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text('Something went wrong'), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } - - Widget forgotPasswordWidget() { - return Responsive( - mobile: buildMobileScreen(), - tablet: buildTabletScreen(), - desktop: Container()); - } - - buildMobileScreen() { - return Column( - children: [ - SizedBox(height: screenHeight * 0.1), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.white, - ), - width: screenWidth, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 50.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Forgot Password', - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: screenWidth / 18), - ), - SvgPicture.asset( - 'assets/images/logo.svg', - width: screenWidth * 0.3, - ) - ], - ), - ), - Container( - child: Form( - key: _forgotPasswordFormKey, - child: Column( - children: [ - Container( - child: Text( - 'Please enter your email address below and we will send you information to change your password.', - style: TextStyle( - color: Colors.grey[700], - fontSize: screenWidth / 27, - fontWeight: FontWeight.w600), - )), - SizedBox(height: 10.0), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - children: [ - TextSpan( - text: 'Email ', - style: TextStyle( - color: Colors.black, - fontSize: screenWidth / 24, - fontWeight: - FontWeight.w500)), - ], - ), - )), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.email_outlined, - color: Theme.of(context) - .primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.emailAddress, - validator: (value) =>FieldValidators.emailFieldValidation(value!), - onSaved: (value) { - _email = value; - }, - ), - ), - ], - ), - ), - SizedBox(height: screenHeight * 0.02), - !_isLoading - ? Container( - width: screenWidth, - child: ElevatedButton( - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - 'Submit', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 22), - )), - ) - : Container( - child: CircularProgressIndicator( - valueColor: - new AlwaysStoppedAnimation( - Color.fromRGBO( - 62, 121, 247, 1))), - ) - ], - )), - ), - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Back to ", - style: TextStyle( - color: Colors.grey, fontSize: screenWidth / 24), - ), - GestureDetector( - onTap: () { - Navigator.pushNamed(context, '/login'); - }, - child: Text( - 'Login', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: screenWidth / 24), - ), - ) - ], - )) - ], - ), - ), - ), - ), - ], - ); - } - - buildTabletScreen() { - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only(topLeft: Radius.circular(50.0))), - height: screenHeight * 0.9, - width: screenWidth, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 50.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Forgot Password', - style: TextStyle( - color: Colors.black54, fontSize: screenWidth / 26), - ), - SvgPicture.asset( - 'assets/images/logo.svg', - width: screenWidth * 0.2, - ) - ], - ), - ), - Container( - width: screenWidth * 0.6, - child: Form( - key: _forgotPasswordFormKey, - child: Column( - children: [ - Container( - child: Text( - 'Please enter your email address below and we will send you information to change your password.', - style: TextStyle( - color: Colors.grey[700], - fontSize: screenWidth / 42, - fontWeight: FontWeight.w600), - )), - SizedBox(height: 10.0), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Email', - style: TextStyle( - fontSize: screenWidth / 40, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.email_outlined, - color: Theme.of(context).primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _email = value; - }, - ), - ), - ], - ), - ), - SizedBox(height: screenHeight * 0.02), - !_isLoading - ? Container( - width: screenWidth, - child: ElevatedButton( - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - 'Submit', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 35), - )), - ) - : Container( - child: CircularProgressIndicator( - valueColor: new AlwaysStoppedAnimation( - Color.fromRGBO(62, 121, 247, 1))), - ) - ], - )), - ), - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Back to ", - style: - TextStyle(color: Colors.grey, fontSize: screenWidth / 40), - ), - GestureDetector( - onTap: () { - Navigator.pushNamed(context, '/login'); - }, - child: Text( - 'Login', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: screenWidth / 40), - ), - ) - ], - )), - SizedBox(height: screenHeight * 0.05) - ], - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: Container( - decoration: BoxDecoration( - color: Color.fromRGBO(73, 128, 255, 1.0) - ), - child: forgotPasswordWidget()), - ); - } -} diff --git a/lib/ui/screens/authentication/login.dart b/lib/ui/screens/authentication/login.dart deleted file mode 100644 index 30b666c..0000000 --- a/lib/ui/screens/authentication/login.dart +++ /dev/null @@ -1,635 +0,0 @@ -import 'package:bottle_crm/responsive.dart'; -import 'package:bottle_crm/utils/validations.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_svg/svg.dart'; - -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class Login extends StatefulWidget { - Login(); - @override - State createState() => _LoginState(); -} - -class _LoginState extends State { - final GlobalKey _loginFormKey = GlobalKey(); - bool _isPasswordVisible = false; - Map _loginFormData = {"email": "", "password": ""}; - bool _isLoading = false; - String _errorMessage = ''; - - @override - void initState() { - super.initState(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 1, color: Colors.black12), - ); - } - - _submitForm() async { - if (!_loginFormKey.currentState!.validate()) { - return; - } - _loginFormKey.currentState!.save(); - setState(() { - _isLoading = true; - }); - Map result = {}; - result = await authBloc.login(_loginFormData); - if (result['error'] == false) { - setState(() { - _errorMessage = ''; - }); - await authBloc.fetchCompanies(); - await FirebaseAnalytics.instance.logEvent(name: "login"); - Navigator.pushNamedAndRemoveUntil( - context, '/companies_List', (route) => false); - } else if (result['error'] == true) { - setState(() { - _errorMessage = result['errors']; - }); - } else { - setState(() { - _errorMessage = ''; - }); - showErrorMessage(context, result['message'].toString()); - } - setState(() { - _isLoading = false; - }); - } - - showErrorMessage(BuildContext context, String message) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(message), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } - - Widget loginWidget() { - return Responsive( - mobile: buildMobileScreen(), - tablet: buildTabletScreen(), - desktop: Container()); - } - - buildMobileScreen() { - return SingleChildScrollView( - child: Column( - children: [ - SizedBox(height: screenHeight * 0.1), - Container( - decoration: BoxDecoration( - color: Colors.white, - ), - width: screenWidth, - height: screenHeight, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 50.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Login', - style: TextStyle( - color: Color.fromARGB(255, 59, 59, 59), - fontWeight: FontWeight.bold, - fontSize: screenWidth / 15), - ), - SvgPicture.asset( - 'assets/images/logo.svg', - width: screenWidth * 0.3, - ) - ], - ), - ), - SizedBox( - height: screenHeight * 0.05, - ), - Container( - child: Form( - key: _loginFormKey, - child: Column( - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - children: [ - TextSpan( - text: 'Email ', - style: TextStyle( - color: Colors.black, - fontSize: screenWidth / 24, - fontWeight: - FontWeight.w500)), - ], - ), - )), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.email_outlined, - color: Theme.of(context) - .primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.emailAddress, - validator: (value) => - FieldValidators.emailFieldValidation( - value!), - onSaved: (value) { - _loginFormData['email'] = value; - }, - ), - ), - ], - ), - ), - SizedBox(height: screenHeight * 0.02), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - children: [ - TextSpan( - text: 'Password ', - style: TextStyle( - color: Colors.black, - fontSize: screenWidth / 24, - fontWeight: - FontWeight.w500)), - ], - ), - )), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - obscureText: - _isPasswordVisible ? false : true, - decoration: InputDecoration( - prefixIcon: Icon(Icons.lock_outline, - color: Theme.of(context) - .primaryColor), - suffixIcon: IconButton( - onPressed: () { - setState(() { - _isPasswordVisible = - !_isPasswordVisible; - }); - }, - icon: Icon( - _isPasswordVisible - ? Icons - .visibility_outlined - : Icons - .visibility_off_outlined, - color: Colors.grey)), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) => - FieldValidators.passwordValidation( - value!), - onSaved: (value) { - _loginFormData['password'] = value; - }, - ), - ), - ], - ), - ), - _errorMessage != '' - ? Container( - child: Text( - _errorMessage, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : SizedBox( - height: 0.0, - ), - GestureDetector( - onTap: () { - Navigator.pushNamed( - context, '/forgot_password'); - }, - child: Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - alignment: Alignment.centerRight, - child: Text( - 'Forgot Password?', - style: TextStyle( - color: Colors.blueAccent, - fontSize: screenWidth / 24, - fontWeight: FontWeight.w500), - ), - ), - ), - !_isLoading - ? Container( - width: screenWidth, - child: ElevatedButton( - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - 'Login', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 22), - )), - ) - : Container( - child: CircularProgressIndicator( - valueColor: - new AlwaysStoppedAnimation( - Color.fromRGBO( - 62, 121, 247, 1))), - ) - ], - )), - ), - Container( - margin: EdgeInsets.symmetric(vertical: 20.0), - child: Text( - ' Or Connect With ', - style: TextStyle( - color: Colors.grey, fontSize: screenWidth / 24), - ), - ), - Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey[400]!), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: GestureDetector( - onTap: () {}, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset('assets/images/google-icon.png', - width: screenWidth / 18), - SizedBox(width: 8.0), - Text( - 'Login With Google', - style: TextStyle( - color: Colors.black87, - fontSize: screenWidth / 24, - fontWeight: FontWeight.w600), - ) - ], - )), - ), - SizedBox( - height: screenHeight * 0.05, - ), - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Don't have an account yet? ", - style: TextStyle( - color: Colors.grey, fontSize: screenWidth / 24), - ), - GestureDetector( - onTap: () { - Navigator.pushNamed(context, '/register'); - }, - child: Text( - 'Sign Up', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: screenWidth / 24), - ), - ) - ], - )) - ], - ), - ), - ), - ], - ), - ); - } - - buildTabletScreen() { - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only(topLeft: Radius.circular(50.0))), - height: screenHeight * 0.9, - width: screenWidth, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 50.0), - child: Column( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Login', - style: TextStyle( - color: Colors.black54, fontSize: screenWidth / 24), - ), - SvgPicture.asset( - 'assets/images/logo.svg', - width: screenWidth * 0.2, - ) - ], - ), - ), - SizedBox( - height: screenHeight * 0.1, - ), - Container( - width: screenWidth * 0.5, - child: Form( - key: _loginFormKey, - child: Column( - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Email', - style: TextStyle( - fontSize: screenWidth / 40, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.email_outlined, - color: Theme.of(context).primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _loginFormData['email'] = value; - }, - ), - ), - ], - ), - ), - SizedBox(height: screenHeight * 0.02), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Password', - style: TextStyle( - fontSize: screenWidth / 40, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - obscureText: _isPasswordVisible ? false : true, - decoration: InputDecoration( - prefixIcon: Icon(Icons.lock_outline, - color: Theme.of(context).primaryColor), - suffixIcon: IconButton( - onPressed: () { - setState(() { - _isPasswordVisible = - !_isPasswordVisible; - }); - }, - icon: Icon( - _isPasswordVisible - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.grey)), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _loginFormData['password'] = value; - }, - ), - ), - ], - ), - ), - _errorMessage != '' - ? Container( - child: Text( - _errorMessage, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 45), - ), - ) - : Container(), - GestureDetector( - onTap: () { - Navigator.pushNamed(context, '/forgot_password'); - }, - child: Container( - margin: EdgeInsets.symmetric(vertical: 20.0), - alignment: Alignment.centerRight, - child: Text( - 'Forgot Password?', - style: TextStyle( - color: Colors.black54, - fontSize: screenWidth / 40, - fontWeight: FontWeight.w500), - ), - ), - ), - !_isLoading - ? Container( - width: screenWidth, - child: ElevatedButton( - onPressed: () { - FocusScope.of(context).unfocus(); - _errorMessage = ''; - _submitForm(); - }, - child: Text( - 'Sign In', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 35), - )), - ) - : Container( - child: CircularProgressIndicator( - valueColor: new AlwaysStoppedAnimation( - Color.fromRGBO(62, 121, 247, 1))), - ) - ], - )), - ), - Container( - width: screenWidth * 0.5, - margin: EdgeInsets.symmetric(vertical: 20.0), - child: Text( - '-------------- Or Connect With --------------', - style: - TextStyle(color: Colors.grey, fontSize: screenWidth / 45), - ), - ), - Container( - width: screenWidth * 0.5, - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey[400]!), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: GestureDetector( - onTap: () {}, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset('assets/images/google-icon.png', - width: screenWidth / 25), - SizedBox(width: 8.0), - Text( - 'Sign In With Google', - style: TextStyle( - color: Colors.black87, - fontSize: screenWidth / 40, - fontWeight: FontWeight.w600), - ) - ], - )), - ), - SizedBox( - height: screenHeight * 0.2, - ), - Container( - width: screenWidth * 0.5, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Don't have an account yet? ", - style: TextStyle( - color: Colors.grey, fontSize: screenWidth / 40), - ), - GestureDetector( - onTap: () { - Navigator.pushNamed(context, '/register'); - }, - child: Text( - 'Sign Up', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: screenWidth / 40), - ), - ) - ], - )) - ], - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - screenWidth = MediaQuery.of(context).size.width; - screenHeight = MediaQuery.of(context).size.height; - return Scaffold( - body: Container( - decoration: BoxDecoration(color: Color.fromARGB(226, 73, 128, 255)), - child: loginWidget()), - ); - } -} diff --git a/lib/ui/screens/authentication/profile.dart b/lib/ui/screens/authentication/profile.dart deleted file mode 100644 index fc4af5f..0000000 --- a/lib/ui/screens/authentication/profile.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'dart:core'; - -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class Profile extends StatefulWidget { - @override - State createState() => _ProfileState(); -} - -class _ProfileState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Profile', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.edit, - size: screenWidth / 20, - color: Theme.of(context).primaryColor), - GestureDetector( - onTap: () {}, - child: Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold), - ), - ), - ), - ], - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 20.0), - height: screenHeight * 0.2, - width: screenWidth, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - borderRadius: - BorderRadius.vertical(top: Radius.circular(20.0))), - child: Container( - child: authBloc.userProfile!.profileUrl != null && - authBloc.userProfile!.profileUrl != "" - ? CircleAvatar( - radius: screenWidth / 11.5, - backgroundColor: Theme.of(context).primaryColor, - child: CircleAvatar( - radius: screenWidth / 12, - backgroundImage: NetworkImage( - authBloc.userProfile!.profileUrl!), - backgroundColor: Colors.white, - ), - ) - : CircleAvatar( - radius: screenWidth / 20, - child: Text( - authBloc.userProfile!.firstName![0].allInCaps, - style: TextStyle( - fontSize: screenWidth / 11, - fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor), - ), - backgroundColor: Colors.grey[200], - ), - )), - Expanded( - child: Container( - padding: EdgeInsets.all(20.0), - color: Colors.white, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Name', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w600, - color: bottomNavBarSelectedTextColor), - ), - SizedBox(height: 5.0), - authBloc.userProfile!.firstName! == "" && - authBloc.userProfile!.lastName! == "" - ? Text('------', - style: TextStyle( - fontSize: screenWidth / 22, - fontWeight: FontWeight.w500, - color: bottomNavBarTextColor)) - : Text( - authBloc.userProfile!.firstName! + - ' ' + - authBloc.userProfile!.lastName!, - style: TextStyle( - fontSize: screenWidth / 22, - fontWeight: FontWeight.w500, - color: bottomNavBarTextColor), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider(color: Colors.grey)), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Date of Joining', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w600, - color: bottomNavBarSelectedTextColor), - ), - SizedBox(height: 5.0), - authBloc.userProfile!.dateOfJoin! == '' - ? Text('------') - : Text( - authBloc.userProfile!.dateOfJoin! - .capitalizeFirstofEach(), - style: TextStyle( - fontSize: screenWidth / 22, - fontWeight: FontWeight.w500, - color: bottomNavBarTextColor), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider(color: Colors.grey[200])), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Email Address', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w600, - color: bottomNavBarSelectedTextColor), - ), - SizedBox(height: 5.0), - Text( - authBloc.userProfile!.email!, - style: TextStyle( - fontSize: screenWidth / 22, - fontWeight: FontWeight.w500, - color: bottomNavBarTextColor), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider(color: Colors.grey[200])), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Permissions', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w600, - color: bottomNavBarSelectedTextColor), - ), - SizedBox(height: 5.0), - Text( - authBloc.userProfile!.role!.toLowerCase() == "admin" - ? 'Sales and Marketing' - : authBloc.userProfile!.hasSalesAccess! && - authBloc - .userProfile!.hasMarketingAccess! - ? "Sales and Marketing" - : authBloc.userProfile!.hasSalesAccess! && - !authBloc.userProfile! - .hasMarketingAccess! - ? "Sales" - : !authBloc.userProfile! - .hasSalesAccess! && - authBloc.userProfile! - .hasMarketingAccess! - ? "Marketing" - : "------", - style: TextStyle( - fontSize: screenWidth / 22, - fontWeight: FontWeight.w500, - color: bottomNavBarTextColor), - ) - ], - ), - ), - ], - ), - )) - ], - ), - ), - ), - ); - } -} diff --git a/lib/ui/screens/authentication/register.dart b/lib/ui/screens/authentication/register.dart deleted file mode 100644 index 372c26c..0000000 --- a/lib/ui/screens/authentication/register.dart +++ /dev/null @@ -1,929 +0,0 @@ -import 'package:bottle_crm/responsive.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_svg/svg.dart'; - -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class Register extends StatefulWidget { - Register(); - @override - State createState() => _RegisterState(); -} - -class _RegisterState extends State { - final GlobalKey _registerFormKey = GlobalKey(); - bool _isPasswordVisible = false; - Map _registerFormData = { - "company_name ": "", - "first_name ": "", - "email": "", - "password": "" - }; - bool _isLoading = false; - String _errorMessage = ''; - String _companyError = ''; - String _firstNameError = ""; - String _emailError = ''; - String _passwordError = ''; - - @override - void initState() { - super.initState(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 1, color: Colors.black12), - ); - } - - _submitForm() async { - if (!_registerFormKey.currentState!.validate()) { - return; - } - _registerFormKey.currentState!.save(); - setState(() { - _isLoading = true; - }); - Map result = await authBloc.register(_registerFormData); - if (result['error'] == false) { - setState(() { - _errorMessage = ''; - }); - await authBloc.fetchCompanies(); - await FirebaseAnalytics.instance.logEvent(name: "Sign_up"); - Navigator.pushNamedAndRemoveUntil( - context, '/dashboard', (route) => false); - } else if (result['error'] == true) { - setState(() { - _companyError = result['errors']['company_name'] != null - ? result['errors']['company_name'][0] - : ''; - _firstNameError = result['errors']['first_name'] != null - ? result['errors']['first_name'][0] - : ''; - _emailError = result['errors']['email'] != null - ? result['errors']['email'][0] - : ''; - _passwordError = result['errors']['password'] != null - ? result['errors']['password'][0] - : ''; - }); - } else { - setState(() { - _errorMessage = ''; - }); - showErrorMessage(context, result['message'].toString()); - } - setState(() { - _isLoading = false; - }); - } - - showErrorMessage(BuildContext context, String message) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(message), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } - - Widget registerWidget() { - return Responsive( - mobile: buildMobileScreen(), - tablet: buildTabletScreen(), - desktop: Container()); - } - - buildMobileScreen() { - return SingleChildScrollView( - child: Column( - children: [ - SizedBox(height: screenHeight * 0.1), - Container( - decoration: BoxDecoration( - color: Colors.white, - ), - width: screenWidth, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 50.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Register', - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: screenWidth / 15), - ), - SvgPicture.asset( - 'assets/images/logo.svg', - width: screenWidth * 0.3, - ) - ], - ), - ), - SizedBox( - height: screenHeight * 0.03, - ), - Container( - child: Form( - key: _registerFormKey, - child: Column( - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - children: [ - TextSpan( - text: 'Company Name ', - style: TextStyle( - color: Colors.black, - fontSize: screenWidth / 24, - fontWeight: - FontWeight.w500)), - ], - ), - )), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.business, - color: Theme.of(context) - .primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['company_name'] = - value; - }, - ), - ), - _companyError != '' - ? Container( - child: Text( - _companyError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.01), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - children: [ - TextSpan( - text: 'Name ', - style: TextStyle( - color: Colors.black, - fontSize: screenWidth / 24, - fontWeight: - FontWeight.w500)), - ], - ), - )), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.person_outline, - color: Theme.of(context) - .primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['first_name'] = value; - }, - ), - ), - _firstNameError != '' - ? Container( - child: Text( - _firstNameError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.001), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - children: [ - TextSpan( - text: 'Email ', - style: TextStyle( - color: Colors.black, - fontSize: screenWidth / 24, - fontWeight: - FontWeight.w500)), - ], - ), - )), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.email_outlined, - color: Theme.of(context) - .primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['email'] = value; - }, - ), - ), - _emailError != '' - ? Container( - child: Text( - _emailError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.01), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - children: [ - TextSpan( - text: 'Password ', - style: TextStyle( - color: Colors.black, - fontSize: screenWidth / 24, - fontWeight: - FontWeight.w500)), - ], - ), - )), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - obscureText: - _isPasswordVisible ? false : true, - decoration: InputDecoration( - prefixIcon: Icon(Icons.lock_outline, - color: Theme.of(context) - .primaryColor), - suffixIcon: IconButton( - onPressed: () { - setState(() { - _isPasswordVisible = - !_isPasswordVisible; - }); - }, - icon: Icon( - _isPasswordVisible - ? Icons - .visibility_outlined - : Icons - .visibility_off_outlined, - color: Colors.grey)), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['password'] = value; - }, - ), - ), - _passwordError != '' - ? Container( - child: Text( - _passwordError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : Container(), - ], - ), - ), - _errorMessage != '' - ? Container( - child: Text( - _errorMessage, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : Container(), - !_isLoading - ? Container( - width: screenWidth, - child: ElevatedButton( - onPressed: () { - FocusScope.of(context).unfocus(); - setState(() { - _errorMessage = ''; - _companyError = ''; - _firstNameError = ''; - _emailError = ''; - _passwordError = ''; - }); - _submitForm(); - }, - child: Text( - 'Create Account', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 22), - )), - ) - : Container( - child: CircularProgressIndicator( - valueColor: - new AlwaysStoppedAnimation( - Color.fromRGBO( - 62, 121, 247, 1))), - ) - ], - )), - ), - Container( - margin: EdgeInsets.symmetric(vertical: 20.0), - child: Text( - ' Or Connect With ', - style: TextStyle( - color: Colors.grey, fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 3.0), - Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey[400]!), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: GestureDetector( - onTap: () {}, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset('assets/images/google-icon.png', - width: screenWidth / 18), - SizedBox(width: 8.0), - Text( - 'Register With Google', - style: TextStyle( - color: Colors.black87, - fontSize: screenWidth / 24, - fontWeight: FontWeight.w600), - ) - ], - )), - ), - SizedBox( - height: screenHeight * 0.05, - ), - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Existing User? ", - style: TextStyle( - color: Colors.grey, fontSize: screenWidth / 24), - ), - GestureDetector( - onTap: () { - Navigator.pushNamed(context, '/login'); - }, - child: Text( - 'Login', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: screenWidth / 24), - ), - ) - ], - )) - ], - ), - ), - ), - ], - ), - ); - } - - buildTabletScreen() { - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only(topLeft: Radius.circular(50.0))), - width: screenWidth, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 50.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Register', - style: TextStyle( - color: Colors.black54, fontSize: screenWidth / 24), - ), - SvgPicture.asset( - 'assets/images/logo.svg', - width: screenWidth * 0.2, - ) - ], - ), - ), - SizedBox( - height: screenHeight * 0.1, - ), - Container( - width: screenWidth * 0.5, - child: Form( - key: _registerFormKey, - child: Column( - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Company Name', - style: TextStyle( - fontSize: screenWidth / 24, - fontWeight: FontWeight.w500)), - Container( - margin: - EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.person_outline, - color: Theme.of(context) - .primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['company_name'] = - value; - }, - ), - ), - _companyError != '' - ? Container( - child: Text( - _companyError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 28), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.01), - Text('First Name', - style: TextStyle( - fontSize: screenWidth / 40, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.person_outline, - color: Theme.of(context).primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['first_name'] = value; - }, - ), - ), - _firstNameError != '' - ? Container( - child: Text( - _firstNameError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 45), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.01), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Email', - style: TextStyle( - fontSize: screenWidth / 40, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.email_outlined, - color: Theme.of(context).primaryColor), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['email'] = value; - }, - ), - ), - _emailError != '' - ? Container( - child: Text( - _emailError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 45), - ), - ) - : Container(), - ], - ), - ), - SizedBox(height: screenHeight * 0.01), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Password', - style: TextStyle( - fontSize: screenWidth / 40, - fontWeight: FontWeight.w500)), - Container( - margin: EdgeInsets.symmetric(vertical: 10.0), - child: TextFormField( - obscureText: _isPasswordVisible ? false : true, - decoration: InputDecoration( - prefixIcon: Icon(Icons.lock_outline, - color: Theme.of(context).primaryColor), - suffixIcon: IconButton( - onPressed: () { - setState(() { - _isPasswordVisible = - !_isPasswordVisible; - }); - }, - icon: Icon( - _isPasswordVisible - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.grey)), - contentPadding: EdgeInsets.all(12.0), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - fillColor: Colors.white, - filled: true, - hintStyle: TextStyle(fontSize: 14.0)), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - setState(() { - _errorMessage = ''; - }); - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - _registerFormData['password'] = value; - }, - ), - ), - _passwordError != '' - ? Container( - child: Text( - _passwordError, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 45), - ), - ) - : Container(), - ], - ), - ), - _errorMessage != '' - ? Container( - child: Text( - _errorMessage, - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 45), - ), - ) - : Container(), - Container(height: 20.0), - !_isLoading - ? Container( - width: screenWidth, - child: ElevatedButton( - onPressed: () { - FocusScope.of(context).unfocus(); - setState(() { - _errorMessage = ''; - _companyError = ''; - _firstNameError = ''; - _emailError = ''; - _passwordError = ''; - }); - _submitForm(); - }, - child: Text( - 'Create Account', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 35), - )), - ) - : Container( - child: CircularProgressIndicator( - valueColor: new AlwaysStoppedAnimation( - Color.fromRGBO(62, 121, 247, 1))), - ) - ], - )), - ), - Container( - width: screenWidth * 0.5, - margin: EdgeInsets.symmetric(vertical: 30.0), - child: Text( - '-------------- Or Connect With --------------', - style: - TextStyle(color: Colors.grey, fontSize: screenWidth / 45), - ), - ), - SizedBox(height: 5.0), - Container( - width: screenWidth * 0.5, - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey[400]!), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: GestureDetector( - onTap: () {}, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset('assets/images/google-icon.png', - width: screenWidth / 25), - SizedBox(width: 8.0), - Text( - 'Register With Google', - style: TextStyle( - color: Colors.black87, - fontSize: screenWidth / 40, - fontWeight: FontWeight.w600), - ) - ], - )), - ), - SizedBox( - height: screenHeight * 0.05, - ), - Container( - width: screenWidth * 0.5, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Existing User? ", - style: TextStyle( - color: Colors.grey, fontSize: screenWidth / 40), - ), - GestureDetector( - onTap: () { - Navigator.pushNamed(context, '/login'); - }, - child: Text( - 'Sign In', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: screenWidth / 40), - ), - ) - ], - )), - SizedBox(height: screenHeight * 0.2) - ], - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: registerWidget()), - ); - } -} diff --git a/lib/ui/screens/cases/case_create.dart b/lib/ui/screens/cases/case_create.dart deleted file mode 100644 index f3a8cce..0000000 --- a/lib/ui/screens/cases/case_create.dart +++ /dev/null @@ -1,953 +0,0 @@ -import 'dart:io'; - -import 'package:bottle_crm/bloc/case_bloc.dart'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/opportunity_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:bottle_crm/utils/utils.dart'; -import 'package:intl/intl.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class CreateCase extends StatefulWidget { - CreateCase(); - @override - State createState() => _CreateCaseState(); -} - -class _CreateCaseState extends State { - TextEditingController _descriptionController = TextEditingController(); - final GlobalKey _caseFormKey = GlobalKey(); - TextEditingController _dateController = TextEditingController(); - TextEditingController fileNameController = new TextEditingController(); - String? selectedDate = ""; - DateTime initialDate = DateTime.now(); - var _currentTabIndex = 0; - Map _errors = {}; - bool _isLoading = false; - File? file = File(''); - List _caseFormKeys = [ - 'name', - 'status', - 'priority', - 'type_of_case', - 'account', - 'contacts', - 'closed_on', - 'description', - 'assigned_to', - 'teams', - ]; - - @override - void initState() { - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - super.initState(); - } - - _selectDate(BuildContext context) async { - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: DateTime(1950), - lastDate: DateTime(2023), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - if (_caseFormKey.currentState != null) { - _caseFormKey.currentState!.save(); - } - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildCaseBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: buildDescriptionBlock()); - } - } - - Positioned buildReqField() { - return Positioned( - child: Container( - width: 3.0, - color: Colors.red, - ), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildCaseBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _caseFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: caseBloc.currentEditCase['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - caseBloc.currentEditCase['name'] = value; - }, - ), - ), - _errors['name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Status ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => caseBloc.statusObjForDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - caseBloc.currentEditCase['status'] = ""; - } else { - caseBloc.currentEditCase['status'] = - selection; - } - }, - selectedItem: - caseBloc.currentEditCase['status'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Satus", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['status'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['status'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Account ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => opportunityBloc.accountsObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - caseBloc.currentEditCase['account'] = ""; - } else { - caseBloc.currentEditCase['account'] = - selection; - } - }, - selectedItem: - caseBloc.currentEditCase['account'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Account", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['account'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['account'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Priority ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => caseBloc.priorityObjForDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - caseBloc.currentEditCase['priority'] = ""; - } else { - caseBloc.currentEditCase['priority'] = - selection; - } - }, - selectedItem: - caseBloc.currentEditCase['priority'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Priority", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['priority'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['priority'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Type Of Case", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => caseBloc.typeOfCaseObjForDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - caseBloc.currentEditCase['type_of_case'] = - ""; - } else { - caseBloc.currentEditCase['type_of_case'] = - selection; - } - }, - selectedItem: - caseBloc.currentEditCase['type_of_case'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a case type", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Contacts", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.contactsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Contacts", - ), - initialValue: - caseBloc.currentEditCase['contacts'], - onSaved: (value) { - if (value == null) return; - caseBloc.currentEditCase['contacts'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Assigned to", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Assigned to", - ), - initialValue: - caseBloc.currentEditCase['assigned_to'], - onSaved: (value) { - if (value == null) return; - caseBloc.currentEditCase['assigned_to'] = - value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Teams", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: teamBloc.teamsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "teams", - ), - initialValue: - caseBloc.currentEditCase['teams'], - onSaved: (value) { - if (value == null) return; - caseBloc.currentEditCase['teams'] = value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Closed Date ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _dateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _selectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - print(value); - caseBloc.currentEditCase['closed_on'] = value; - }, - ), - ), - _errors['closed_on'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['closed_on'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - ]))))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Container( - padding: EdgeInsets.all(8.0), - child: TextFormField( - controller: _descriptionController, - maxLines: 10, - decoration: InputDecoration( - hintText: 'Enter description...', - border: OutlineInputBorder(), - ), - enabled: !_isLoading, - ), - )); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - caseBloc.cancelCurrentEditCase(); - caseBloc.currentEditCaseId = ""; - FocusScope.of(context).unfocus(); - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - caseBloc.currentEditCaseId == "" - ? 'Add Case' - : "Edit Case", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_caseFormKey.currentState != null) - _caseFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - // caseBloc.currentEditCase['description'] = - // _controller.document.toPlainText(); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Case', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_caseFormKey.currentState != null) - _caseFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_caseFormKey.currentState != null) { - if (!_caseFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _caseFormKey.currentState!.save(); - await Future.delayed(const Duration(seconds: 1), () async {}); - - Map _result = {}; - if (caseBloc.currentEditCaseId != null && - caseBloc.currentEditCaseId != "") { - _result = await caseBloc.editCase(); - } else { - _result = await caseBloc.createCase(file: file); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - caseBloc.cancelCurrentEditCase(); - caseBloc.currentEditCaseId = ""; - showToaster(_result['message'], context); - caseBloc.cases.clear(); - caseBloc.offset = ""; - await caseBloc.fetchCases(); - await FirebaseAnalytics.instance.logEvent(name: "Case_Created"); - Navigator.pushReplacementNamed(context, '/cases_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _caseFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/cases/case_details.dart b/lib/ui/screens/cases/case_details.dart deleted file mode 100644 index a15d41b..0000000 --- a/lib/ui/screens/cases/case_details.dart +++ /dev/null @@ -1,800 +0,0 @@ -import 'package:bottle_crm/bloc/case_bloc.dart'; -import 'package:bottle_crm/model/case.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; - -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; - -class CaseDetails extends StatefulWidget { - CaseDetails(); - @override - State createState() => _CaseDetailsState(); -} - -class _CaseDetailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Case Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - caseBloc.currentEditCaseId = - caseBloc.currentCase!.id.toString(); - await caseBloc - .updateCurrentEditCase(caseBloc.currentCase!); - Navigator.pushNamed(context, '/case_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Case Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.24, - child: Text( - 'Attachments', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.21, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: !_isLoading - ? Container( - child: buildTopBar(), - color: Colors.white, - ) - : Container( - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - if (_currentTabIndex == 0) { - setState(() { - _currentTabIndex = 1; - }); - } - }, - child: buildContactInfoBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 2; - }); - }, - child: buildAttachmentBlock()); - } else if (_currentTabIndex == 2) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildDescriptionBlock()); - } - } - - buildAttachmentBlock() { - return Container( - alignment: Alignment.center, - color: Colors.white, - height: screenHeight, - width: screenWidth, - child: Text("No Attachment Found..."), - ); - } - - buildDescriptionBlock() { - return Container( - alignment: Alignment.center, - color: Colors.white, - height: screenHeight, - width: screenWidth, - child: Text( - caseBloc.currentCase!.description != "" - ? caseBloc.currentCase!.description! - : "No Description Found", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22.0)), - ); - } - - buildContactInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - caseBloc.currentCase!.name!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - child: Container( - child: Row( - children: [ - CircleAvatar( - radius: screenWidth / 25, - backgroundImage: NetworkImage( - "https://www.thefamouspeople.com/profiles/images/virat-kohli-3.jpg"), - ), - SizedBox(width: 3.0), - CircleAvatar( - radius: screenWidth / 25, - backgroundImage: NetworkImage( - "https://upload.wikimedia.org/wikipedia/commons/2/29/Ms._Smriti_Mandhana%2C_Arjun_Awardee_%28Cricket%29%2C_in_New_Delhi_on_July_16%2C_2019_%28cropped%29.jpg"), - ), - SizedBox(width: 3.0), - CircleAvatar( - radius: screenWidth / 25, - backgroundColor: Colors.grey, - child: Text( - "JR", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), - ) - ], - ), - ), - ), - ], - ), - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.name!.capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "Account :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.account!.name != null - ? caseBloc.currentCase!.account!.name! - : "-----", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Status :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.status!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Closed Date :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.closedOn!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - )), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Priority :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.priority!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Type of Case :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - caseBloc.currentCase!.caseType == "" - ? "----" - : caseBloc.currentCase!.caseType!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - SizedBox(width: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.createdBy!.firstName!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(width: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - " +91 0000 000 000", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 10.0), - Text( - " +91 0000 000 000", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Teams :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.teams!.length == 0 - ? "----" - : _getteams(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Users :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - caseBloc.currentCase!.assignedTo!.length == 0 - ? "----" - : _getUsers(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteAccountAlertDialog(context, caseBloc.currentCase!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))); - } - - _getteams() { - List teams = []; - - caseBloc.currentCase!.teams!.forEach((_teams) { - String _teamsList; - _teamsList = _teams.name!; - teams.add(_teamsList); - }); - - return teams.toString().replaceAll("[", "").replaceAll("]", ""); - } - - _getUsers() { - List users = []; - - caseBloc.currentCase!.assignedTo!.forEach((_teams) { - String _teamsList; - _teamsList = _teams.firstName!; - users.add(_teamsList); - }); - - return users.toString().replaceAll("[", "").replaceAll("]", ""); - } - - void showDeleteAccountAlertDialog(BuildContext context, Case casee) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - casee.name != "" ? casee.name!.capitalizeFirstofEach() : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this account?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteCase(casee); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteCase(Case casee) async { - setState(() { - _isLoading = true; - }); - Map result = await caseBloc.deleteCase(casee); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - caseBloc.cases.clear(); - await caseBloc.fetchCases(); - await FirebaseAnalytics.instance.logEvent(name: "Case_deleted"); - Navigator.pushReplacementNamed(context, '/cases_list'); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, 'Something went wrong', casee); - } - } - - showErrorMessage(BuildContext context, msg, Case casee) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteCase(casee); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/cases/cases_list.dart b/lib/ui/screens/cases/cases_list.dart deleted file mode 100644 index ded4ffc..0000000 --- a/lib/ui/screens/cases/cases_list.dart +++ /dev/null @@ -1,599 +0,0 @@ -import 'package:bottle_crm/bloc/case_bloc.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -//import 'package:dropdown_search/dropdown_search.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; - -class CasesList extends StatefulWidget { - CasesList(); - @override - State createState() => _CasesListState(); -} - -class _CasesListState extends State { - final GlobalKey _filtersFormKey = GlobalKey(); - bool _isLoading = false; - bool _isNextPageLoading = false; - ScrollController? scrollController; - bool _isFilter = false; - - List _cases = []; - Map _filtersFormData = { - "name": "", - "status": "", - "priority": "", - "account": "", - }; - - @override - void initState() { - setState(() { - _cases = caseBloc.cases; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - caseBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - await caseBloc.fetchCases( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isNextPageLoading = false; - }); - } - }); - super.initState(); - } - - _submitForm() async { - if (_isFilter) { - _filtersFormKey.currentState!.save(); - } - setState(() { - _isLoading = true; - }); - caseBloc.offset = ""; - caseBloc.cases.clear(); - await caseBloc.fetchCases(filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isLoading = false; - }); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _buildFilterBlock() { - return _isFilter - ? Container( - color: Colors.grey[100], - child: Form( - key: _filtersFormKey, - child: Column( - children: [ - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['title'], - cursorWidth: 3.0, - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter title", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['title'] = value; - }, - ), - ), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Status", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: caseBloc.statusObjForDropDown, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // caseBloc.currentEditCase["status"] = ""; - // } else { - // caseBloc.currentEditCase['status '] = - // selection; - // } - // }, - // selectedItem: - // caseBloc.currentEditCase['status '], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Status", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Source', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Priority", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: caseBloc.priorityObjForDropDown, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // caseBloc.currentEditCase['priority'] = - // ""; - // } else { - // caseBloc.currentEditCase['priority'] = - // selection; - // } - // }, - // selectedItem: - // caseBloc.currentEditCase['priority'], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Priority", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Priority', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - SizedBox(height: 10.0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _isFilter = false; - }); - FocusScope.of(context).unfocus(); - setState(() { - _filtersFormData = { - "name": "", - "status": "", - "priority": "", - "account": "", - }; - }); - _submitForm(); - }, - child: Text( - "Reset", - style: TextStyle(fontSize: screenWidth / 24), - )), - SizedBox(width: 20.0), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - ), - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - "Filter", - style: TextStyle(fontSize: screenWidth / 24), - )), - ], - ) - ], - ), - )) - : Container(); - } - - Widget _buildCasesList() { - return _cases.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _cases.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - caseBloc.currentCase = _cases[index]; - Navigator.pushNamed(context, '/case_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Text( - _cases[index].name!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - Container( - child: Text( - _cases[index].priority!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - ], - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - children: [ - _cases[index].createdBy!.profileUrl != - "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage( - _cases[index].profileUrl!), - ) - : CircleAvatar( - radius: screenWidth / 25, - backgroundColor: - Theme.of(context) - .primaryColor, - child: Text( - _cases[index].name![0], - style: TextStyle( - color: Colors.white, - fontWeight: - FontWeight.bold), - ), - ), - SizedBox(width: 3.0), - CircleAvatar( - radius: screenWidth / 25, - backgroundImage: NetworkImage( - "https://upload.wikimedia.org/wikipedia/commons/2/29/Ms._Smriti_Mandhana%2C_Arjun_Awardee_%28Cricket%29%2C_in_New_Delhi_on_July_16%2C_2019_%28cropped%29.jpg"), - ), - SizedBox(width: 3.0), - CircleAvatar( - radius: screenWidth / 25, - backgroundColor: Colors.grey, - child: Text( - "JR", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), - ) - ], - ), - ), - Row( - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: - ColorBrightness.light), - child: Text( - _cases[index].status!, - style: TextStyle( - color: Colors.white, - fontSize: 12.0), - ), - ), - ], - ), - ], - ), - ) - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Cases Found.'), - ); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - Navigator.pop(context, true); - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Cases', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - ), - ], - ), - ), - _buildFilterBlock(), - Expanded( - child: Container( - color: Colors.white, child: _buildCasesList()), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - caseBloc.currentEditCaseId = ""; - if (caseBloc.cases.length == 0) { - showAlertDialog(context); - } else { - Navigator.pushNamed(context, '/case_create'); - } - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any cases, Please create case first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/case_create"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/screens/contacts/contact_create.dart b/lib/ui/screens/contacts/contact_create.dart deleted file mode 100644 index 23ff8f2..0000000 --- a/lib/ui/screens/contacts/contact_create.dart +++ /dev/null @@ -1,1426 +0,0 @@ -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; -import 'dart:io'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter/services.dart'; -import 'package:intl/intl.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class CreateContact extends StatefulWidget { - CreateContact(); - @override - State createState() => _CreateContactState(); -} - -class _CreateContactState extends State { - var _currentTabIndex = 0; - quill.QuillController _controller = quill.QuillController.basic(); - final GlobalKey _contactFormKey = GlobalKey(); - final GlobalKey _addressFormKey = GlobalKey(); - TextEditingController _dateController = TextEditingController(); - DateTime initialDate = DateTime.now(); - Map _errors = {}; - bool _isLoading = false; - String? selectedDate = ""; - File file = new File(''); - List _contactFormKeys = [ - "salutation", - "first_name", - "last_name", - "date_of_birth", - "organization", - "title", - "primary_email", - "mobile_number", - "secondary_number", - "department", - "language", - "do_not_call" - ]; - List _addressFormKeys = [ - "address_line ", - "street", - "city", - "state", - "pincode", - "country" - ]; - @override - void initState() { - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - super.initState(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - _selectDate(BuildContext context) async { - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: DateTime(1950), - lastDate: DateTime.now(), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: buildContactBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - _currentTabIndex = 2; - }); - }, - onSwipeRight: (offset) { - setState(() { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - _currentTabIndex = 0; - }); - }, - child: buildAddressBlock()); - } else if (_currentTabIndex == 2) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: buildDescriptionBlock()); - } - } - - Positioned buildReqField() { - return Positioned( - child: Container( - width: 3.0, - color: Colors.red, - ), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildContactBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _contactFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Salutation ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['salutation'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - contactBloc.currentEditContact['salutation'] = - value; - }, - ), - ), - _errors['salutation'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['salutation'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'First Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['first_name'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - contactBloc.currentEditContact['first_name'] = - value; - }, - ), - ), - _errors['first_name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['first_name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Last Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - contactBloc.currentEditContact['last_name'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - contactBloc.currentEditContact['last_name'] = - value; - }, - ), - ), - _errors['last_name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['last_name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Phone Number", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['mobile_number'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - hintText: '+91 XXXXXXXXXX', - ), - keyboardType: TextInputType.phone, - onSaved: (value) { - contactBloc - .currentEditContact['mobile_number'] = - value; - }, - ), - ), - _errors['mobile_number'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['mobile_number'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Email ", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['primary_email'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - if (value.isNotEmpty && - !RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") - .hasMatch(value)) { - return 'Enter valid email address.'; - } - return null; - }, - onSaved: (value) { - contactBloc - .currentEditContact['primary_email'] = - value; - }, - ), - ), - _errors['primary_email'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['primary_email'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Date of birth", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - // initialValue: contactBloc - // .currentEditContact['date_of_birth'], - controller: _dateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _selectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - keyboardType: TextInputType.text), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Organization", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['organization'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - contactBloc - .currentEditContact['organization'] = - value; - }, - ), - ), - _errors['organization'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['organization'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Title ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - contactBloc.currentEditContact['title'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - contactBloc.currentEditContact['title'] = - value; - }, - ), - ), - _errors['title'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['title'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Department", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['department'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - contactBloc - .currentEditContact['department'] = - value; - }), - ), - _errors['department'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['department'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Language", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['language'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - contactBloc.currentEditContact['language'] = - value; - }), - ), - _errors['language'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['language'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - child: Text( - "Do not call", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - ), - Container( - alignment: Alignment.centerRight, - child: Switch( - value: contactBloc - .currentEditContact['do_not_call'], - onChanged: (value) { - setState(() { - contactBloc.currentEditContact[ - 'do_not_call'] = value; - }); - }, - activeColor: Colors.blue, - focusColor: Colors.blue, - )), - ])), - ]))))); - } - - Widget buildAddressBlock() { - return SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _addressFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Address Line ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['address']['address_line'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 5.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - contactBloc.currentEditContact['address'] - ['address_line'] = value; - }, - ), - ), - _errors['address_line'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['address']['address_line'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Street ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['address']['street'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 5.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - // validator: (value) { - // if (value!.isEmpty) { - // return 'This field is required.'; - // } - // return null; - // }, - onSaved: (value) { - contactBloc.currentEditContact['address'] - ['street'] = value; - }, - ), - ), - _errors['street'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['address']['street'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'City ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['address']['city'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 5.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - // validator: (value) { - // if (value!.isEmpty) { - // return 'This field is required.'; - // } - // return null; - // }, - onSaved: (value) { - contactBloc.currentEditContact['address'] - ['city'] = value; - }, - ), - ), - _errors['city'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['address']['city'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'State ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['address']['state'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 5.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - // validator: (value) { - // if (value!.isEmpty) { - // return 'This field is required.'; - // } - // return null; - // }, - onSaved: (value) { - contactBloc.currentEditContact['address'] - ['state'] = value; - }, - ), - ), - _errors['state'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['address']['state'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Pincode ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: contactBloc - .currentEditContact['address']['pincode'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 5.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], - // validator: (value) { - // if (value!.isEmpty) { - // return 'This field is required.'; - // } - // return null; - // }, - onSaved: (value) { - contactBloc.currentEditContact['address'] - ['pincode'] = value; - }, - ), - ), - _errors['pincode'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['address']['pincode'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Country ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => leadBloc.countries, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - contactBloc.currentEditContact['address'] - ['country'] = ""; - } else { - contactBloc.currentEditContact['address'] - ['country'] = selection; - } - }, - selectedItem: contactBloc - .currentEditContact['address']['country'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Counry", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - ])) - ])))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - quill.QuillSimpleToolbar( - controller: _controller, - config: const quill.QuillSimpleToolbarConfig(), - ), - Expanded( - child: Container( - child: quill.QuillEditor.basic( - controller: _controller, - config: const quill.QuillEditorConfig()), - ), - ) - ], - )); - } - - @override - Widget build(BuildContext context) { - // Set readOnly property based on loading state - _controller.readOnly = _isLoading; - - return Scaffold( - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - contactBloc.cancelCurrentEditContact(); - contactBloc.currentEditContactId = ""; - FocusScope.of(context).unfocus(); - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - contactBloc.currentEditContactId == "" - ? 'Add Contact' - : "Edit Contact", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - // contactBloc.currentEditContact['description'] = - // _controller.document.toPlainText(); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'Contact', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'Address', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_contactFormKey.currentState != null) { - if (!_contactFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _contactFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_addressFormKey.currentState != null) { - if (!_addressFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _addressFormKey.currentState!.save(); - Map _result = {}; - if (contactBloc.currentEditContactId != null && - contactBloc.currentEditContactId != "") { - _result = await contactBloc.editContact(); - } else { - _result = await contactBloc.createContact(file: file); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - contactBloc.cancelCurrentEditContact(); - contactBloc.currentEditContactId = ""; - showToaster(_result['message'], context); - contactBloc.contacts.clear(); - contactBloc.offset = ""; - await contactBloc.fetchContacts(); - await FirebaseAnalytics.instance.logEvent(name: "Contact_Created"); - Navigator.pushReplacementNamed(context, '/contacts_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']['contact_errors']; - }); - for (var key in _contactFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - for (var key in _addressFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 1; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - // showErrorMessage(context, _result['message'].toString()); - } - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/contacts/contact_details.dart b/lib/ui/screens/contacts/contact_details.dart deleted file mode 100644 index d375ae5..0000000 --- a/lib/ui/screens/contacts/contact_details.dart +++ /dev/null @@ -1,739 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class ContactDetails extends StatefulWidget { - ContactDetails(); - @override - State createState() => _ContactDetailsState(); -} - -class _ContactDetailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - if (_currentTabIndex == 0) { - setState(() { - _currentTabIndex = 1; - }); - } - }, - child: buildContactInfoBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 2; - }); - }, - child: buildSocialBlock()); - } else if (_currentTabIndex == 2) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildDescriptionBlock()); - } - } - - buildSocialBlock() { - return Container( - alignment: Alignment.center, - height: screenHeight, - width: screenWidth, - color: Colors.white, - child: Text("No Socials Found..."), - ); - } - - buildDescriptionBlock() { - return Container( - alignment: Alignment.center, - height: screenHeight, - width: screenWidth, - color: Colors.white, - child: Text( - contactBloc.currentContact!.description != "" - ? contactBloc.currentContact!.description! - : "No Description Found", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22.0)), - ); - } - - buildContactInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "First Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - contactBloc.currentContact!.firstName! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "Second Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - contactBloc.currentContact!.lastName! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Title :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - contactBloc.currentContact!.title! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Crated Date :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - contactBloc.currentContact!.createdOn! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - )), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Text( - "http://www.micropyramid.com", - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 30), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - contactBloc.currentContact!.createdBy!.firstName! - .capitalizeFirstofEach() + - " ${contactBloc.currentContact!.createdBy!.lastName!}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: - Icon(Icons.email_outlined, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - contactBloc.currentContact!.primaryEmail!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 10.0), - Text( - contactBloc.currentContact!.secondaryEmail! != "" - ? contactBloc.currentContact!.secondaryEmail! - : "-----", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - contactBloc.currentContact!.primaryMobile != "" - ? contactBloc.currentContact!.primaryMobile! - : "-----", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 10.0), - Text( - " +91 0000 000 000", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Do not call :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - alignment: Alignment.centerLeft, - width: screenWidth * 0.50, - child: Switch( - value: contactBloc.currentContact!.doNotCall!, - onChanged: (value) {}, - activeColor: Colors.blue, - focusColor: Colors.blue, - )) - ], - ), - ], - ), - ), - SizedBox(height: 10), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column(children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Address :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "${contactBloc.currentContact!.address!['address_line']}${contactBloc.currentContact!.address!['street']}${contactBloc.currentContact!.address!['city']}${contactBloc.currentContact!.address!['state']}${contactBloc.currentContact!.address!['postcode']}${contactBloc.currentContact!.address!['country']}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ])), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteContactAlertDialog( - context, contactBloc.currentContact!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Contact Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.edit_outlined, - size: screenWidth / 18, - color: Theme.of(context).primaryColor), - GestureDetector( - onTap: () async { - if (!_isLoading) { - contactBloc.currentEditContactId = - contactBloc.currentContact!.id.toString(); - await contactBloc.updateCurrentEditContact( - contactBloc.currentContact!); - Navigator.pushNamed( - context, '/contact_create'); - } - }, - child: Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold), - ), - ), - ), - ], - ), - ) - ], - ), - ), - !_isLoading - ? Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Contact Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Social', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.21, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: !_isLoading - ? Container( - child: buildTopBar(), - color: Colors.white, - ) - : Container(color: Colors.white)) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - ); - } - - void showDeleteContactAlertDialog(BuildContext context, Contact contact) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - contact.firstName != "" - ? contact.firstName!.capitalizeFirstofEach() - : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this contact?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteContact(contact); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteContact(Contact contact) async { - setState(() { - _isLoading = true; - }); - Map result = await contactBloc.deleteContact(contact); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - contactBloc.contacts.clear(); - await contactBloc.fetchContacts(); - await FirebaseAnalytics.instance.logEvent(name: "Contact_Deleted"); - Navigator.pushReplacementNamed(context, '/contacts_list'); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, 'Something went wrong', contact); - } - } - - showErrorMessage(BuildContext context, msg, Contact contact) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteContact(contact); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/contacts/contacts_list.dart b/lib/ui/screens/contacts/contacts_list.dart deleted file mode 100644 index ea749d5..0000000 --- a/lib/ui/screens/contacts/contacts_list.dart +++ /dev/null @@ -1,488 +0,0 @@ -import 'package:bottle_crm/model/contact.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; - -class ContactsList extends StatefulWidget { - ContactsList(); - @override - State createState() => _ContactsListState(); -} - -class _ContactsListState extends State { - final GlobalKey _filtersFormKey = GlobalKey(); - bool _isFilter = false; - List _contacts = []; - Map _filtersFormData = {"name": "", "city": "", "assigned_to": []}; - bool _isLoading = false; - bool _isNextPageLoading = false; - ScrollController? scrollController; - - @override - void initState() { - setState(() { - _contacts = contactBloc.contacts; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - contactBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - await contactBloc.fetchContacts( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isNextPageLoading = false; - }); - } - }); - super.initState(); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - _submitForm() async { - if (_isFilter) { - _filtersFormKey.currentState!.save(); - } - setState(() { - _isLoading = true; - }); - contactBloc.contacts.clear(); - await contactBloc.fetchContacts( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isLoading = false; - }); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _buildFilterBlock() { - return _isFilter - ? Container( - color: Colors.grey[100], - child: Form( - key: _filtersFormKey, - child: Column( - children: [ - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter Name", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['name'] = value; - }, - ), - ), - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['city'], - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter City", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['city'] = value; - }, - ), - ), - SizedBox(height: 10.0), - Container( - padding: padding(), - child: Container( - width: screenWidth * 0.92, - child: Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.assignedToList, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Users", - ), - initialValue: _filtersFormData['assigned_to'], - onSaved: (value) { - if (value == null) return; - _filtersFormData['assigned_to'] = value; - }, - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _isFilter = false; - }); - FocusScope.of(context).unfocus(); - setState(() { - _filtersFormData = { - "name": "", - "city": "", - "assigned_to": [] - }; - }); - _submitForm(); - }, - child: Text( - "Reset", - style: TextStyle(fontSize: screenWidth / 24), - )), - SizedBox(width: 20.0), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - ), - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - "Filter", - style: TextStyle(fontSize: screenWidth / 24), - )), - ], - ) - ], - ), - )) - : Container(); - } - - Widget _buildContactsList() { - return _contacts.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _contacts.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - contactBloc.currentContact = _contacts[index]; - Navigator.pushNamed(context, '/contact_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.6, - child: Text( - _contacts[index] - .firstName! - .capitalizeFirstofEach() + - " " + - _contacts[index].lastName!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - Container( - child: Text( - _contacts[index].createdOn!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ) - ], - ), - SizedBox(height: 8.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.4, - child: Row( - children: [ - Icon( - Icons.phone, - color: Colors.green, - size: screenWidth / 20, - ), - SizedBox(width: 5.0), - Container( - child: Text( - _contacts[index].primaryMobile == "" - ? "---------" - : _contacts[index] - .primaryMobile!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 25), - ), - ) - ], - ), - ), - Container( - width: screenWidth * 0.4, - child: Row( - children: [ - Container( - child: Icon( - Icons.email_outlined, - color: Colors.blue, - size: screenWidth / 20, - )), - SizedBox(width: 5.0), - Container( - width: screenWidth * 0.33, - child: Text( - _contacts[index].primaryEmail == - "" - ? "--------" - : _contacts[index] - .primaryEmail!, - style: TextStyle( - overflow: - TextOverflow.ellipsis, - color: Colors.grey, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w600)), - ) - ], - ), - ), - ], - ), - ) - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Contacts Found.'), - ); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - currentBottomNavigationIndex == "4" - ? Navigator.pushReplacementNamed( - context, "/dashboard") - : Navigator.pushReplacementNamed( - context, "/more_options"); - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Contacts', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - ), - ], - ), - ), - _buildFilterBlock(), - Expanded( - child: Container( - color: Colors.white, - child: _buildContactsList(), - ), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.pushNamed(context, '/contact_create'); - contactBloc.currentEditContactId = ""; - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any contacts, Please create contact first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/contacts_list"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/screens/dashboard/dashboard.dart b/lib/ui/screens/dashboard/dashboard.dart deleted file mode 100644 index 7ac64b7..0000000 --- a/lib/ui/screens/dashboard/dashboard.dart +++ /dev/null @@ -1,403 +0,0 @@ -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/ui/widgets/recent_card_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/dashboard_bloc.dart'; -import 'package:bottle_crm/ui/widgets/dashboard_count_card.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class Dashboard extends StatefulWidget { - Dashboard(); - @override - State createState() => _DashboardState(); -} - -class _DashboardState extends State { - int _selectedTabIndex = 0; - - @override - void initState() { - super.initState(); - } - - OutlineInputBorder boxBorder(Color color) { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(0)), - borderSide: BorderSide(width: 0, color: color), - ); - } - - Widget _buildCards(BuildContext context) { - return Container( - padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0), - child: Column( - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CountCard( - //color: Color.fromRGBO(44, 113, 255, 1), - color: Colors.white, - lable: "Accounts", - count: dashboardBloc.dashboardData['accountsCount'], - icon: 'assets/images/accounts_color.svg', - routeName: "/accounts_list", - index: 1), - CountCard( - //color: Color.fromRGBO(96, 75, 186, 1), - color: Colors.white, - lable: "Leads", - count: dashboardBloc.dashboardData['leadsCount'], - icon: 'assets/images/flag.svg', - routeName: "/leads_list", - index: 4) - ], - ), - ), - Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CountCard( - //color: Color.fromRGBO(52, 141, 80, 1), - color: Colors.white, - lable: "Contacts", - count: dashboardBloc.dashboardData['contactsCount'], - icon: 'assets/images/identification.svg', - routeName: "/contacts_list", - index: 4), - CountCard( - //color: Color.fromRGBO(255, 86, 45, 1), - color: Colors.white, - lable: "Opportunities", - count: dashboardBloc.dashboardData['opportunitiesCount'], - icon: 'assets/images/opportunities_color.svg', - routeName: "/opportunities_list", - index: 4) - ], - ), - ), - ], - ), - ); - } - - Widget _buildRecentAccounts(BuildContext context) { - return dashboardBloc.dashboardData['accounts'] != null && - dashboardBloc.dashboardData['accounts'].length > 0 - ? ListView.builder( - itemCount: dashboardBloc.dashboardData['accounts'].length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () {}, - child: RecentCardWidget( - source: 'accounts', - name: dashboardBloc.dashboardData['accounts'][index].name, - photoUrl: dashboardBloc.dashboardData['accounts'][index] - .createdBy!.profileUrl == - null - ? dashboardBloc.dashboardData['accounts'][index] - .createdBy!.firstName[0].allInCaps - : dashboardBloc.dashboardData['accounts'][index] - .createdBy!.profileUrl, - createdBy: dashboardBloc - .dashboardData['accounts'][index].createdBy!.firstName, - tags: dashboardBloc.dashboardData['accounts'][index].tags!, - date: - dashboardBloc.dashboardData['accounts'][index].createdOn, - city: dashboardBloc - .dashboardData['accounts'][index].billingCity, - email: dashboardBloc.dashboardData['accounts'][index].email, - ), - ); - }) - : Container( - margin: EdgeInsets.only(top: 10.0), - child: Text("No Data Found", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).secondaryHeaderColor)), - ); - } - - Widget _buildRecentContacts(BuildContext context) { - return dashboardBloc.dashboardData['contacts'] != null && - dashboardBloc.dashboardData['contacts'].length > 0 - ? ListView.builder( - itemCount: dashboardBloc.dashboardData['contacts'].length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () {}, - child: RecentCardWidget( - source: 'contacts', - name: dashboardBloc - .dashboardData['contacts'][index].firstName + - "" + - dashboardBloc.dashboardData['contacts'][index].lastName, - date: - dashboardBloc.dashboardData['contacts'][index].createdOn, - tags: [], - photoUrl: dashboardBloc - .dashboardData['contacts'][index].createdBy!.profileUrl, - createdBy: dashboardBloc - .dashboardData['contacts'][index].createdBy!.firstName, - email: dashboardBloc - .dashboardData['contacts'][index].primaryEmail, - ), - ); - }) - : Container( - margin: EdgeInsets.only(top: 10.0), - child: Text("No Data Found", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).secondaryHeaderColor)), - ); - } - - Widget _buildRecentOpportunities(BuildContext context) { - return dashboardBloc.dashboardData['opportunities'] != null && - dashboardBloc.dashboardData['opportunities'].length > 0 - ? ListView.builder( - itemCount: dashboardBloc.dashboardData['opportunities'].length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () {}, - child: RecentCardWidget( - source: 'opportunities', - name: - dashboardBloc.dashboardData['opportunities'][index].name, - photoUrl: dashboardBloc.dashboardData['opportunities'][index] - .createdBy!.profileUrl, - createdBy: dashboardBloc.dashboardData['opportunities'][index] - .createdBy!.firstName, - tags: - dashboardBloc.dashboardData['opportunities'][index].tags!, - date: dashboardBloc - .dashboardData['opportunities'][index].createdOn, - city: dashboardBloc - .dashboardData['opportunities'][index].leadSource, - email: dashboardBloc - .dashboardData['opportunities'][index].amount, - ), - ); - }) - : Container( - margin: EdgeInsets.only(top: 10.0), - child: Text("No Data Found", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).secondaryHeaderColor)), - ); - } - - Widget _recentTabWidget() { - return Column( - children: [ - Container( - margin: EdgeInsets.only(bottom: 10.0, left: 10.0), - alignment: Alignment.centerLeft, - child: Text( - "Recently added", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: screenWidth / 25, - ), - ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: Color.fromARGB(5, 250, 250, 251), - borderRadius: BorderRadius.all(Radius.circular(20.0)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _selectedTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: bottomNavBarSelectedTextColor, - width: _selectedTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'Accounts ', - style: TextStyle( - color: _selectedTabIndex == 0 - ? bottomNavBarSelectedTextColor - : Colors.grey, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _selectedTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: bottomNavBarSelectedTextColor, - width: _selectedTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Contacts ', - style: TextStyle( - color: _selectedTabIndex == 1 - ? bottomNavBarSelectedTextColor - : Colors.grey, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _selectedTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: bottomNavBarSelectedTextColor, - width: _selectedTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.30, - child: Text( - 'Opportunities ', - style: TextStyle( - color: _selectedTabIndex == 2 - ? bottomNavBarSelectedTextColor - : Colors.grey, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ); - } - - _buildtabSelectTab() { - if (_selectedTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _selectedTabIndex = 1; - }); - }, - child: _buildRecentAccounts(context)); - } else if (_selectedTabIndex == 1) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _selectedTabIndex = 2; - }); - }, - onSwipeRight: (offset) { - setState(() { - _selectedTabIndex = 0; - }); - }, - child: _buildRecentContacts(context)); - } else if (_selectedTabIndex == 2) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _selectedTabIndex = 1; - }); - }, - child: _buildRecentOpportunities(context)); - } - } - - @override - Widget build(BuildContext context) { - WidgetsFlutterBinding.ensureInitialized(); - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) { - if (!didPop) { - Navigator.pushNamedAndRemoveUntil( - context, '/companies_List', (route) => false); - } - }, - child: Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 20.0, vertical: 10.0), - child: Text( - 'Dashboard', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ), - Expanded( - child: Container( - color: Colors.white, - child: Column( - children: [ - _buildCards(context), - _recentTabWidget(), - Expanded( - child: _buildtabSelectTab(), - ), - ], - ), - ), - ), - ], - ), - ), - ), - ]), - bottomNavigationBar: BottomNavigationBarWidget())); - } -} diff --git a/lib/ui/screens/documents/document_create.dart b/lib/ui/screens/documents/document_create.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/ui/screens/documents/document_details.dart b/lib/ui/screens/documents/document_details.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/ui/screens/documents/documents_list.dart b/lib/ui/screens/documents/documents_list.dart deleted file mode 100644 index 417b66e..0000000 --- a/lib/ui/screens/documents/documents_list.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; - -class DocumentsList extends StatefulWidget { - DocumentsList(); - @override - State createState() => _DocumentsListState(); -} - -class _DocumentsListState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Documents List'), - ), - body: Center( - child: Text('No Documents Found'), - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } -} diff --git a/lib/ui/screens/events/event_create.dart b/lib/ui/screens/events/event_create.dart deleted file mode 100644 index ec64f54..0000000 --- a/lib/ui/screens/events/event_create.dart +++ /dev/null @@ -1,994 +0,0 @@ -import 'dart:io'; -import 'package:bottle_crm/bloc/account_bloc.dart'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/dashboard_bloc.dart'; -import 'package:bottle_crm/bloc/event_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:bottle_crm/utils/utils.dart'; -import 'package:intl/intl.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; - -class CreateEvent extends StatefulWidget { - CreateEvent(); - @override - State createState() => _CreateEventState(); -} - -class _CreateEventState extends State { - quill.QuillController _controller = quill.QuillController.basic(); - final GlobalKey _eventFormKey = GlobalKey(); - TextEditingController _startDateController = TextEditingController(); - TextEditingController _endDateController = TextEditingController(); - TextEditingController _startTimeController = TextEditingController(); - TextEditingController _endTimeController = TextEditingController(); - TextEditingController fileNameController = new TextEditingController(); - String? selectedDate = ""; - DateTime? initialDate = DateTime.now(); - var _currentTabIndex = 0; - Map _errors = {}; - bool _isLoading = false; - File file = new File(''); - List _eventFormKeys = [ - "name", - "event_type", - "contacts", - "start_date", - "start_time", - "end_date", - "end_time", - "description", - "teams", - "assigned_to", - "recurring_day", - ]; - - TimeOfDay startSelectedTime = TimeOfDay.now(); - TimeOfDay endSelectedTime = TimeOfDay.now(); - - @override - void initState() { - _startDateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - _endDateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - - _startTimeController.text = "00:00:00"; - _endTimeController.text = "00:00:00"; - super.initState(); - } - - _StartSelectDate(BuildContext context) async { - showTimePicker(context: context, initialTime: TimeOfDay.now()); - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate!, - firstDate: DateTime(1965), - lastDate: DateTime.now().add(Duration(days: 730)), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _startDateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - _endSelectDate(BuildContext context) async { - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate!, - firstDate: DateTime(1965), - lastDate: DateTime.now().add(Duration(days: 730)), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _endDateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_eventFormKey.currentState != null) - _eventFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: _buildEventBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: buildDescriptionBlock()); - } - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget _buildEventBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _eventFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Event Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - eventBloc.currentEditEvent['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - eventBloc.currentEditEvent['name'] = value; - }, - ), - ), - _errors['name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Event Type ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownButtonFormField( - decoration: InputDecoration( - border: boxBorder(), - contentPadding: EdgeInsets.all(12.0)), - style: TextStyle(color: Colors.black), - hint: Text('select Event Type'), - value: eventBloc.currentEditEvent['event_type'], - onChanged: (value) { - accountBloc.currentEditAccount['event_type'] = - value; - }, - items: - ['Recurring', 'Non-Recurring'].map((item) { - return DropdownMenuItem( - child: new Text(item), - value: item, - ); - }).toList(), - ), - ), - _errors['event_type'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['event_type'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Contacts ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.contactsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Contacts", - ), - initialValue: - eventBloc.currentEditEvent['contacts'], - onSaved: (value) { - if (value == null) return; - eventBloc.currentEditEvent['contacts'] = - value; - }, - ), - ), - _errors['event_type'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['event_type'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Start Date ", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _startDateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _StartSelectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - print(value); - eventBloc.currentEditEvent['start_date'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Start Time ", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _startTimeController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () async { - final TimeOfDay? picked_s = - await showTimePicker( - context: context, - initialTime: startSelectedTime, - builder: (BuildContext? context, - Widget? child) { - return MediaQuery( - data: MediaQuery.of(context!) - .copyWith( - alwaysUse24HourFormat: - true), - child: child!, - ); - }); - - if (picked_s != null && - picked_s != startSelectedTime) - setState(() { - startSelectedTime = picked_s; - DateTime parsedTime = DateFormat.jm() - .parse(picked_s - .format(context) - .toString()); - _startTimeController.text = - DateFormat('HH:mm:ss') - .format(parsedTime); - }); - }, - icon: Icon(Icons.punch_clock_outlined), - ), - ), - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - print(value); - eventBloc.currentEditEvent['start_time'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "End Date ", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _endDateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _endSelectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - // keyboardType: TextInputType.text - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - print(value); - eventBloc.currentEditEvent['end_date'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "End Time ", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _endTimeController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () async { - final TimeOfDay? picked_s = - await showTimePicker( - context: context, - initialTime: endSelectedTime, - builder: (BuildContext? context, - Widget? child) { - return MediaQuery( - data: MediaQuery.of(context!) - .copyWith( - alwaysUse24HourFormat: - true), - child: child!, - ); - }); - - if (picked_s != null && - picked_s != endSelectedTime) - setState(() { - endSelectedTime = picked_s; - DateTime parsedTime = DateFormat.jm() - .parse(picked_s - .format(context) - .toString()); - _endTimeController.text = - DateFormat('HH:mm:ss') - .format(parsedTime); - }); - }, - icon: Icon(Icons.punch_clock) - ), - ), - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - eventBloc.currentEditEvent['end_time'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Teams", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: teamBloc.teamsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "teams", - ), - initialValue: - eventBloc.currentEditEvent['teams'], - onSaved: (value) { - if (value == null) return; - eventBloc.currentEditEvent['teams'] = - value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Assigned to", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Users", - ), - initialValue: eventBloc - .currentEditEvent['assigned_to'], - onSaved: (value) { - if (value == null) return; - eventBloc - .currentEditEvent['assigned_to'] = - value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Recurring Days", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: eventBloc.recurringDays, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Recurring Days", - ), - initialValue: eventBloc - .currentEditEvent['recurring_days'], - onSaved: (value) { - if (value == null) return; - eventBloc.currentEditEvent[ - 'recurring_days'] = value; - })) - ])), - ]))))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - quill.QuillSimpleToolbar( - controller: _controller, - config: const quill.QuillSimpleToolbarConfig(), - ), - Expanded( - child: Container( - child: quill.QuillEditor.basic( - controller: _controller, - config: const quill.QuillEditorConfig()), - ), - ) - ], - )); - } - - @override - Widget build(BuildContext context) { - // Set readOnly property based on loading state - _controller.readOnly = _isLoading; - - return Scaffold( - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - dashboardBloc.fetchDashboardDetails(); - eventBloc.cancelCurrentEditEvent(); - eventBloc.currentEditEventId = ""; - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - eventBloc.currentEditEventId == "" - ? 'Add Event' - : "Edit Event", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_eventFormKey.currentState != null) - _eventFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - eventBloc.currentEditEvent['description'] = - _controller.document.toPlainText(); - print(eventBloc.currentEditEvent['description']); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Event', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_eventFormKey.currentState != null) - _eventFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container(color: Colors.white, child: _buildEventBlock()), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_eventFormKey.currentState != null) { - if (!_eventFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _eventFormKey.currentState!.save(); - await Future.delayed(const Duration(seconds: 1), () async {}); - - Map _result = {}; - if (eventBloc.currentEditEventId != null && - eventBloc.currentEditEventId != "") { - _result = await eventBloc.editEvent(); - } else { - _result = await eventBloc.createEvent(); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - eventBloc.cancelCurrentEditEvent(); - eventBloc.currentEditEventId = ""; - showToaster(_result['message'], context); - eventBloc.events.clear(); - eventBloc.offset = ""; - await eventBloc.fetchEvents(); - eventBloc.events; - await FirebaseAnalytics.instance.logEvent(name: "Event_Created"); - Navigator.pushReplacementNamed(context, '/events_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _eventFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/events/event_details.dart b/lib/ui/screens/events/event_details.dart deleted file mode 100644 index 8d6a45a..0000000 --- a/lib/ui/screens/events/event_details.dart +++ /dev/null @@ -1,614 +0,0 @@ -import 'package:bottle_crm/bloc/event_bloc.dart'; -import 'package:bottle_crm/model/events.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:intl/intl.dart'; - -class EventDetails extends StatefulWidget { - EventDetails(); - @override - State createState() => _EventDeailsState(); -} - -class _EventDeailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Event Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - eventBloc.currentEditEventId = - eventBloc.currentEvent!.id.toString(); - await eventBloc - .updateCurrentEditEvent(eventBloc.currentEvent!); - Navigator.pushNamed(context, '/event_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Event Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ), - Expanded( - child: Container( - child: buildTaskInfoBlock(), - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildTaskInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - width: screenWidth, - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - eventBloc.currentEvent!.name!.capitalizeFirstofEach(), - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: ColorBrightness.light), - child: Text( - eventBloc.currentEvent!.status!, - style: TextStyle(color: Colors.white, fontSize: 12.0), - ), - ), - SizedBox(height: 5.0), - Container( - child: Container( - child: Row( - children: [ - Container( - child: ProfilePicViewWidget(eventBloc - .currentEvent!.assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser.firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - ), - ], - ), - ), - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Event Type :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - eventBloc.currentEvent!.eventType!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - // Row( - // children: [ - // Container( - // alignment: Alignment.centerRight, - // width: screenWidth * 0.28, - // child: Text( - // "Create Date :", - // style: TextStyle( - // color: Colors.blueGrey[800], - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Text( - // eventBloc.currentEvent!.createdOn!, - // style: TextStyle( - // color: Colors.black45, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // ], - // ), - // SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "Start :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - eventBloc.currentEvent!.startDate! == "" - ? "-----" - : DateFormat("dd-MM-yyyy").format( - DateTime.parse( - eventBloc.currentEvent!.startDate!))+" "+eventBloc.currentEvent!.startTime!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "End :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - eventBloc.currentEvent!.startDate! == "" - ? "-----" - : DateFormat("dd-MM-yyyy").format( - DateTime.parse( - eventBloc.currentEvent!.startDate!))+" "+eventBloc.currentEvent!.endTime!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - ], - )), - SizedBox(height: 10.0), - Container( - // padding: EdgeInsets.all(10.0), - // decoration: BoxDecoration( - // border: Border.all(width: 1.0, color: Colors.black12), - // borderRadius: BorderRadius.all(Radius.circular(5.0)), - // ), - // child: Column( - // children: [ - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Container( - // alignment: Alignment.centerRight, - // width: screenWidth * 0.28, - // child: Text( - // "Organization :", - // style: TextStyle( - // color: Colors.blueGrey[800], - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // authBloc.selectedOrganization!.name! - // .capitalizeFirstofEach(), - // style: TextStyle( - // color: Colors.black45, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // ), - // ], - // )), - // ], - // ), - // SizedBox(height: 10), - // Row( - // children: [ - // Container( - // alignment: Alignment.centerRight, - // width: screenWidth * 0.28, - // child: Text( - // "Contact :", - // style: TextStyle( - // color: Colors.blueGrey[800], - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Text( - // taskBloc.currentTask!.createdBy!.firstName! - // .capitalizeFirstofEach() + - // " ${taskBloc.currentTask!.createdBy!.lastName!}", - // style: TextStyle( - // color: Colors.black45, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // ], - // ), - // SizedBox(height: 10), - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Container( - // alignment: Alignment.centerRight, - // width: screenWidth * 0.28, - // child: - // Icon(Icons.email_outlined, size: screenWidth / 20)), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "account@mp.com", - // style: TextStyle( - // color: Colors.black45, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // ), - // Container( - // padding: EdgeInsets.symmetric(vertical: 5.0), - // child: Divider()) - // ], - // )), - // ], - // ), - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Container( - // alignment: Alignment.centerRight, - // width: screenWidth * 0.28, - // child: Icon(Icons.phone, size: screenWidth / 20)), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "------------", - // style: TextStyle( - // color: Colors.black45, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // ), - // Container( - // padding: EdgeInsets.symmetric(vertical: 5.0), - // child: Divider()) - // ], - // )), - // ], - // ), - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Container( - // alignment: Alignment.centerRight, - // width: screenWidth * 0.28, - // child: Text( - // "Priority :", - // style: TextStyle( - // color: Colors.blueGrey[800], - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Text( - // taskBloc.currentTask!.priority == "" - // ? "------" - // : taskBloc.currentTask!.priority!, - // style: TextStyle( - // color: taskBloc.currentTask!.priority == "" - // ? Colors.black45 - // : Colors.blue, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - // ], - // ), - // ], - // ), - ), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteTaskAlertDialog(context, eventBloc.currentEvent!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))); - } - - void showDeleteTaskAlertDialog(BuildContext context, Event event) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - event.name != "" ? event.name!.capitalizeFirstofEach() : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this Event?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteEvent(event); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteEvent(Event event) async { - setState(() { - _isLoading = true; - }); - Map result = await eventBloc.deleteEvent(event); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - eventBloc.events.clear(); - await eventBloc.fetchEvents(); - await FirebaseAnalytics.instance.logEvent(name: "Event_Deleted"); - Navigator.pushReplacementNamed(context, '/events_list'); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, result['message'].toString(), event); - } - } - - showErrorMessage(BuildContext context, msg, Event event) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteEvent(event); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/events/events_list.dart b/lib/ui/screens/events/events_list.dart deleted file mode 100644 index c4a4372..0000000 --- a/lib/ui/screens/events/events_list.dart +++ /dev/null @@ -1,221 +0,0 @@ -import 'package:bottle_crm/bloc/event_bloc.dart'; -import 'package:bottle_crm/model/events.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:intl/intl.dart'; - -class EventsList extends StatefulWidget { - EventsList(); - @override - State createState() => _EventsListState(); -} - -class _EventsListState extends State { - List _events = []; - bool _isLoading = false; - bool _isFilter = false; - bool _isNextPageLoading = false; - ScrollController? scrollController; - - @override - void initState() { - setState(() { - _events = eventBloc.events; - }); - super.initState(); - } - - Widget _buildTasksList() { - return _events.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _events.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - eventBloc.currentEvent = _events[index]; - Navigator.pushNamed(context, '/event_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _events[index] - .name! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Text(DateFormat("dd-MM-yyyy").format(DateTime.parse(_events[index].dateOfMeeting!)), - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)) - ], - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - //crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - _events[index].startDate!+" "+_events[index].startTime! , - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - Text( - "To" , - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 22)), - Text( - _events[index].endDate!+" "+_events[index].endTime! , - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)) - ], - ), - ) - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Accounts Found.'), - ); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - Navigator.pushReplacementNamed( - context, "/more_options"); - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Events', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Expanded( - child: Container( - color: Colors.white, - child: _buildTasksList(), - ), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - // accountBloc.currentEditAccountId = ""; - // if (accountBloc.openAccounts.length == 0) { - // showAlertDialog(context); - // } else { - Navigator.pushNamed(context, '/event_create'); - // } - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } -} diff --git a/lib/ui/screens/http_excepion.dart b/lib/ui/screens/http_excepion.dart deleted file mode 100644 index 43518df..0000000 --- a/lib/ui/screens/http_excepion.dart +++ /dev/null @@ -1,10 +0,0 @@ -class HttpException implements Exception { - final String errorMessage; - - HttpException(this.errorMessage); - - @override - String toString() { - return errorMessage; - } -} diff --git a/lib/ui/screens/invoices/invoice_create.dart b/lib/ui/screens/invoices/invoice_create.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/ui/screens/invoices/invoice_details.dart b/lib/ui/screens/invoices/invoice_details.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/ui/screens/invoices/invoices_list.dart b/lib/ui/screens/invoices/invoices_list.dart deleted file mode 100644 index dcc1d2a..0000000 --- a/lib/ui/screens/invoices/invoices_list.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; - -class InvoicesList extends StatefulWidget { - InvoicesList(); - @override - State createState() => _InvoicesListState(); -} - -class _InvoicesListState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Invoices List'), - ), - body: Center( - child: Text('NO Invoice Found'), - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } -} diff --git a/lib/ui/screens/leads/lead_create.dart b/lib/ui/screens/leads/lead_create.dart deleted file mode 100644 index ccd810c..0000000 --- a/lib/ui/screens/leads/lead_create.dart +++ /dev/null @@ -1,1715 +0,0 @@ -import 'dart:io'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; -//import 'package:textfield_tags/textfield_tags.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; - -class CreateLead extends StatefulWidget { - CreateLead(); - @override - State createState() => _CreateLeadState(); -} - -class _CreateLeadState extends State { - var _currentTabIndex = 0; - quill.QuillController _controller = quill.QuillController.basic(); - final GlobalKey _leadFormKey = GlobalKey(); - final GlobalKey _contactFormKey = GlobalKey(); - final GlobalKey _addressFormKey = GlobalKey(); - TextEditingController fileNameController = new TextEditingController(); - File? file = File(''); - Map _errors = {}; - bool _isLoading = false; - - List _leadFormKeys = [ - "title", - "account_name", - "website ", - "assigned_to", - "organization", - "status", - "source", - "tags", - "skype_ID", - "opportunity_amount", - "industry" - ]; - - List _contactFormKeys = [ - "first_name", - "last_name", - "email", - "phone", - ]; - - List _addressFormKeys = [ - "address_line", - "street", - "city", - "state", - "postcode", - "country", - ]; - - @override - void initState() { - super.initState(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return buildLeadBlock(); - } else if (_currentTabIndex == 1) { - return buildContactBlock(); - } else if (_currentTabIndex == 2) { - return buildAddressBlock(); - } else if (_currentTabIndex == 3) { - return buildDescriptionBlock(); - } - } - - Positioned buildReqField() { - return Positioned( - child: Container( - width: 3.0, - color: Colors.red, - ), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - _filePicker() async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(allowMultiple: false); - if (result != null) { - file = File(result.files[0].path!); - var _filename = file!.path.toString(); - var split = _filename.split('/'); - Map values = { - for (int i = 0; i < split.length; i++) i: split[i] - }; - setState(() { - fileNameController.text = values[7].toString(); - }); - } else {} - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildLeadBlock() { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_leadFormKey.currentState != null) - _leadFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _leadFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Lead Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['title'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - leadBloc.currentEditLead['title'] = value; - }, - ), - ), - _errors['title'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['title'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Website ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['website'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - hintText: 'https://www.bottlecrm.com', - enabledBorder: - buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: - buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - leadBloc.currentEditLead['website'] = - value; - }, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }), - ), - _errors['website'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['website'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Assigned To ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - // validator: (value) { - // if (value == null) { - // return 'Please select one or more options'; - // } - // return null; - // }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Users", - ), - initialValue: - leadBloc.currentEditLead['assigned_to'], - onSaved: (value) { - if (value == null) return; - leadBloc.currentEditLead['assigned_to'] = - value; - }, - ), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Status ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - height: screenHeight / 17, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => leadBloc.status, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - leadBloc.currentEditLead['status'] = ""; - } else { - leadBloc.currentEditLead['status'] = - selection; - } - }, - selectedItem: - leadBloc.currentEditLead['status'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Status", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Source ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - height: screenHeight / 17, - child: Stack(children: [ - DropdownSearch( - items: (filter, infiniteScrollProps) => leadBloc.source, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - leadBloc.currentEditLead['source'] = - ""; - } else { - leadBloc.currentEditLead['source'] = - selection; - } - }, - selectedItem: - leadBloc.currentEditLead['source'], - popupProps: PopupProps.bottomSheet( - itemBuilder: - (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, - vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: - screenWidth / 22)), - ); - }, - constraints: - BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Source", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ])), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Amount ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: leadBloc - .currentEditLead['opportunity_amount'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - onSaved: (value) { - leadBloc.currentEditLead[ - 'opportunity_amount'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'SkypeID ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['skype_ID'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - leadBloc.currentEditLead['skype_ID'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Attachment ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: fileNameController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: - buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: - buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: _filePicker, - icon: Icon(Icons.upload))), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'Industry ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - height: screenHeight / 17, - child: Stack(children: [ - DropdownSearch( - items: (filter, infiniteScrollProps) => leadBloc.industry, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - leadBloc.currentEditLead['industry'] = - ""; - } else { - leadBloc.currentEditLead['industry'] = - selection; - } - }, - selectedItem: - leadBloc.currentEditLead['industry'], - popupProps: PopupProps.bottomSheet( - itemBuilder: - (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, - vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: - screenWidth / 22)), - ); - }, - constraints: - BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Industry", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ])), - ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Container( - // width: screenWidth * 0.92, - // child: RichText( - // text: TextSpan( - // text: 'Tags ', - // style: buildLableTextStyle(), - // ), - // )), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 9, - // child: Container( - // margin: EdgeInsets.only(bottom: 5.0), - // child: TextFieldTags( - // initialTags: - // leadBloc.currentEditLead['tags'], - // textFieldStyler: TextFieldStyler( - // //contentPadding: EdgeInsets.all(12.0), - // textFieldBorder: - // buildBorder(Colors.black54), - // textFieldFocusedBorder: - // buildBorder(Colors.black54), - // hintText: 'Enter Tags', - // hintStyle: TextStyle(fontSize: 16.0), - // helperText: "", - // ), - // tagsStyler: TagsStyler( - // tagTextPadding: EdgeInsets.symmetric( - // horizontal: 5.0), - // tagDecoration: BoxDecoration( - // color: Colors.lightGreen[300], - // borderRadius: - // BorderRadius.circular(0.0), - // ), - // tagCancelIcon: Icon(Icons.cancel, - // size: 18.0, - // color: Colors.green[900]), - // tagPadding: const EdgeInsets.all(6.0)), - // onTag: (tag) { - // setState(() { - // leadBloc.currentEditLead['tags'] - // .add(tag); - // }); - // }, - // onDelete: (tag) { - // setState(() { - // leadBloc.currentEditLead['tags'] - // .remove(tag); - // }); - // }, - // ), - // ), - // ), - // ])), - ]))))), - ); - } - - Widget buildContactBlock() { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - _currentTabIndex = 2; - }); - }, - onSwipeRight: (offset) { - setState(() { - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - _currentTabIndex = 0; - }); - }, - child: Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _contactFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - width: screenWidth * 0.92, - child: RichText( - text: TextSpan( - text: 'First Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['first_name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - leadBloc.currentEditLead['first_name'] = - value; - }, - ), - ), - _errors['first_name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['first_name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'Last Name ', - style: buildLableTextStyle(), - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - height: screenHeight / 17, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['last_name'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - leadBloc.currentEditLead['last_name'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'Email Address ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['email'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - if (value.isNotEmpty && - !RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") - .hasMatch(value)) { - return 'Enter valid email address.'; - } - return null; - }, - onSaved: (value) { - leadBloc.currentEditLead['email'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'Phone Number ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['phone'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - hintText: '+91 XXXXXXXXXX', - ), - keyboardType: TextInputType.phone, - onSaved: (value) { - leadBloc.currentEditLead['phone'] = value; - }, - ), - ), - ])), - ]))))), - ); - } - - Widget buildAddressBlock() { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - _currentTabIndex = 3; - }); - }, - onSwipeRight: (offset) { - setState(() { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _addressFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'Address Line ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['address_line'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - leadBloc.currentEditLead['address_line'] = - value; - }, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - ), - ), - _errors['address_line'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['address_line'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'Street ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: leadBloc.currentEditLead['street'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - leadBloc.currentEditLead['street'] = value; - }, - ), - ), - _errors['street'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['street'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'City ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: leadBloc.currentEditLead['city'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - leadBloc.currentEditLead['city'] = value; - }, - ), - ), - _errors['city'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['city'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'State ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: leadBloc.currentEditLead['state'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - leadBloc.currentEditLead['state'] = value; - }, - ), - ), - _errors['state'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['state'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'Pincode ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - leadBloc.currentEditLead['postcode'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - leadBloc.currentEditLead['postcode'] = value; - }, - ), - ), - _errors['postcode'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['postcode'][0], - style: TextStyle( - color: Colors.red[700], fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - text: 'Country ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - height: screenHeight / 17, - child: Stack(children: [ - DropdownSearch( - items: (filter, infiniteScrollProps) => leadBloc.countries, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - leadBloc.currentEditLead['country'] = ""; - } else { - leadBloc.currentEditLead['country'] = - selection; - } - }, - selectedItem: - leadBloc.currentEditLead['country'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Country", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ])), - ])), - ])))), - ); - } - - Widget buildDescriptionBlock() { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: - Column( - children: [ - quill.QuillSimpleToolbar( - controller: _controller, - config: const quill.QuillSimpleToolbarConfig(), - ), - Expanded( - child: Container( - child: quill.QuillEditor.basic( - controller: _controller, - config: const quill.QuillEditorConfig()), - ), - ) - ], - ) - ), - ); - } - - @override - Widget build(BuildContext context) { - // Set readOnly property based on loading state - _controller.readOnly = _isLoading; - - return Scaffold( - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - leadBloc.cancelCurrentEditLead(); - leadBloc.currentEditLeadId = ""; - FocusScope.of(context).unfocus(); - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - leadBloc.currentEditLeadId == "" - ? 'Add Lead' - : "Edit Lead", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_leadFormKey.currentState != null) - _leadFormKey.currentState!.save(); - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - leadBloc.currentEditLead['description'] = - _controller.document.toPlainText(); - FocusScope.of(context).unfocus(); - if (!_isLoading) _submitForm(); // - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: !_isLoading - ? BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))) - : BoxDecoration(), - width: screenWidth * 0.15, - child: !_isLoading - ? Text( - 'Lead', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ) - : Container(), - ), - ), - GestureDetector( - onTap: () { - if (_leadFormKey.currentState != null) - _leadFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: !_isLoading - ? BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))) - : BoxDecoration(), - width: screenWidth * 0.15, - child: !_isLoading - ? Text( - 'Contact', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ) - : Container(), - ), - ), - GestureDetector( - onTap: () { - if (_leadFormKey.currentState != null) - _leadFormKey.currentState!.save(); - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: !_isLoading - ? BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 2 ? 3.0 : 0.0, - ))) - : BoxDecoration(), - width: screenWidth * 0.20, - child: !_isLoading - ? Text( - 'Address', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ) - : Container(), - ), - ), - GestureDetector( - onTap: () { - if (_leadFormKey.currentState != null) - _leadFormKey.currentState!.save(); - if (_contactFormKey.currentState != null) - _contactFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 3; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: !_isLoading - ? BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 3 ? 3.0 : 0.0, - ))) - : BoxDecoration(), - width: screenWidth * 0.25, - child: !_isLoading - ? Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 3 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ) - : Container(), - ), - ), - ], - ), - ), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_leadFormKey.currentState != null) { - if (!_leadFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _leadFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_contactFormKey.currentState != null) { - if (!_contactFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _contactFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 2; - }); - - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_addressFormKey.currentState != null) { - if (!_addressFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _addressFormKey.currentState!.save(); - - Map _result = {}; - if (leadBloc.currentEditLeadId != null && - leadBloc.currentEditLeadId != "") { - _result = await leadBloc.editLead(); - } else { - _result = await leadBloc.createLead(file: file); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - leadBloc.cancelCurrentEditLead(); - leadBloc.currentEditLeadId = ""; - showToaster(_result['message'], context); - leadBloc.openLeads.clear(); - leadBloc.offset = ""; - await leadBloc.fetchLeads(); - await FirebaseAnalytics.instance.logEvent(name: "Lead_Creatd"); - Navigator.pushReplacementNamed(context, '/leads_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _leadFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - for (var key in _contactFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 1; - }); - showToaster(_errors[key][0], context); - return; - } - } - for (var key in _addressFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 2; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/leads/lead_details.dart b/lib/ui/screens/leads/lead_details.dart deleted file mode 100644 index 774d3dc..0000000 --- a/lib/ui/screens/leads/lead_details.dart +++ /dev/null @@ -1,917 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/model/lead.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -import 'package:bottle_crm/ui/widgets/tags_widget.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -class LeadDetails extends StatefulWidget { - LeadDetails(); - @override - State createState() => _LeadDetailsState(); -} - -class _LeadDetailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Lead Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - leadBloc.currentEditLeadId = - leadBloc.currentLead!.id.toString(); - await leadBloc - .updateCurrentEditLead(leadBloc.currentLead!); - Navigator.pushNamed(context, '/leads_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - !_isLoading - ? Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Lead Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Attachment', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: - _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'Notes', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ) - : Container(), - ], - ), - ), - Expanded( - child: !_isLoading - ? Container( - child: buildTopBar(), - color: Colors.white, - ) - : Container( - height: screenHeight, - width: screenWidth, - color: Colors.white)) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ])); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return buildLeadInfoBlock(); - } else if (_currentTabIndex == 1) { - return buildAttachmentBlock(); - } else if (_currentTabIndex == 2) { - return buildDescriptionBlock(); - } - } - - buildAttachmentBlock() { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - color: Colors.white, - alignment: Alignment.center, - height: screenHeight, - width: screenWidth, - child: Text("No Attachments Found."), - ), - ); - } - - buildDescriptionBlock() { - return SwipeDetector( - onSwipeRight: (offset) { - if (_currentTabIndex == 2) { - setState(() { - _currentTabIndex = 1; - }); - } - }, - child: Container( - height: screenHeight, - width: screenWidth, - color: Colors.white, - alignment: Alignment.center, - child: Text( - leadBloc.currentLead!.description != "" - ? leadBloc.currentLead!.description! - : "No Description Found", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22.0)), - ), - ); - } - - buildLeadInfoBlock() { - return SwipeDetector( - onSwipeLeft: (offset) { - if (_currentTabIndex == 0) { - setState(() { - _currentTabIndex = 1; - }); - } - }, - child: Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Text( - leadBloc.currentLead!.title!.allInCaps, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 10.0), - Row(children: [ - leadBloc.currentLead!.opportunityAmount != "" - ? Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: ColorBrightness.dark), - child: Text( - leadBloc.currentLead!.opportunityAmount!, - style: TextStyle( - color: Colors.white, fontSize: 15.0), - ), - ) - : Container(), - leadBloc.currentLead!.status != "" - ? Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: ColorBrightness.dark), - child: Text( - leadBloc.currentLead!.status!, - style: TextStyle( - color: Colors.white, fontSize: 15.0), - ), - ) - : Container(), - ]), - SizedBox(height: 10.0), - TagViewWidget(leadBloc.currentLead!.tags!), - SizedBox(height: 10.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: ProfilePicViewWidget(leadBloc - .currentLead!.assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser.firstName![0].inCaps - : assignedUser.profileUrl) - .toList())), - ], - ), - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - child: Text( - "Create Date", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - child: Text( - leadBloc.currentLead!.createdOn!, - style: TextStyle( - color: Colors.grey, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - ], - ), - ), - Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - "Expected Close Date", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - child: Text( - leadBloc.currentLead!.createdOn!, - style: TextStyle( - color: Colors.grey, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - ], - ), - ) - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Text( - "http://www.micropyramid.com", - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 30), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - leadBloc.currentLead!.createdBy!.firstName! - .capitalizeFirstofEach() + - " ${leadBloc.currentLead!.createdBy!.lastName!}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.email_outlined, - size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - leadBloc.currentLead!.email! == "" - ? "-----" - : leadBloc.currentLead!.email!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - leadBloc.currentLead!.phone! == "" - ? "------" - : leadBloc.currentLead!.phone!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Image.asset( - "assets/images/skype.png", - width: screenWidth / 22, - )), - Text( - leadBloc.currentLead!.skypeID == "" - ? " ---------" - : " " + leadBloc.currentLead!.skypeID!, - style: TextStyle( - color: leadBloc.currentLead!.skypeID != "" - ? Colors.blue - : Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Industry :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "product & services", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Pipeline :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "----------", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Probability :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "----------", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "First Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - leadBloc.currentLead!.firstName != "" - ? leadBloc.currentLead!.firstName! - : "-----", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Last Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - leadBloc.currentLead!.lastName! == "" - ? "-----" - : leadBloc.currentLead!.lastName!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Job Title :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - leadBloc.currentLead!.title! == "" - ? "-----" - : leadBloc.currentLead!.title!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - )), - SizedBox(height: 10), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column(children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Address :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "${leadBloc.currentLead!.addressLine}, ${leadBloc.currentLead!.street}, ${leadBloc.currentLead!.city}, ${leadBloc.currentLead!.state}, ${leadBloc.currentLead!.postcode}, ${leadBloc.currentLead!.country}.", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ])), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteLeadAlertDialog(context, leadBloc.currentLead!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))), - ); - } - - void showDeleteLeadAlertDialog(BuildContext context, Lead lead) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - lead.firstName != "" - ? lead.firstName!.capitalizeFirstofEach() - : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this lead?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteLead(lead); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteLead(Lead lead) async { - setState(() { - _isLoading = true; - }); - Map result = await leadBloc.deleteLead(lead); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - leadBloc.openLeads.clear(); - await leadBloc.fetchLeads(); - await FirebaseAnalytics.instance.logEvent(name: "Lead_Deleted"); - Navigator.pushReplacementNamed(context, '/leads_list'); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, 'Something went wrong', lead); - } - } - - showErrorMessage(BuildContext context, msg, Lead lead) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteLead(lead); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/leads/leads_list.dart b/lib/ui/screens/leads/leads_list.dart deleted file mode 100644 index f157783..0000000 --- a/lib/ui/screens/leads/leads_list.dart +++ /dev/null @@ -1,868 +0,0 @@ -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -//import 'package:dropdown_search/dropdown_search.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; -import 'package:bottle_crm/model/lead.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; - -class LeadsList extends StatefulWidget { - LeadsList(); - @override - State createState() => _LeadsListState(); -} - -class _LeadsListState extends State { - var _currentTabIndex = 0; - final GlobalKey _filtersFormKey = GlobalKey(); - bool _isFilter = false; - List _openLeads = []; - List _closedLeads = []; - Map _filtersFormData = { - "title": "", - "source": null, - "assigned_to": [], - "status": null, - "tags": [] - }; - bool _isLoading = false; - bool _isNextPageLoading = false; - ScrollController? scrollController; - - @override - void initState() { - setState(() { - _openLeads = leadBloc.openLeads; - _closedLeads = leadBloc.closedLeads; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - leadBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - await leadBloc.fetchLeads( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isNextPageLoading = false; - }); - } - }); - super.initState(); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _submitForm() async { - if (_isFilter) { - _filtersFormKey.currentState!.save(); - } - setState(() { - _isLoading = true; - }); - leadBloc.offset = ""; - leadBloc.openLeads.clear(); - leadBloc.closedLeads.clear(); - await leadBloc.fetchLeads(filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isLoading = false; - }); - } - - _buildFilterBlock() { - return _isFilter - ? Container( - color: Colors.grey[100], - child: Form( - key: _filtersFormKey, - child: Column( - children: [ - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['title'], - cursorWidth: 3.0, - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter title", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['title'] = value; - }, - ), - ), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Status", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: leadBloc.status, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // leadBloc.currentEditLead["status"] = ""; - // } else { - // leadBloc.currentEditLead['status '] = - // selection; - // } - // }, - // selectedItem: - // leadBloc.currentEditLead['status '], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Status", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Source', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Source", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: leadBloc.source, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // leadBloc.currentEditLead["source"] = ""; - // } else { - // leadBloc.currentEditLead['source '] = - // selection; - // } - // }, - // selectedItem: - // leadBloc.currentEditLead['source '], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Source", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Source', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - SizedBox(height: 10.0), - Container( - padding: padding(), - child: Container( - width: screenWidth * 0.92, - child: Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Users", - ), - initialValue: _filtersFormData['assigned_to'], - onSaved: (value) { - if (value == null) return; - _filtersFormData['assigned_to'] = value; - }, - ), - ), - ), - ), - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - margin: EdgeInsets.only(bottom: 5.0), - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: leadBloc.filterTags, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text("Tags"), - initialValue: _filtersFormData['tags'], - onSaved: (value) { - if (value == null) return; - _filtersFormData['tags'] = value; - }, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _isFilter = false; - }); - FocusScope.of(context).unfocus(); - setState(() { - _filtersFormData = { - "title": "", - "source": null, - "assigned_to": [], - "status": null, - "tags": "" - }; - }); - _submitForm(); - }, - child: Text( - "Reset", - style: TextStyle(fontSize: screenWidth / 24), - )), - SizedBox(width: 20.0), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - ), - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - "Filter", - style: TextStyle(fontSize: screenWidth / 24), - )), - ], - ) - ], - ), - )) - : Container(); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return _buildOpenLeadList(); - } else if (_currentTabIndex == 1) { - return _buildClosedLeadList(); - } - } - - Widget _buildOpenLeadList() { - return _openLeads.length != 0 - ? SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - _closedLeads = leadBloc.closedLeads; - }); - }, - child: Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _openLeads.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - leadBloc.currentLead = _openLeads[index]; - Navigator.pushNamed(context, '/lead_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: - BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - _openLeads[index].title!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - child: ProfilePicViewWidget( - _openLeads[index] - .assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser - .firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - ), - Row( - children: [ - _openLeads[index].status != "" - ? Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, - vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: - ColorBrightness.light), - child: Text( - _openLeads[index].status == "" - ? "" - : _openLeads[index] - .status!, - style: TextStyle( - color: Colors.white, - fontSize: 15.0), - ), - ) - : Container(), - SizedBox(width: 10.0), - Container( - child: Text( - _openLeads[index] - .opportunityAmount == - "" - ? "โ‚น0" - : "โ‚น" + - _openLeads[index] - .opportunityAmount!, - style: TextStyle( - color: Theme.of(context) - .primaryColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w600)), - ) - ], - ), - ], - ), - ) - ], - ), - )); - }), - ), - ) - : SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight, - width: screenWidth, - color: Colors.white, - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Open Leas Found.'), - ), - ); - } - - Widget _buildClosedLeadList() { - return _closedLeads.length != 0 - ? SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _closedLeads.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - leadBloc.currentLead = _closedLeads[index]; - Navigator.pushNamed(context, '/lead_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: - BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - _closedLeads[index].title!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - child: ProfilePicViewWidget( - _closedLeads[index] - .assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser - .firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - ), - Row( - children: [ - _closedLeads[index].status != "" - ? Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, - vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: - ColorBrightness.light), - child: Text( - _closedLeads[index].status == - "" - ? "" - : _closedLeads[index] - .status!, - style: TextStyle( - color: Colors.white, - fontSize: 15.0), - ), - ) - : Container(), - SizedBox(width: 10.0), - Container( - child: Text( - _closedLeads[index] - .opportunityAmount == - "" - ? "โ‚น0" - : "โ‚น" + - _closedLeads[index] - .opportunityAmount!, - style: TextStyle( - color: Theme.of(context) - .primaryColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w600)), - ) - ], - ), - ], - ), - ) - ], - ), - )); - }), - ), - ) - : SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight, - width: screenWidth, - color: Colors.white, - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Closed Leads Found.'), - ), - ); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - currentBottomNavigationIndex == "4" - ? Navigator.pushReplacementNamed( - context, "/dashboard") - : Navigator.pushReplacementNamed( - context, "/more_options"); - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Leads', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - if (_currentTabIndex != 0) { - setState(() { - _currentTabIndex = 0; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Open', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_currentTabIndex != 1) { - setState(() { - _currentTabIndex = 1; - _closedLeads = leadBloc.closedLeads; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Closed', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ) - ], - ), - ), - ], - ), - ), - _buildFilterBlock(), - Expanded( - child: Container( - color: Colors.white, - child: GestureDetector(child: buildTopBar()), - ), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - leadBloc.currentEditLeadId = ""; - if (leadBloc.openLeads.length == 0 && - leadBloc.closedLeads.length == 0) { - showAlertDialog(context); - } else { - Navigator.pushNamed(context, '/leads_create'); - } - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any leads, Please create lead first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/leads_create"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/screens/more_options_screen.dart b/lib/ui/screens/more_options_screen.dart deleted file mode 100644 index 99cd87a..0000000 --- a/lib/ui/screens/more_options_screen.dart +++ /dev/null @@ -1,319 +0,0 @@ -import 'dart:core'; - -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class MoreOptions extends StatefulWidget { - @override - State createState() => _MoreOptionsState(); -} - -class _MoreOptionsState extends State { - final List _optionsList = [ - { - 'title': 'Contacts', - 'route': '/contacts_list', - 'icon': 'assets/images/contacts.svg' - }, - { - 'title': 'Opportunities', - 'route': '/opportunities_list', - 'icon': 'assets/images/opportunities.svg' - }, - { - 'title': 'Cases', - 'route': '/cases_list', - 'icon': 'assets/images/cases.svg' - }, - { - 'title': 'Documents', - 'route': '/documents_list', - 'icon': 'assets/images/documents.svg' - }, - { - 'title': 'Leads', - 'route': '/leads_list', - 'icon': 'assets/images/leads.svg' - }, - { - 'title': 'Invoices', - 'route': '/invoices_list', - 'icon': 'assets/images/invoices.svg' - }, - { - 'title': 'Events', - 'route': '/events_list', - 'icon': 'assets/images/events.svg' - }, - { - 'title': 'Teams', - 'route': '/teams_list', - 'icon': 'assets/images/teams.svg' - }, - { - 'title': 'Users', - 'route': '/users_list', - 'icon': 'assets/images/users.svg' - }, - { - 'title': 'Settings', - 'route': '/settings_List', - 'icon': 'assets/images/settings.svg' - }, - { - 'title': 'Change Password', - 'route': '/change_password', - 'icon': 'assets/images/change_password.svg' - }, - {'title': 'Logout', 'icon': 'assets/images/logout.svg'}, - ]; - - @override - void initState() { - super.initState(); - } - - void showLogoutAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Logout?", - style: TextStyle(color: Theme.of(context).secondaryHeaderColor), - ), - content: Text( - "Are you sure you want to logout?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - SharedPreferences prefs = - await SharedPreferences.getInstance(); - prefs.remove('authToken'); - prefs.remove('org'); - currentBottomNavigationIndex = "0"; - await FirebaseAnalytics.instance - .logEvent(name: "User_Logged_Out"); - Navigator.pushReplacementNamed(context, "/login"); - }, - child: Text("Logout")), - ], - ); - }); - } - - buildProfile() { - return Container( - padding: EdgeInsets.only(left: 20.0, right: 10.0), - height: screenHeight * 0.15, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - margin: EdgeInsets.only(right: 10.0), - child: authBloc.userProfile!.profileUrl != null && - authBloc.userProfile!.profileUrl != "" - ? CircleAvatar( - radius: screenWidth / 11.5, - backgroundColor: Theme.of(context).primaryColor, - child: CircleAvatar( - radius: screenWidth / 12, - backgroundImage: - NetworkImage(authBloc.userProfile!.profileUrl!), - backgroundColor: Colors.white, - ), - ) - : CircleAvatar( - radius: screenWidth / 12, - child: Text( - authBloc.userProfile!.firstName![0].allInCaps, - style: TextStyle( - fontSize: screenWidth / 11, - fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor), - ), - backgroundColor: Colors.grey[200], - ), - ), - Container( - width: screenWidth * 0.6, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - authBloc.userProfile!.firstName!.capitalizeFirstofEach(), - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 24, - fontWeight: FontWeight.w500), - ), - SizedBox(height: 3.0), - Text( - authBloc.userProfile!.email!, - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 26, - fontWeight: FontWeight.w500), - ), - SizedBox(height: 3.0), - Text.rich(TextSpan(children: [ - TextSpan( - text: "Permissions: ", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 26, - fontWeight: FontWeight.w500)), - TextSpan( - text: authBloc.userProfile!.role!.toLowerCase() == - "admin" - ? 'Sales and Marketing' - : authBloc.userProfile!.hasSalesAccess! && - authBloc.userProfile!.hasMarketingAccess! - ? "Sales and Marketing" - : authBloc.userProfile!.hasSalesAccess! && - !authBloc - .userProfile!.hasMarketingAccess! - ? "Sales" - : !authBloc.userProfile! - .hasSalesAccess! && - authBloc.userProfile! - .hasMarketingAccess! - ? "Marketing" - : "---", - style: TextStyle( - color: Colors.green, - fontSize: screenWidth / 26, - fontWeight: FontWeight.w500)) - ])) - ], - ), - ), - Container( - child: IconButton( - onPressed: () { - Navigator.pushNamed(context, '/profile'); - }, - icon: Icon(Icons.arrow_forward_ios, - color: Colors.white, size: screenWidth / 15)), - ) - ], - ), - )); - } - - buildOptions() { - return Container( - padding: EdgeInsets.symmetric(vertical: 10.0), - child: ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: _optionsList.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - if (_optionsList[index]['title'] == "Logout") { - showLogoutAlertDialog(context); - } else { - Navigator.pushNamed(context, _optionsList[index]['route']); - } - }, - child: Column( - children: [ - Container( - child: Row( - children: [ - Container( - padding: - EdgeInsets.fromLTRB(screenWidth / 12, 8, 20, 8), - child: SvgPicture.asset( - _optionsList[index]['icon'], - width: screenWidth / 18, - ), - ), - Container( - child: Text( - _optionsList[index]['title'], - style: TextStyle( - color: _optionsList[index]['title'] == "Logout" - ? Colors.red - : Color.fromRGBO(75, 75, 78, 1), - fontSize: screenWidth / 24, - fontWeight: FontWeight.w500), - ), - ) - ], - ), - ), - index == _optionsList.length - 1 - ? Container() - : Container( - padding: EdgeInsets.symmetric(horizontal: 20.0), - child: Divider(color: Colors.grey[200])) - ], - ), - ); - }, - ), - ); - } - - @override - Widget build(BuildContext context) { - return PopScope( - canPop: true, - child: Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Text( - 'More Options', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ), - buildProfile(), - Expanded( - child: Container( - color: Colors.white, - child: buildOptions(), - )) - ], - ), - ), - ), - bottomNavigationBar: BottomNavigationBarWidget(), - ), - ); - } -} diff --git a/lib/ui/screens/opportunities/opportunitie_create.dart b/lib/ui/screens/opportunities/opportunitie_create.dart deleted file mode 100644 index 5e61094..0000000 --- a/lib/ui/screens/opportunities/opportunitie_create.dart +++ /dev/null @@ -1,1024 +0,0 @@ -import 'dart:io'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/dashboard_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:bottle_crm/bloc/opportunity_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:intl/intl.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; -//import 'package:textfield_tags/textfield_tags.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; - -class CreateOpportunities extends StatefulWidget { - CreateOpportunities(); - @override - State createState() => _CreateOpportunitiesState(); -} - -class _CreateOpportunitiesState extends State { - quill.QuillController _controller = quill.QuillController.basic(); - final GlobalKey _opportunityFormKey = GlobalKey(); - TextEditingController _dateController = TextEditingController(); - TextEditingController fileNameController = new TextEditingController(); - String? selectedDate = ""; - DateTime? initialDate = DateTime.now(); - var _currentTabIndex = 0; - Map _errors = {}; - bool _isLoading = false; - File file = new File(''); - List _opportunityFormKeys = [ - "name", - "account", - "amount", - "contacts", - "currency", - "stage", - "lead_source", - "probability", - "assigned_to", - "contacts", - "due_date", - "tags", - "opportunity_attachment" - "teams" - ]; - - @override - void initState() { - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - super.initState(); - } - - _selectDate(BuildContext context) async { - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate!, - firstDate: DateTime(1950), - lastDate: DateTime(2023), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_opportunityFormKey.currentState != null) - _opportunityFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: buildOpportunityBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: buildDescriptionBlock()); - } - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildOpportunityBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _opportunityFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: opportunityBloc - .currentEditOpportunity['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - opportunityBloc - .currentEditOpportunity['name'] = value; - }, - ), - ), - _errors['name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Lead Source ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => opportunityBloc.leadSourceObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc.currentEditOpportunity[ - 'lead_source'] = ""; - } else { - opportunityBloc.currentEditOpportunity[ - 'lead_source'] = selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['lead_source'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Lead Source", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Account", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => opportunityBloc.accountsObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc - .currentEditOpportunity['account'] = ""; - } else { - opportunityBloc - .currentEditOpportunity['account'] = - selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['account'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Lead Source", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Probability ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - maxLength: 2, - initialValue: opportunityBloc - .currentEditOpportunity['probability'] - .toString(), - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - onSaved: (value) { - opportunityBloc.currentEditOpportunity[ - 'probability'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Amount ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: opportunityBloc - .currentEditOpportunity['amount'] - .toString(), - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - onSaved: (value) { - opportunityBloc - .currentEditOpportunity['amount'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Teams", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: teamBloc.teamsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "teams", - ), - initialValue: opportunityBloc - .currentEditOpportunity['teams'], - onSaved: (value) { - if (value == null) return; - opportunityBloc - .currentEditOpportunity['teams'] = - value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Currency", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => opportunityBloc.currencyObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc.currentEditOpportunity[ - 'currency'] = ""; - } else { - opportunityBloc.currentEditOpportunity[ - 'currency'] = selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['currency'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Currency", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Stage ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => opportunityBloc.stageObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc - .currentEditOpportunity['stage'] = ""; - } else { - opportunityBloc - .currentEditOpportunity['stage'] = - selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['stage'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Stage", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Assigned to", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Users", - ), - initialValue: opportunityBloc - .currentEditOpportunity['assigned_to'], - onSaved: (value) { - if (value == null) return; - opportunityBloc.currentEditOpportunity[ - 'assigned_to'] = value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Contacts ', - style: buildLableTextStyle(), - children: [ - TextSpan( - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.contactsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Contacts", - ), - initialValue: opportunityBloc - .currentEditOpportunity['contacts'], - onSaved: (value) { - if (value == null) return; - opportunityBloc - .currentEditOpportunity['contacts'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Due Date ", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _dateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _selectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - // keyboardType: TextInputType.text - ), - ), - ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Tags", - // style: buildLableTextStyle(), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // margin: EdgeInsets.only(bottom: 5.0), - // child: TextFieldTags( - // initialTags: opportunityBloc.tags, - // textFieldStyler: TextFieldStyler( - // textFieldBorder: boxBorder(), - // textFieldFocusedBorder: boxBorder(), - // hintText: 'Enter Tags', - // hintStyle: TextStyle(fontSize: 16.0), - // helperText: "", - // ), - // tagsStyler: TagsStyler( - // tagTextPadding: - // EdgeInsets.symmetric(horizontal: 5.0), - // tagDecoration: BoxDecoration( - // color: Colors.lightGreen[300], - // borderRadius: BorderRadius.circular(0.0), - // ), - // tagCancelIcon: Icon(Icons.cancel, - // size: 18.0, color: Colors.green[900]), - // tagPadding: const EdgeInsets.all(6.0)), - // onTag: (tag) { - // setState(() { - // opportunityBloc - // .currentEditOpportunity['tags'] - // .add(tag); - // }); - // }, - // onDelete: (tag) { - // setState(() { - // opportunityBloc - // .currentEditOpportunity['tags'] - // .remove(tag); - // }); - // }, - // ), - // ), - // ])), - ]))))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - quill.QuillSimpleToolbar( - controller: _controller, - config: const quill.QuillSimpleToolbarConfig(), - ), - Expanded( - child: Container( - child: quill.QuillEditor.basic( - controller: _controller, - config: const quill.QuillEditorConfig()), - ), - ) - ], - )); - } - - @override - Widget build(BuildContext context) { - // Set readOnly property based on loading state - _controller.readOnly = _isLoading; - - return Scaffold( - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - dashboardBloc.fetchDashboardDetails(); - opportunityBloc.cancelCurrentEditOpportunity(); - opportunityBloc.currentEditOpportunityId = ""; - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - opportunityBloc.currentEditOpportunityId == "" - ? 'Add Opportunity' - : "Edit Opportunity", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_opportunityFormKey.currentState != null) - _opportunityFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - // opportunityBloc.currentEditOpportunity['description'] = - // _controller.document.toPlainText(); - print(opportunityBloc - .currentEditOpportunity['description']); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Opportunity', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_opportunityFormKey.currentState != null) - _opportunityFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_opportunityFormKey.currentState != null) { - if (!_opportunityFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _opportunityFormKey.currentState!.save(); - await Future.delayed(const Duration(seconds: 1), () async {}); - - Map _result = {}; - if (opportunityBloc.currentEditOpportunityId != null && - opportunityBloc.currentEditOpportunityId != "") { - _result = await opportunityBloc.editOpportunity(); - } else { - _result = await opportunityBloc.createOpportunity(file: file); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - opportunityBloc.cancelCurrentEditOpportunity(); - opportunityBloc.currentEditOpportunityId = ""; - showToaster(_result['message'], context); - opportunityBloc.opportunities.clear(); - opportunityBloc.offset = ""; - await opportunityBloc.fetchOpportunities(); - opportunityBloc.opportunities; - await FirebaseAnalytics.instance.logEvent(name: "Opportunity_Created"); - Navigator.pushReplacementNamed(context, '/opportunities_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _opportunityFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/opportunities/opportunitie_create.dart.bak b/lib/ui/screens/opportunities/opportunitie_create.dart.bak deleted file mode 100644 index 223addf..0000000 --- a/lib/ui/screens/opportunities/opportunitie_create.dart.bak +++ /dev/null @@ -1,1024 +0,0 @@ -import 'dart:io'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/dashboard_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:bottle_crm/bloc/opportunity_bloc.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:intl/intl.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; -//import 'package:textfield_tags/textfield_tags.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; - -class CreateOpportunities extends StatefulWidget { - CreateOpportunities(); - @override - State createState() => _CreateOpportunitiesState(); -} - -class _CreateOpportunitiesState extends State { - quill.QuillController _controller = quill.QuillController.basic(); - final GlobalKey _opportunityFormKey = GlobalKey(); - TextEditingController _dateController = TextEditingController(); - TextEditingController fileNameController = new TextEditingController(); - String? selectedDate = ""; - DateTime? initialDate = DateTime.now(); - var _currentTabIndex = 0; - Map _errors = {}; - bool _isLoading = false; - File file = new File(''); - List _opportunityFormKeys = [ - "name", - "account", - "amount", - "contacts", - "currency", - "stage", - "lead_source", - "probability", - "assigned_to", - "contacts", - "due_date", - "tags", - "opportunity_attachment" - "teams" - ]; - - @override - void initState() { - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - super.initState(); - } - - _selectDate(BuildContext context) async { - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate!, - firstDate: DateTime(1950), - lastDate: DateTime(2023), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - if (_opportunityFormKey.currentState != null) - _opportunityFormKey.currentState!.save(); - _currentTabIndex = 1; - }); - }, - child: buildOpportunityBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: buildDescriptionBlock()); - } - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildOpportunityBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _opportunityFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: opportunityBloc - .currentEditOpportunity['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - opportunityBloc - .currentEditOpportunity['name'] = value; - }, - ), - ), - _errors['name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Lead Source ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => opportunityBloc.leadSourceObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc.currentEditOpportunity[ - 'lead_source'] = ""; - } else { - opportunityBloc.currentEditOpportunity[ - 'lead_source'] = selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['lead_source'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Lead Source", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Account", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => opportunityBloc.accountsObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc - .currentEditOpportunity['account'] = ""; - } else { - opportunityBloc - .currentEditOpportunity['account'] = - selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['account'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Lead Source", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Probability ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - maxLength: 2, - initialValue: opportunityBloc - .currentEditOpportunity['probability'] - .toString(), - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - onSaved: (value) { - opportunityBloc.currentEditOpportunity[ - 'probability'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Amount ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: opportunityBloc - .currentEditOpportunity['amount'] - .toString(), - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - onSaved: (value) { - opportunityBloc - .currentEditOpportunity['amount'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Teams", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: teamBloc.teamsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "teams", - ), - initialValue: opportunityBloc - .currentEditOpportunity['teams'], - onSaved: (value) { - if (value == null) return; - opportunityBloc - .currentEditOpportunity['teams'] = - value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Currency", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: opportunityBloc.currencyObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc.currentEditOpportunity[ - 'currency'] = ""; - } else { - opportunityBloc.currentEditOpportunity[ - 'currency'] = selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['currency'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Currency", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Stage ', - style: buildLableTextStyle(), - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: opportunityBloc.stageObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - opportunityBloc - .currentEditOpportunity['stage'] = ""; - } else { - opportunityBloc - .currentEditOpportunity['stage'] = - selection; - } - }, - selectedItem: opportunityBloc - .currentEditOpportunity['stage'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Stage", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Assigned to", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Users", - ), - initialValue: opportunityBloc - .currentEditOpportunity['assigned_to'], - onSaved: (value) { - if (value == null) return; - opportunityBloc.currentEditOpportunity[ - 'assigned_to'] = value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Contacts ', - style: buildLableTextStyle(), - children: [ - TextSpan( - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.contactsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Contacts", - ), - initialValue: opportunityBloc - .currentEditOpportunity['contacts'], - onSaved: (value) { - if (value == null) return; - opportunityBloc - .currentEditOpportunity['contacts'] = - value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Due Date ", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _dateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _selectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - // keyboardType: TextInputType.text - ), - ), - ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Tags", - // style: buildLableTextStyle(), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // margin: EdgeInsets.only(bottom: 5.0), - // child: TextFieldTags( - // initialTags: opportunityBloc.tags, - // textFieldStyler: TextFieldStyler( - // textFieldBorder: boxBorder(), - // textFieldFocusedBorder: boxBorder(), - // hintText: 'Enter Tags', - // hintStyle: TextStyle(fontSize: 16.0), - // helperText: "", - // ), - // tagsStyler: TagsStyler( - // tagTextPadding: - // EdgeInsets.symmetric(horizontal: 5.0), - // tagDecoration: BoxDecoration( - // color: Colors.lightGreen[300], - // borderRadius: BorderRadius.circular(0.0), - // ), - // tagCancelIcon: Icon(Icons.cancel, - // size: 18.0, color: Colors.green[900]), - // tagPadding: const EdgeInsets.all(6.0)), - // onTag: (tag) { - // setState(() { - // opportunityBloc - // .currentEditOpportunity['tags'] - // .add(tag); - // }); - // }, - // onDelete: (tag) { - // setState(() { - // opportunityBloc - // .currentEditOpportunity['tags'] - // .remove(tag); - // }); - // }, - // ), - // ), - // ])), - ]))))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - quill.QuillSimpleToolbar( - controller: _controller, - config: const quill.QuillSimpleToolbarConfig(), - ), - Expanded( - child: Container( - child: quill.QuillEditor.basic( - controller: _controller, - config: const quill.QuillEditorConfig()), - ), - ) - ], - )); - } - - @override - Widget build(BuildContext context) { - // Set readOnly property based on loading state - _controller.readOnly = _isLoading; - - return Scaffold( - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - dashboardBloc.fetchDashboardDetails(); - opportunityBloc.cancelCurrentEditOpportunity(); - opportunityBloc.currentEditOpportunityId = ""; - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - opportunityBloc.currentEditOpportunityId == "" - ? 'Add Opportunity' - : "Edit Opportunity", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_opportunityFormKey.currentState != null) - _opportunityFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - // opportunityBloc.currentEditOpportunity['description'] = - // _controller.document.toPlainText(); - print(opportunityBloc - .currentEditOpportunity['description']); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Opportunity', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_opportunityFormKey.currentState != null) - _opportunityFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ) - : Container(), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_opportunityFormKey.currentState != null) { - if (!_opportunityFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _opportunityFormKey.currentState!.save(); - await Future.delayed(const Duration(seconds: 1), () async {}); - - Map _result = {}; - if (opportunityBloc.currentEditOpportunityId != null && - opportunityBloc.currentEditOpportunityId != "") { - _result = await opportunityBloc.editOpportunity(); - } else { - _result = await opportunityBloc.createOpportunity(file: file); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - opportunityBloc.cancelCurrentEditOpportunity(); - opportunityBloc.currentEditOpportunityId = ""; - showToaster(_result['message'], context); - opportunityBloc.opportunities.clear(); - opportunityBloc.offset = ""; - await opportunityBloc.fetchOpportunities(); - opportunityBloc.opportunities; - await FirebaseAnalytics.instance.logEvent(name: "Opportunity_Created"); - Navigator.pushReplacementNamed(context, '/opportunities_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _opportunityFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/opportunities/opportunitie_details.dart b/lib/ui/screens/opportunities/opportunitie_details.dart deleted file mode 100644 index 24226e3..0000000 --- a/lib/ui/screens/opportunities/opportunitie_details.dart +++ /dev/null @@ -1,839 +0,0 @@ -import 'package:bottle_crm/bloc/opportunity_bloc.dart'; -import 'package:bottle_crm/model/opportunities.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:bottle_crm/ui/widgets/tags_widget.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class OpportunitiesDetails extends StatefulWidget { - OpportunitiesDetails(); - @override - State createState() => _OpportunitiesDetailsState(); -} - -class _OpportunitiesDetailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Opportunity Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - opportunityBloc.currentEditOpportunityId = - opportunityBloc.currentOpportunity!.id - .toString(); - await opportunityBloc - .updateCurrentEditOpportunity( - opportunityBloc.currentOpportunity!); - Navigator.pushNamed( - context, '/opportunitie_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - !_isLoading - ? Container( - child: Column( - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 - ? 3.0 - : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Opportunity Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 28, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 - ? 3.0 - : 0.0, - ))), - width: screenWidth * 0.24, - child: Text( - 'Attachments', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 2 - ? 3.0 - : 0.0, - ))), - width: screenWidth * 0.21, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - )), - ], - ), - ) - : Container(), - Expanded( - child: Container( - child: !_isLoading - ? buildTopBar() - : Container(color: Colors.white), - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ])); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildOpportunityInfoBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 2; - }); - }, - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: buildAttachmentBlock()); - } else if (_currentTabIndex == 2) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildDescriptionBlock()); - } - } - - buildAttachmentBlock() { - return Container( - alignment: Alignment.center, - height: screenHeight, - width: screenWidth, - color: Colors.white, - child: Text("No Attachment Found..."), - ); - } - - buildDescriptionBlock() { - return Container( - alignment: Alignment.center, - height: screenHeight, - width: screenWidth, - color: Colors.white, - child: Text( - opportunityBloc.currentOpportunity!.description != "" - ? opportunityBloc.currentOpportunity!.description! - : "No Description Found", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22.0)), - ); - } - - buildOpportunityInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Text( - opportunityBloc.currentOpportunity!.name!.allInCaps, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - opportunityBloc.currentOpportunity!.amount != "" - ? Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: ColorBrightness.dark), - child: Text( - opportunityBloc.currentOpportunity!.amount!, - style: TextStyle(color: Colors.white, fontSize: 15.0), - ), - ) - : Container(), - SizedBox(height: 10.0), - TagViewWidget(opportunityBloc.currentOpportunity!.tags!), - SizedBox(height: 10.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: ProfilePicViewWidget(opportunityBloc - .currentOpportunity!.assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser.firstName![0].inCaps - : assignedUser.profileUrl) - .toList()) - ), - ], - ), - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - opportunityBloc.currentOpportunity!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "Account :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - opportunityBloc.currentOpportunity!.account!.name == - null - ? "-----" - : opportunityBloc - .currentOpportunity!.account!.name!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Status :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - opportunityBloc.currentOpportunity!.stage!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Closed Date :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - opportunityBloc.currentOpportunity!.dueDate == null - ? "-----" - : opportunityBloc.currentOpportunity!.closedOn - .toString(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - )), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Lead Souece :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - opportunityBloc.currentOpportunity!.leadSource!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Amount :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - opportunityBloc.currentOpportunity!.amount!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - SizedBox(width: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - opportunityBloc - .currentOpportunity!.createdBy!.firstName! - .capitalizeFirstofEach() + - " ${opportunityBloc.currentOpportunity!.createdBy!.lastName!}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(width: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - " +91 0000 000 000", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 10.0), - Text( - " +91 0000 000 000", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Teams :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - opportunityBloc.currentOpportunity!.teams!.length == 0 - ? "-----" - : _getTeams(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "User :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - alignment: Alignment.centerLeft, - width: screenWidth * 0.50, - child: Text( - opportunityBloc - .currentOpportunity!.assignedTo!.length == - 0 - ? "-----" - : _getUsers(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )) - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Probability :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - alignment: Alignment.centerLeft, - width: screenWidth * 0.50, - child: Text( - opportunityBloc.currentOpportunity!.probability! - .toString() == - "" - ? "-----" - : opportunityBloc.currentOpportunity!.probability! - .toString(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )) - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteOpportunityAlertDialog( - context, opportunityBloc.currentOpportunity!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))); - } - - _getTeams() { - List teams = []; - - opportunityBloc.currentOpportunity!.teams!.forEach((_teams) { - String _teamsList; - _teamsList = _teams.name!; - teams.add(_teamsList); - }); - - return teams.toString().replaceAll("[", "").replaceAll("]", ""); - } - - _getUsers() { - List users = []; - - opportunityBloc.currentOpportunity!.assignedTo!.forEach((_teams) { - String _teamsList; - _teamsList = _teams.firstName!; - users.add(_teamsList); - }); - - return users.toString().replaceAll("[", "").replaceAll("]", ""); - } - - void showDeleteOpportunityAlertDialog( - BuildContext context, Opportunity opportunity) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - opportunity.name != "" - ? opportunity.name!.capitalizeFirstofEach() - : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this Opportunity?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteOpportunity(opportunity); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteOpportunity(Opportunity opportunity) async { - setState(() { - _isLoading = true; - }); - Map result = await opportunityBloc.deleteOpportunity(opportunity); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - opportunityBloc.opportunities.clear(); - opportunityBloc.fetchOpportunities(); - await FirebaseAnalytics.instance.logEvent(name: "Opportunity_Deleted"); - Navigator.pushNamedAndRemoveUntil( - context, '/opportunities_list', ((route) => false)); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, result['message'].toString(), opportunity); - } - } - - showErrorMessage(BuildContext context, msg, Opportunity opportunity) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteOpportunity(opportunity); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/opportunities/opportunities_list.dart b/lib/ui/screens/opportunities/opportunities_list.dart deleted file mode 100644 index cddc6c2..0000000 --- a/lib/ui/screens/opportunities/opportunities_list.dart +++ /dev/null @@ -1,780 +0,0 @@ -import 'package:bottle_crm/bloc/opportunity_bloc.dart'; -import 'package:bottle_crm/model/opportunities.dart'; -import 'package:bottle_crm/model/profile.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -//import 'package:dropdown_search/dropdown_search.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; - -class OpportunitiesList extends StatefulWidget { - OpportunitiesList(); - @override - State createState() => _OpportunitiesListState(); -} - -class _OpportunitiesListState extends State { - final GlobalKey _filtersFormKey = GlobalKey(); - bool _isFilter = false; - bool _isNextPageLoading = false; - ScrollController? scrollController; - List? _assignedUsers = []; - List _opportunities = []; - Map _filtersFormData = { - "name": "", - "account": "", - "stage": "", - "lead_source": "", - "tags": [] - }; - bool _isLoading = false; - - @override - void initState() { - setState(() { - _opportunities = opportunityBloc.opportunities; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - opportunityBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - await opportunityBloc.fetchOpportunities( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isNextPageLoading = false; - }); - } - }); - _getUsers(); - super.initState(); - } - - _getUsers() { - _opportunities.forEach((element) { - _assignedUsers!.addAll(element.assignedTo!); - }); - } - - _submitForm() async { - if (_isFilter) { - _filtersFormKey.currentState!.save(); - } - setState(() { - _isLoading = true; - }); - opportunityBloc.offset = ""; - opportunityBloc.opportunities.clear(); - await opportunityBloc.fetchOpportunities( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isLoading = false; - }); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _buildFilterBlock() { - return _isFilter - ? Container( - color: Colors.grey[100], - child: Form( - key: _filtersFormKey, - child: Column( - children: [ - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter Name", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['name'] = value; - }, - ), - ), - SizedBox(height: 10.0), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Stage", - // style: buildLableTextStyle(), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // height: 48.0, - // margin: EdgeInsets.only(bottom: 5.0), - // child: DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: opportunityBloc.stageObjforDropDown, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // opportunityBloc - // .currentEditOpportunity['stage'] = ""; - // } else { - // opportunityBloc - // .currentEditOpportunity['stage'] = - // selection; - // } - // }, - // selectedItem: opportunityBloc - // .currentEditOpportunity['stage'], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Stage", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Stage', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ) - // ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Account", - // style: buildLableTextStyle(), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // height: 48.0, - // margin: EdgeInsets.only(bottom: 5.0), - // child: DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: opportunityBloc.accountsObjforDropDown, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // opportunityBloc - // .currentEditOpportunity['account'] = ""; - // } else { - // opportunityBloc - // .currentEditOpportunity['account'] = - // selection; - // } - // }, - // selectedItem: opportunityBloc - // .currentEditOpportunity['account'], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Account", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Account', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ) - // ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Lead Source", - // style: buildLableTextStyle(), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // height: 48.0, - // margin: EdgeInsets.only(bottom: 5.0), - // child: DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: opportunityBloc.leadSourceObjforDropDown, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // opportunityBloc.currentEditOpportunity[ - // 'lead_source'] = ""; - // } else { - // opportunityBloc.currentEditOpportunity[ - // 'lead_source'] = selection; - // } - // }, - // selectedItem: opportunityBloc - // .currentEditOpportunity['lead_source'], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Lead Source", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Lead source', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ) - // ])), - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - margin: EdgeInsets.only(bottom: 5.0), - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: opportunityBloc.filtertags, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text("Tags"), - initialValue: _filtersFormData['tags'], - onSaved: (value) { - if (value == null) return; - _filtersFormData['tags'] = value; - }, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _isFilter = false; - }); - FocusScope.of(context).unfocus(); - setState(() { - _filtersFormData = { - "name": "", - "account": "", - "stage": "", - "lead_source": "", - "tags": [] - }; - }); - _submitForm(); - }, - child: Text( - "Reset", - style: TextStyle(fontSize: screenWidth / 24), - )), - SizedBox(width: 20.0), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - ), - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - "Filter", - style: TextStyle(fontSize: screenWidth / 24), - )), - ], - ) - ], - ), - )) - : Container(); - } - - Widget _buildopportunitiesList() { - return _opportunities.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _opportunities.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - opportunityBloc.currentOpportunity = - _opportunities[index]; - Navigator.pushNamed(context, '/opportunitie_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - child: Text( - _opportunities[index] - .name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 22), - ), - ), - ], - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - // _opportunities[index].assignedTo!.length != 0 - // ? Container( - // child: Row( - // children: [ - // _opportunities[index] - // .assignedTo![0] - // .profileUrl != - // "" - // ? CircleAvatar( - // radius: screenWidth / 25, - // backgroundImage: - // NetworkImage( - // _opportunities[ - // index] - // .assignedTo![ - // 0] - // .profileUrl!), - // ) - // : CircleAvatar( - // radius: screenWidth / 25, - // backgroundColor: - // randomColor.randomColor( - // colorBrightness: - // ColorBrightness - // .light), - // child: Text( - // _opportunities[index] - // .assignedTo![0] - // .firstName![0] - // .toUpperCase(), - // style: TextStyle( - // color: Colors.white, - // fontWeight: - // FontWeight - // .bold), - // ), - // ), - // SizedBox(width: 3.0), - // _opportunities[index] - // .assignedTo![0] - // .profileUrl != - // ""&&_opportunities[index].assignedTo!.length<=2 - // ? CircleAvatar( - // radius: screenWidth / 25, - // backgroundImage: - // NetworkImage( - // _opportunities[ - // index] - // .assignedTo![ - // 1] - // .profileUrl!), - // ) - // : CircleAvatar( - // radius: screenWidth / 25, - // backgroundColor: - // randomColor.randomColor( - // colorBrightness: - // ColorBrightness - // .light), - // child: Text( - // _opportunities[index] - // .assignedTo![1] - // .firstName![0] - // .toUpperCase(), - // style: TextStyle( - // color: Colors.white, - // fontWeight: - // FontWeight - // .bold), - // ), - // ), - // SizedBox(width: 3.0), - // CircleAvatar( - // radius: screenWidth / 25, - // backgroundColor: Colors.grey, - // child: Text( - // "JR", - // style: TextStyle( - // color: Colors.white, - // fontWeight: - // FontWeight.bold), - // ), - // ), - // ], - // ), - // ) - // : Container(), - ProfilePicViewWidget(_opportunities[index] - .assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser - .firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - Row( - children: [ - _opportunities[index].stage != "" - ? Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, - vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: - ColorBrightness.light), - child: Text( - _opportunities[index].stage!, - style: TextStyle( - color: Colors.white, - fontSize: 12.0), - ), - ) - : Container(), - SizedBox(width: 10.0), - Container( - child: Text( - _opportunities[index].amount == "" - ? "โ‚น0" - : "โ‚น" + - _opportunities[index] - .amount!, - style: TextStyle( - color: Theme.of(context) - .primaryColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w600)), - ) - ], - ), - ], - ), - ), - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Opportunitis Found.'), - ); - } - - - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - currentBottomNavigationIndex == "4" - ? Navigator.pushReplacementNamed( - context, "/dashboard") - : Navigator.pushReplacementNamed( - context, "/more_options"); - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Opportunities', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - ), - ], - ), - ), - _buildFilterBlock(), - Expanded( - child: Container( - color: Colors.white, - child: _buildopportunitiesList(), - ), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - opportunityBloc.currentEditOpportunityId = ""; - if (opportunityBloc.opportunities.length == 0) { - showAlertDialog(context); - } else { - Navigator.pushNamed(context, '/opportunitie_create'); - } - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any opportunity, Please create opportunity first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/opportunitie_create"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/screens/settings/settings.dart b/lib/ui/screens/settings/settings.dart deleted file mode 100644 index 1ac0a4c..0000000 --- a/lib/ui/screens/settings/settings.dart +++ /dev/null @@ -1,424 +0,0 @@ -import 'package:bottle_crm/bloc/setting_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:bottle_crm/model/user.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/tags_widget.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/model/settings.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class SettingsList extends StatefulWidget { - SettingsList(); - @override - State createState() => _SettingsListState(); -} - -class _SettingsListState extends State { - var _currentTabIndex = 0; - - List _settings = []; - List _users = []; - - bool _isLoading = false; - bool _isNextPageLoading = false; - ScrollController? scrollController; - - @override - void initState() { - setState(() { - _settings = settingsBloc.apiSettings; - _users = settingsBloc.usersList; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - settingsBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - setState(() { - _isNextPageLoading = false; - }); - } - }); - super.initState(); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: _buildSettingsList()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: _buildUsersBlock()); - } - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: [ - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, size: screenWidth / 18)), - Text( - 'Settings', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - if (_currentTabIndex != 0) { - setState(() { - _currentTabIndex = 0; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Api Settings', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - if (_currentTabIndex != 1) { - setState(() { - _currentTabIndex = 1; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Users', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ) - ], - ), - ), - ], - ), - ), - Expanded( - child: Container(color: Colors.white, child: buildTopBar()), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - Widget _buildSettingsList() { - return _settings.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _settings.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - settingsBloc.currentSettings = _settings[index]; - Navigator.pushNamed(context, '/settings_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _settings[index] - .title! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Container( - width: screenWidth * 0.3, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _settings[index] - .createdBy! - .profileUrl != - "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage( - _settings[index] - .createdBy! - .profileUrl!), - ) - : CircleAvatar( - radius: screenWidth / 25, - backgroundColor: - Theme.of(context) - .primaryColor, - child: Text( - _settings[index] - .createdBy! - .firstName![0] - .allInCaps, - style: TextStyle( - color: Colors.white, - fontWeight: - FontWeight.bold), - ), - ), - SizedBox(width: 5.0), - Text( - _settings[index] - .createdBy! - .firstName! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w500, - fontSize: screenWidth / 25)) - ], - ), - ) - ], - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.5, - child: TagViewWidget( - _settings[index].tags!)), - Text(_settings[index].createdOn!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)) - ], - ), - ) - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Api Settings Found.'), - ); - } - - Widget _buildUsersBlock() { - return _users.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _users.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - userBloc.currentUser = _users[index]; - Navigator.pushNamed(context, '/user_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - children: [ - _users[index].profilePic != "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage( - _users[index].profilePic!), - ) - : CircleAvatar( - radius: screenWidth / 25, - backgroundColor: - Theme.of(context).primaryColor, - child: Text( - _users[index] - .firstName![0] - .allInCaps, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), - ), - SizedBox(width: 10.0), - Column( - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _users[index] - .firstName! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Container( - width: screenWidth * 0.55, - child: Text(_users[index].role!, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.grey, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 30)), - ), - ], - ), - ], - ), - ), - SizedBox(height: 5.0), - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Users Found.'), - ); - } -} diff --git a/lib/ui/screens/settings/settings_details.dart b/lib/ui/screens/settings/settings_details.dart deleted file mode 100644 index 9ed7b87..0000000 --- a/lib/ui/screens/settings/settings_details.dart +++ /dev/null @@ -1,498 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/setting_bloc.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/tags_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class SettingsDeails extends StatefulWidget { - SettingsDeails(); - @override - State createState() => _SettingsDeailsState(); -} - -class _SettingsDeailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Settings Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - // GestureDetector( - // onTap: () async { - - // }, - // child: Container( - // decoration: BoxDecoration( - // borderRadius: - // BorderRadius.all(Radius.circular(3.0)), - // color: Colors.white, - // ), - // width: screenWidth * 0.18, - // height: screenHeight * 0.04, - // alignment: Alignment.center, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // children: [ - // SvgPicture.asset( - // 'assets/images/Icon_edit_color.svg', - // color: Theme.of(context).primaryColor, - // width: screenWidth / 25, - // ), - // Container( - // child: Text( - // "Edit", - // style: TextStyle( - // fontSize: screenWidth / 25, - // color: Theme.of(context).primaryColor, - // fontWeight: FontWeight.w500), - // ), - // ), - // ], - // ), - // ), - // ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Team Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ), - Expanded( - child: Container( - child: buildteamInfoBlock(), - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildteamInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - width: screenWidth, - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - settingsBloc.currentSettings!.title!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - SizedBox(height: 10.0), - TagViewWidget(settingsBloc.currentSettings!.tags!), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Create Date :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - settingsBloc.currentSettings!.createdOn!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "Closed on :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Status :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - ], - )), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - settingsBloc.currentSettings!.createdBy!.firstName! - .capitalizeFirstofEach() + - " ${settingsBloc.currentSettings!.createdBy!.lastName!}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: - Icon(Icons.email_outlined, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "settings@mp.com", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "------------", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - // GestureDetector( - // onTap: () { - // if (!_isLoading) - // showDeleteteamAlertDialog(context, settingsBloc.currentTeam!); - // }, - // child: Container( - // padding: EdgeInsets.all(8.0), - // decoration: BoxDecoration( - // border: Border.all(width: 1.0, color: Colors.red.shade100), - // borderRadius: BorderRadius.all(Radius.circular(3.0)), - // color: Colors.white, - // ), - // width: screenWidth * 0.25, - // alignment: Alignment.center, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // SvgPicture.asset( - // 'assets/images/icon_delete_color.svg', - // width: screenWidth / 25, - // ), - // SizedBox(width: 10.0), - // Container( - // child: Text( - // "Delete", - // style: TextStyle( - // fontSize: screenWidth / 23, - // color: Colors.red, - // fontWeight: FontWeight.w500), - // ), - // ), - // ], - // ), - // ), - // ) - ]))); - } - - // void showDeleteteamAlertDialog(BuildContext context, Settings settings) { - // showDialog( - // context: context, - // builder: (BuildContext context) { - // return CupertinoAlertDialog( - // title: Text( - // settings.title!.capitalizeFirstofEach(), - // style: TextStyle(color: Colors.black), - // ), - // content: Text( - // "Are you sure you want to delete this team?", - // style: TextStyle(fontSize: 15.0), - // ), - // actions: [ - // CupertinoDialogAction( - // isDefaultAction: true, - // onPressed: () { - // Navigator.pop(context); - // }, - // child: Text("Cancel")), - // CupertinoDialogAction( - // textStyle: TextStyle(color: Colors.red), - // isDefaultAction: true, - // onPressed: () async { - // Navigator.pop(context); - // deleteSettings(settings); - // }, - // child: Text("Delete")), - // ], - // ); - // }); - // } - - // deleteSettings(Settings settings) async { - // setState(() { - // _isLoading = true; - // }); - // Map result = await settingsBloc.deleteTeam(team); - // setState(() { - // _isLoading = false; - // }); - // if (result['error'] == false) { - // showToaster(result['message'], context); - // teamBloc.teams.clear(); - // await teamBloc.fetchTeams(); - // await FirebaseAnalytics.instance.logEvent(name: "team_Deleted"); - // Navigator.pushReplacementNamed(context, '/teams_list'); - // } else if (result['error'] == true) { - // showToaster(result['message'], context); - // } else { - // showErrorMessage(context, result['message'].toString(), team); - // } - // } - - // showErrorMessage(BuildContext context, msg, Team team) { - // return showDialog( - // context: context, - // builder: (_) => AlertDialog( - // title: Text('Alert'), - // content: Text(msg), - // actions: [ - // TextButton( - // onPressed: () { - // Navigator.pop(context); - // deleteTeam(team); - // }, - // child: Text('RETRY')) - // ], - // )); - // } -} diff --git a/lib/ui/screens/settings/settings_userDetails.dart b/lib/ui/screens/settings/settings_userDetails.dart deleted file mode 100644 index b0618cb..0000000 --- a/lib/ui/screens/settings/settings_userDetails.dart +++ /dev/null @@ -1,512 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class SettingsUserDetails extends StatefulWidget { - SettingsUserDetails(); - @override - State createState() => _SettingsUserDetailsState(); -} - -class _SettingsUserDetailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'User Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - // GestureDetector( - // onTap: () async { - // if (!_isLoading) { - // userBloc.currentEditUserId = - // userBloc.currentUser!.id.toString(); - // await userBloc - // .updateCurrentEditUser(userBloc.currentUser!); - // Navigator.pushNamed(context, '/user_create'); - // } - // }, - // child: Container( - // decoration: BoxDecoration( - // borderRadius: - // BorderRadius.all(Radius.circular(3.0)), - // color: Colors.white, - // ), - // width: screenWidth * 0.18, - // height: screenHeight * 0.04, - // alignment: Alignment.center, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // children: [ - // SvgPicture.asset( - // 'assets/images/Icon_edit_color.svg', - // color: Theme.of(context).primaryColor, - // width: screenWidth / 25, - // ), - // Container( - // child: Text( - // "Edit", - // style: TextStyle( - // fontSize: screenWidth / 25, - // color: Theme.of(context).primaryColor, - // fontWeight: FontWeight.w500), - // ), - // ), - // ], - // ), - // ), - // ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'User Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ), - Expanded( - child: Container( - child: buildTopBar(), - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return buildUserInfoBlock(); - } else if (_currentTabIndex == 1) { - return buildDescriptionBlock(); - } - } - - buildDescriptionBlock() { - return Container( - child: Center( - child: Text("No Notes Found."), - ), - ); - } - - buildUserInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - width: screenWidth, - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "First Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.firstName! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Last Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.lastName! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Role :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.role! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 5.0), - Text( - "https//:www.micropyramid.com", - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 25), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: - Icon(Icons.email_outlined, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.email!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.phone!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Image.asset( - "assets/images/skype.png", - width: screenWidth / 22, - )), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Text( - // " skype@Id", - // style: TextStyle( - // color: Colors.blue, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - ], - ), - ], - ), - ), - SizedBox(height: 10), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column(children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Address :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "${userBloc.currentUser!.adressLine} ${userBloc.currentUser!.street!}, ${userBloc.currentUser!.city!}, ${userBloc.currentUser!.state!}, ${userBloc.currentUser!.country!}, ${userBloc.currentUser!.pincode!}.", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ])), - SizedBox(height: 10.0), - ]))); - } -} diff --git a/lib/ui/screens/tasks/task_create.dart b/lib/ui/screens/tasks/task_create.dart deleted file mode 100644 index a33ff19..0000000 --- a/lib/ui/screens/tasks/task_create.dart +++ /dev/null @@ -1,867 +0,0 @@ -import 'dart:io'; - -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/task_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:bottle_crm/utils/utils.dart'; -import 'package:intl/intl.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; - -class CreateTask extends StatefulWidget { - CreateTask(); - @override - State createState() => _CreateTaskState(); -} - -class _CreateTaskState extends State { - final GlobalKey _taskFormKey = GlobalKey(); - TextEditingController _dateController = TextEditingController(); - TextEditingController fileNameController = new TextEditingController(); - String? selectedDate = ""; - DateTime initialDate = DateTime.now(); - var _currentTabIndex = 0; - Map _errors = {}; - bool _isLoading = false; - File? file = File(''); - List _taskFormKeys = [ - 'title', - 'status', - 'priority', - 'account', - 'contacts', - 'closed_on', - 'assigned_to', - 'teams', - ]; - - @override - void initState() { - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - super.initState(); - } - - _selectDate(BuildContext context) async { - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: DateTime(1950), - lastDate: DateTime(2023), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return buildTaskBlock(); - } else if (_currentTabIndex == 1) { - return buildDescriptionBlock(); - } - } - - Positioned buildReqField() { - return Positioned( - child: Container( - width: 3.0, - color: Colors.red, - ), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildTaskBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _taskFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Task Title ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - taskBloc.currentEditTask!['title'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - taskBloc.currentEditTask!['title'] = value; - }, - ), - ), - _errors['title'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['title'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Account ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => taskBloc.accountsObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - taskBloc.currentEditTask!['account'] = ""; - } else { - taskBloc.currentEditTask!['account'] = - selection; - } - }, - selectedItem: - taskBloc.currentEditTask!['account'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a account", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['account'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['account'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Contacts", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.contactsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Contacts", - ), - initialValue: - taskBloc.currentEditTask!['contacts'], - onSaved: (value) { - if (value == null) return; - taskBloc.currentEditTask!['contacts'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Status ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => taskBloc.status!, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - taskBloc.currentEditTask!['status'] = ""; - } else { - taskBloc.currentEditTask!['status'] = - selection; - } - }, - selectedItem: - taskBloc.currentEditTask!['status'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Status", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['status'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['status'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Priority ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: (filter, infiniteScrollProps) => taskBloc.priorities!, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - taskBloc.currentEditTask!['priority'] = ""; - } else { - taskBloc.currentEditTask!['priority'] = - selection; - } - }, - selectedItem: - taskBloc.currentEditTask!['priority'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Priority", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['priority'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['priority'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Due Date ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _dateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _selectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - taskBloc.currentEditTask!['due_date'] = value; - }, - ), - ), - _errors['due_date'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['due_date'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Assigned to", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Assigned to", - ), - initialValue: taskBloc - .currentEditTask!['assigned_to'], - onSaved: (value) { - if (value == null) return; - taskBloc.currentEditTask!['assigned_to'] = - value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Teams", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: teamBloc.teamsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "teams", - ), - initialValue: - taskBloc.currentEditTask!['teams'], - onSaved: (value) { - if (value == null) return; - taskBloc.currentEditTask!['teams'] = - value; - })) - ])), - ]))))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: SizedBox() - // Column( - // children: [ - // quill.QuillToolbar.basic( - // controller: _controller, - // showAlignmentButtons: true, - // showBackgroundColorButton: false, - // showCameraButton: false, - // showImageButton: false, - // showVideoButton: false, - // showDividers: false, - // showColorButton: false, - // showUndo: false, - // showRedo: false, - // showQuote: false, - // showClearFormat: false, - // showIndent: false, - // showLink: false, - // showCodeBlock: false, - // showInlineCode: false, - // showListCheck: false, - // ), - // Expanded( - // child: Container( - // child: quill.QuillEditor.basic( - // controller: _controller, - // readOnly: true, - // ), - // ), - // ) - // ], - // ) - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - taskBloc.cancelCurrentEditTask(); - taskBloc.currentEditTaskId = ""; - FocusScope.of(context).unfocus(); - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - taskBloc.currentEditTaskId == "" - ? 'Add Task' - : "Edit Task", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_taskFormKey.currentState != null) - _taskFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Task', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context).secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_taskFormKey.currentState != null) { - if (!_taskFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _taskFormKey.currentState!.save(); - // setState(() { - // _currentTabIndex = 1; - // }); - await Future.delayed(const Duration(seconds: 1), () async {}); - - Map _result = {}; - if (taskBloc.currentEditTaskId != null && - taskBloc.currentEditTaskId != "") { - _result = await taskBloc.editTask(); - } else { - _result = await taskBloc.createTask(); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - taskBloc.cancelCurrentEditTask(); - taskBloc.currentEditTaskId = ""; - showToaster(_result['message'], context); - taskBloc.tasks.clear(); - await taskBloc.fetchTasks(); - await FirebaseAnalytics.instance.logEvent(name: "task_Created"); - Navigator.pushReplacementNamed(context, '/tasks_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _taskFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/tasks/task_create.dart.bak b/lib/ui/screens/tasks/task_create.dart.bak deleted file mode 100644 index e7d03fd..0000000 --- a/lib/ui/screens/tasks/task_create.dart.bak +++ /dev/null @@ -1,867 +0,0 @@ -import 'dart:io'; - -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/task_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; - -import 'package:bottle_crm/utils/utils.dart'; -import 'package:intl/intl.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; - -class CreateTask extends StatefulWidget { - CreateTask(); - @override - State createState() => _CreateTaskState(); -} - -class _CreateTaskState extends State { - final GlobalKey _taskFormKey = GlobalKey(); - TextEditingController _dateController = TextEditingController(); - TextEditingController fileNameController = new TextEditingController(); - String? selectedDate = ""; - DateTime initialDate = DateTime.now(); - var _currentTabIndex = 0; - Map _errors = {}; - bool _isLoading = false; - File? file = File(''); - List _taskFormKeys = [ - 'title', - 'status', - 'priority', - 'account', - 'contacts', - 'closed_on', - 'assigned_to', - 'teams', - ]; - - @override - void initState() { - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(DateTime.now().toString())); - super.initState(); - } - - _selectDate(BuildContext context) async { - final DateTime? selected = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: DateTime(1950), - lastDate: DateTime(2023), - ); - if (selected != null && selected.toString() != selectedDate) - setState(() { - initialDate = selected; - selectedDate = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - _dateController.text = DateFormat("yyyy-MM-dd") - .format(DateFormat("yyyy-MM-dd").parse(selected.toString())); - }); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return buildTaskBlock(); - } else if (_currentTabIndex == 1) { - return buildDescriptionBlock(); - } - } - - Positioned buildReqField() { - return Positioned( - child: Container( - width: 3.0, - color: Colors.red, - ), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildTaskBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _taskFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Task Title ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - taskBloc.currentEditTask!['title'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - taskBloc.currentEditTask!['title'] = value; - }, - ), - ), - _errors['title'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['title'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Account ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: taskBloc.accountsObjforDropDown, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - taskBloc.currentEditTask!['account'] = ""; - } else { - taskBloc.currentEditTask!['account'] = - selection; - } - }, - selectedItem: - taskBloc.currentEditTask!['account'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a account", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['account'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['account'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Contacts", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - dataSource: contactBloc.contactsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Contacts", - ), - initialValue: - taskBloc.currentEditTask!['contacts'], - onSaved: (value) { - if (value == null) return; - taskBloc.currentEditTask!['contacts'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Status ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: taskBloc.status!, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - taskBloc.currentEditTask!['status'] = ""; - } else { - taskBloc.currentEditTask!['status'] = - selection; - } - }, - selectedItem: - taskBloc.currentEditTask!['status'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Status", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['status'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['status'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Priority ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - height: 48.0, - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownSearch( - items: taskBloc.priorities!, - onChanged: print, - onSaved: (selection) { - if (selection == null) { - taskBloc.currentEditTask!['priority'] = ""; - } else { - taskBloc.currentEditTask!['priority'] = - selection; - } - }, - selectedItem: - taskBloc.currentEditTask!['priority'], - popupProps: PopupProps.bottomSheet( - itemBuilder: (context, item, isSelected) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 15.0, vertical: 10.0), - child: Text(item!, - style: TextStyle( - fontSize: screenWidth / 22)), - ); - }, - constraints: BoxConstraints(maxHeight: 400), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - border: boxBorder(), - enabledBorder: boxBorder(), - focusedErrorBorder: boxBorder(), - focusedBorder: boxBorder(), - errorBorder: boxBorder(), - contentPadding: EdgeInsets.all(12), - hintText: "Search a Priority", - )), - showSearchBox: true, - showSelectedItems: false, - ), - ), - ), - _errors['priority'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['priority'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Due Date ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: _dateController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: () { - _selectDate(context); - }, - icon: Icon(Icons.calendar_today_outlined), - ), - ), - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - taskBloc.currentEditTask!['due_date'] = value; - }, - ), - ), - _errors['due_date'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['due_date'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Assigned to", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Assigned to", - ), - initialValue: taskBloc - .currentEditTask!['assigned_to'], - onSaved: (value) { - if (value == null) return; - taskBloc.currentEditTask!['assigned_to'] = - value; - })) - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Teams", - style: TextStyle( - fontSize: 18, color: Colors.black54), - ), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: teamBloc.teamsObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "teams", - ), - initialValue: - taskBloc.currentEditTask!['teams'], - onSaved: (value) { - if (value == null) return; - taskBloc.currentEditTask!['teams'] = - value; - })) - ])), - ]))))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: SizedBox() - // Column( - // children: [ - // quill.QuillToolbar.basic( - // controller: _controller, - // showAlignmentButtons: true, - // showBackgroundColorButton: false, - // showCameraButton: false, - // showImageButton: false, - // showVideoButton: false, - // showDividers: false, - // showColorButton: false, - // showUndo: false, - // showRedo: false, - // showQuote: false, - // showClearFormat: false, - // showIndent: false, - // showLink: false, - // showCodeBlock: false, - // showInlineCode: false, - // showListCheck: false, - // ), - // Expanded( - // child: Container( - // child: quill.QuillEditor.basic( - // controller: _controller, - // readOnly: true, - // ), - // ), - // ) - // ], - // ) - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - taskBloc.cancelCurrentEditTask(); - taskBloc.currentEditTaskId = ""; - FocusScope.of(context).unfocus(); - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - taskBloc.currentEditTaskId == "" - ? 'Add Task' - : "Edit Task", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_taskFormKey.currentState != null) - _taskFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Task', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context).secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_taskFormKey.currentState != null) { - if (!_taskFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _taskFormKey.currentState!.save(); - // setState(() { - // _currentTabIndex = 1; - // }); - await Future.delayed(const Duration(seconds: 1), () async {}); - - Map _result = {}; - if (taskBloc.currentEditTaskId != null && - taskBloc.currentEditTaskId != "") { - _result = await taskBloc.editTask(); - } else { - _result = await taskBloc.createTask(); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - taskBloc.cancelCurrentEditTask(); - taskBloc.currentEditTaskId = ""; - showToaster(_result['message'], context); - taskBloc.tasks.clear(); - await taskBloc.fetchTasks(); - await FirebaseAnalytics.instance.logEvent(name: "task_Created"); - Navigator.pushReplacementNamed(context, '/tasks_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _taskFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/tasks/task_details.dart b/lib/ui/screens/tasks/task_details.dart deleted file mode 100644 index f2d9a35..0000000 --- a/lib/ui/screens/tasks/task_details.dart +++ /dev/null @@ -1,583 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/task_bloc.dart'; -import 'package:bottle_crm/model/task.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; - -class TasskDeails extends StatefulWidget { - TasskDeails(); - @override - State createState() => _TasskDeailsState(); -} - -class _TasskDeailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Task Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - taskBloc.currentEditTaskId = - taskBloc.currentTask!.id.toString(); - await taskBloc - .updateCurrentEditTask(taskBloc.currentTask!); - Navigator.pushNamed(context, '/task_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Task Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ), - Expanded( - child: Container( - child: buildTaskInfoBlock(), - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildTaskInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - width: screenWidth, - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - taskBloc.currentTask!.title!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: ColorBrightness.light), - child: Text( - taskBloc.currentTask!.status!, - style: TextStyle(color: Colors.white, fontSize: 12.0), - ), - ), - SizedBox(height: 5.0), - Container( - child: Container( - child: Row( - children: [ - Container( - child: ProfilePicViewWidget(taskBloc - .currentTask!.assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser.firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - ), - ], - ), - ), - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Create Date :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - taskBloc.currentTask!.createdOn!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "Closed on :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - taskBloc.currentTask!.dueDate! == "" - ? "-----" - : taskBloc.currentTask!.dueDate!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Status :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - taskBloc.currentTask!.status!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - ], - )), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - taskBloc.currentTask!.createdBy!.firstName! - .capitalizeFirstofEach() + - " ${taskBloc.currentTask!.createdBy!.lastName!}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: - Icon(Icons.email_outlined, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "account@mp.com", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "------------", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Priority :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - taskBloc.currentTask!.priority == "" - ? "------" - : taskBloc.currentTask!.priority!, - style: TextStyle( - color: taskBloc.currentTask!.priority == "" - ? Colors.black45 - : Colors.blue, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteTaskAlertDialog(context, taskBloc.currentTask!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))); - } - - void showDeleteTaskAlertDialog(BuildContext context, Task task) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - task.title != "" ? task.title!.capitalizeFirstofEach() : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this Task?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteTask(task); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteTask(Task task) async { - setState(() { - _isLoading = true; - }); - Map result = await taskBloc.deleteTask(task); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - taskBloc.tasks.clear(); - await taskBloc.fetchTasks(); - await FirebaseAnalytics.instance.logEvent(name: "Task_Deleted"); - Navigator.pushReplacementNamed(context, '/tasks_list'); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, result['message'].toString(), task); - } - } - - showErrorMessage(BuildContext context, msg, Task task) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteTask(task); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/tasks/tasks_list.dart b/lib/ui/screens/tasks/tasks_list.dart deleted file mode 100644 index 3024699..0000000 --- a/lib/ui/screens/tasks/tasks_list.dart +++ /dev/null @@ -1,570 +0,0 @@ -import 'package:bottle_crm/model/task.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -//import 'package:dropdown_search/dropdown_search.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:bottle_crm/bloc/task_bloc.dart'; - -class TasksList extends StatefulWidget { - TasksList(); - @override - State createState() => _TasksListState(); -} - -class _TasksListState extends State { - final GlobalKey _filtersFormKey = GlobalKey(); - List _tasks = []; - bool _isFilter = false; - ScrollController? scrollController; - bool _isNextPageLoading = false; - Map _filtersFormData = {"title": "", "status": "", "priority": ""}; - bool _isLoading = false; - - @override - void initState() { - setState(() { - _tasks = taskBloc.tasks; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - taskBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - await taskBloc.fetchTasks( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isNextPageLoading = false; - }); - } - }); - super.initState(); - } - - _submitForm() async { - if (_isFilter) { - _filtersFormKey.currentState!.save(); - } - setState(() { - _isLoading = true; - }); - taskBloc.offset = ""; - taskBloc.tasks.clear(); - await taskBloc.fetchTasks(filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isLoading = false; - }); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _buildFilterBlock() { - return _isFilter - ? Container( - color: Colors.grey[100], - child: Form( - key: _filtersFormKey, - child: Column( - children: [ - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['title'], - cursorWidth: 3.0, - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter title", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['title'] = value; - }, - ), - ), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Status", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: taskBloc.status, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // taskBloc.currentEditTask!["status"] = - // ""; - // } else { - // taskBloc.currentEditTask!['status '] = - // selection; - // } - // }, - // selectedItem: - // taskBloc.currentEditTask!['status '], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Status", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Status', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Priority", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: taskBloc.priorities, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // taskBloc.currentEditTask!["priority"] = - // ""; - // } else { - // taskBloc.currentEditTask!["priority"] = - // selection; - // } - // }, - // selectedItem: - // taskBloc.currentEditTask!['priority '], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Source", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Priority', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - SizedBox(height: 10.0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _isFilter = false; - }); - FocusScope.of(context).unfocus(); - setState(() { - _filtersFormData = { - "title": "", - "status": [], - "priority": [] - }; - }); - _submitForm(); - }, - child: Text( - "Reset", - style: TextStyle(fontSize: screenWidth / 24), - )), - SizedBox(width: 20.0), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - ), - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - "Filter", - style: TextStyle(fontSize: screenWidth / 24), - )), - ], - ) - ], - ), - )) - : Container(); - } - - Widget _buildTasksList() { - return _tasks.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _tasks.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - taskBloc.currentTask = _tasks[index]; - Navigator.pushNamed(context, '/task_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _tasks[index] - .title! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Text(_tasks[index].createdOn!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)) - ], - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - children: [ - Container( - child: ProfilePicViewWidget( - _tasks[index] - .assignedTo! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser - .firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - ), - ], - ), - ), - Container( - padding: EdgeInsets.symmetric( - horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor( - colorBrightness: ColorBrightness.light), - child: Text( - _tasks[index].status!, - style: TextStyle( - color: Colors.white, fontSize: 12.0), - ), - ), - ], - ), - ) - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Accounts Found.'), - ); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - Navigator.pushReplacementNamed( - context, "/dashboard"); - currentBottomNavigationIndex="0"; - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Tasks', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - ), - ], - ), - ), - _buildFilterBlock(), - Expanded( - child: Container( - color: Colors.white, - child: _buildTasksList(), - ), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - taskBloc.currentEditTaskId = ""; - if (taskBloc.tasks.length == 0) { - showAlertDialog(context); - } else { - Navigator.pushNamed(context, '/task_create'); - } - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any accounts, Please create account first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/task_create"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/screens/teams/team_create.dart b/lib/ui/screens/teams/team_create.dart deleted file mode 100644 index 697daea..0000000 --- a/lib/ui/screens/teams/team_create.dart +++ /dev/null @@ -1,516 +0,0 @@ -import 'dart:io'; - -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:multiselect_formfield/multiselect_formfield.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class CreateTeam extends StatefulWidget { - CreateTeam(); - @override - State createState() => _CreateTeamState(); -} - -class _CreateTeamState extends State { - quill.QuillController _controller = quill.QuillController.basic(); - final GlobalKey _teamFormKey = GlobalKey(); - var _currentTabIndex = 0; - Map _errors = {}; - bool _isLoading = false; - File? file = File(''); - List _teamFormKeys = [ - 'name', - 'assign_users', - ]; - - List _deskey = [ - 'description', - ]; - - @override - void initState() { - super.initState(); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - if (_teamFormKey.currentState != null) { - _teamFormKey.currentState!.save(); - } - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildTeamBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: buildDescriptionBlock()); - } - } - - Positioned buildReqField() { - return Positioned( - child: Container( - width: 3.0, - color: Colors.red, - ), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - Widget buildTeamBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _teamFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Team Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: teamBloc.currentEditTeam!['name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - teamBloc.currentEditTeam!['name'] = value; - }, - ), - ), - _errors['name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Assign Users ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - child: MultiSelectFormField( - border: boxBorder(), - fillColor: Colors.white, - validator: (value) { - if (value == null) { - return 'Please select one or more options'; - } - return null; - }, - dataSource: userBloc.usersObjForDropdown, - textField: 'name', - valueField: 'id', - okButtonLabel: 'OK', - chipLabelStyle: - TextStyle(color: Colors.black), - cancelButtonLabel: 'CANCEL', - // required: true, - hintWidget: Text( - "Please choose one or more", - style: TextStyle(color: Colors.grey), - ), - title: Text( - "Assigned to", - ), - initialValue: teamBloc - .currentEditTeam!['assign_users'], - onSaved: (value) { - if (value == null) return; - teamBloc.currentEditTeam![ - 'assign_users'] = value; - })) - ])), - ]))))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - quill.QuillSimpleToolbar( - controller: _controller, - config: const quill.QuillSimpleToolbarConfig(), - ), - Expanded( - child: Container( - child: quill.QuillEditor.basic( - controller: _controller, - config: const quill.QuillEditorConfig()), - ), - ) - ], - )); - } - - @override - Widget build(BuildContext context) { - // Set readOnly property based on loading state - _controller.readOnly = _isLoading; - - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - teamBloc.cancelCurrentEditTeam(); - teamBloc.currentEditTeamId = ""; - FocusScope.of(context).unfocus(); - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - teamBloc.currentEditTeamId == "" - ? 'Add Team' - : "Edit Team", - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - if (_teamFormKey.currentState != null) - _teamFormKey.currentState!.save(); - FocusScope.of(context).unfocus(); - // teamBloc.currentEditTeam!['description'] = - // _controller.document.toPlainText(); - if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Team', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context).secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_teamFormKey.currentState != null) - _teamFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context).secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_teamFormKey.currentState != null) { - if (!_teamFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _teamFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - await Future.delayed(const Duration(seconds: 1), () async {}); - // if (_controller.getPlainText().isEmpty) { - // setState(() { - // _isLoading = false; - // }); - // showToaster('โš  Please enter Description.', context); - // return; - // } - - Map _result = {}; - if (teamBloc.currentEditTeamId != null && - teamBloc.currentEditTeamId != "") { - _result = await teamBloc.editTeam(); - } else { - _result = await teamBloc.createTeam(); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - teamBloc.cancelCurrentEditTeam(); - teamBloc.currentEditTeamId = ""; - showToaster(_result['message'], context); - teamBloc.teams.clear(); - await teamBloc.fetchTeams(); - await FirebaseAnalytics.instance.logEvent(name: "team_Created"); - Navigator.pushReplacementNamed(context, '/teams_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _teamFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - for (var key in _deskey) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 1; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/teams/team_details.dart b/lib/ui/screens/teams/team_details.dart deleted file mode 100644 index 1d00257..0000000 --- a/lib/ui/screens/teams/team_details.dart +++ /dev/null @@ -1,523 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/model/team.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; - -class TeamkDeails extends StatefulWidget { - TeamkDeails(); - @override - State createState() => _TeamDeailsState(); -} - -class _TeamDeailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Team Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - teamBloc.currentEditTeamId = - teamBloc.currentTeam!.id.toString(); - await teamBloc - .updateCurrentEditTeam(teamBloc.currentTeam!); - Navigator.pushNamed(context, '/team_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'Team Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ), - Expanded( - child: Container( - child: buildteamInfoBlock(), - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildteamInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - width: screenWidth, - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Text( - teamBloc.currentTeam!.name!.capitalizeFirstofEach(), - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ), - SizedBox(height: 5.0), - Container( - child: Container( - child: Row( - children: [ - Container( - child: ProfilePicViewWidget(teamBloc - .currentTeam!.users! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser.firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - ), - ], - ), - ), - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Create Date :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - teamBloc.currentTeam!.createdOn!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.29, - child: Text( - "Closed on :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Status :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - ], - )), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Text( - teamBloc.currentTeam!.createdBy!.firstName! - .capitalizeFirstofEach() + - " ${teamBloc.currentTeam!.createdBy!.lastName!}", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: - Icon(Icons.email_outlined, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "account@mp.com", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "------------", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - GestureDetector( - onTap: () { - if (!_isLoading) - showDeleteteamAlertDialog(context, teamBloc.currentTeam!); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.red.shade100), - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.25, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/images/icon_delete_color.svg', - width: screenWidth / 25, - ), - SizedBox(width: 10.0), - Container( - child: Text( - "Delete", - style: TextStyle( - fontSize: screenWidth / 23, - color: Colors.red, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ]))); - } - - void showDeleteteamAlertDialog(BuildContext context, Team team) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - team.name != "" ? team.name!.capitalizeFirstofEach() : "", - style: TextStyle(color: Colors.black), - ), - content: Text( - "Are you sure you want to delete this team?", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - deleteTeam(team); - }, - child: Text("Delete")), - ], - ); - }); - } - - deleteTeam(Team team) async { - setState(() { - _isLoading = true; - }); - Map result = await teamBloc.deleteTeam(team); - setState(() { - _isLoading = false; - }); - if (result['error'] == false) { - showToaster(result['message'], context); - teamBloc.teams.clear(); - await teamBloc.fetchTeams(); - await FirebaseAnalytics.instance.logEvent(name: "team_Deleted"); - Navigator.pushReplacementNamed(context, '/teams_list'); - } else if (result['error'] == true) { - showToaster(result['message'], context); - } else { - showErrorMessage(context, result['message'].toString(), team); - } - } - - showErrorMessage(BuildContext context, msg, Team team) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - deleteTeam(team); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/teams/teams_list.dart b/lib/ui/screens/teams/teams_list.dart deleted file mode 100644 index 3111837..0000000 --- a/lib/ui/screens/teams/teams_list.dart +++ /dev/null @@ -1,562 +0,0 @@ -import 'package:bottle_crm/model/team.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:bottle_crm/ui/widgets/profile_pic_widget.dart'; -//import 'package:dropdown_search/dropdown_search.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; - -class TeamsList extends StatefulWidget { - TeamsList(); - @override - State createState() => _TeamsListState(); -} - -class _TeamsListState extends State { - final GlobalKey _filtersFormKey = GlobalKey(); - List _teams = []; - bool _isFilter = false; - ScrollController? scrollController; - bool _isNextPageLoading = false; - Map _filtersFormData = { - "team_name": "", - "created_by": "", - "assigned_users": "" - }; - bool _isLoading = false; - - @override - void initState() { - setState(() { - _teams = teamBloc.teams; - }); - scrollController = ScrollController(); - scrollController!.addListener(() async { - if (scrollController!.offset >= - scrollController!.position.maxScrollExtent && - !scrollController!.position.outOfRange && - teamBloc.offset != "" && - !_isNextPageLoading) { - setState(() { - _isNextPageLoading = true; - }); - await teamBloc.fetchTeams( - filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isNextPageLoading = false; - }); - } - }); - super.initState(); - } - - _submitForm() async { - if (_isFilter) { - _filtersFormKey.currentState!.save(); - } - setState(() { - _isLoading = true; - }); - teamBloc.offset = ""; - teamBloc.teams.clear(); - await teamBloc.fetchTeams(filtersData: _isFilter ? _filtersFormData : null); - setState(() { - _isLoading = false; - }); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _buildFilterBlock() { - return _isFilter - ? Container( - color: Colors.grey[100], - child: Form( - key: _filtersFormKey, - child: Column( - children: [ - SizedBox(height: 10.0), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: _filtersFormData['team_name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: "Enter team name", - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - onSaved: (value) { - _filtersFormData['team name'] = value; - }, - ), - ), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Created by", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: teamBloc.userObjForDropDown, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // teamBloc.currentEditTeam![ - // "created_by"] = ""; - // } else { - // teamBloc.currentEditTeam![ - // 'created_by'] = selection; - // } - // }, - // selectedItem: - // teamBloc.currentEditTeam!['created_by'], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a user", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'Created By', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Users", - // style: TextStyle( - // fontSize: 18, color: Colors.black54), - // ), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // height: screenHeight / 17, - // child: Stack(children: [ - // DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: teamBloc.userObjForDropDown, - // onChanged: print, - // onSaved: (selection) { - // if (selection == null) { - // teamBloc.currentEditTeam![ - // "assigned_users"] = ""; - // } else { - // teamBloc.currentEditTeam![ - // "assigned_users"] = selection; - // } - // }, - // selectedItem: teamBloc - // .currentEditTeam!['assigned_users '], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Source", - // )), - // popupTitle: Container( - // height: 50, - // decoration: BoxDecoration( - // color: - // Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'assigned users', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: - // (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // ), - // ])), - // ])), - SizedBox(height: 10.0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _isFilter = false; - }); - FocusScope.of(context).unfocus(); - setState(() { - _filtersFormData = { - "team_name": "", - "created_by": [], - "assigned_users": [] - }; - }); - _submitForm(); - }, - child: Text( - "Reset", - style: TextStyle(fontSize: screenWidth / 24), - )), - SizedBox(width: 20.0), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - ), - onPressed: () { - FocusScope.of(context).unfocus(); - _submitForm(); - }, - child: Text( - "Filter", - style: TextStyle(fontSize: screenWidth / 24), - )), - ], - ) - ], - ), - )) - : Container(); - } - - Widget _buildTeamsList() { - return _teams.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _teams.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - teamBloc.currentTeam = _teams[index]; - Navigator.pushNamed(context, '/team_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _teams[index] - .name! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Text(_teams[index].createdOn!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)) - ], - ), - ), - SizedBox(height: 5.0), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - children: [ - Container( - child: ProfilePicViewWidget(_teams[ - index] - .users! - .map((assignedUser) => - assignedUser.profileUrl == "" - ? assignedUser - .firstName![0].inCaps - : assignedUser.profileUrl) - .toList()), - ), - ], - ), - ), - ], - ), - ) - ], - ), - )); - }), - ) - : Center( - child: Text(_isLoading || _isNextPageLoading - ? "Fetching data..." - : 'No Teams Found.'), - ); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - Navigator.pushReplacementNamed( - context, "/more_options"); - }, - child: Icon(Icons.arrow_back_ios, - color: Colors.white, - size: screenWidth / 18)), - SizedBox(width: 10.0), - Text( - 'Teams', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - Container( - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - ), - ], - ), - ), - _buildFilterBlock(), - Expanded( - child: Container( - color: Colors.white, - child: _buildTeamsList(), - ), - ), - _isNextPageLoading - ? Container( - color: Colors.white, - child: Container( - width: 20.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())))) - : Container() - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - teamBloc.currentEditTeamId = ""; - if (teamBloc.teams.length == 0) { - showAlertDialog(context); - } else { - Navigator.pushNamed(context, '/team_create'); - } - }, - child: Icon(Icons.add, color: Colors.white), - backgroundColor: Theme.of(context).primaryColor, - ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any team, Please create team first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/team_create"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/screens/users/user_create.dart b/lib/ui/screens/users/user_create.dart deleted file mode 100644 index 24cff09..0000000 --- a/lib/ui/screens/users/user_create.dart +++ /dev/null @@ -1,1222 +0,0 @@ -import 'dart:io'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -//import 'package:dropdown_search/dropdown_search.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter/services.dart'; -//import 'package:flutter_quill/flutter_quill.dart' as quill; -import 'package:file_picker/file_picker.dart'; - -class CreateUser extends StatefulWidget { - CreateUser(); - @override - State createState() => _CreateUserState(); -} - -class _CreateUserState extends State { - int _currentTabIndex = 0; - //quill.QuillController _controller = quill.QuillController.basic(); - final GlobalKey _userFormKey = GlobalKey(); - final GlobalKey _addressFormKey = GlobalKey(); - TextEditingController fileNameController = new TextEditingController(); - File file = new File(''); - List _userFormKeys = [ - 'username', - 'role', - 'profile_pic', - 'date_joined', - 'email', - "password", - 'first_name', - 'last_name', - 'has_marketing_access', - 'has_sales_access', - 'is_active', - 'is_admin', - ]; - List _addressFormKeys = [ - "address_line ", - "street", - "city", - "state", - "pincode", - "country" - ]; - Map _errors = {}; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - OutlineInputBorder boxBorder() { - return OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - borderSide: BorderSide(width: 1, color: Colors.black45), - ); - } - - OutlineInputBorder buildBorder(Color color) { - return OutlineInputBorder( - borderSide: BorderSide(color: color, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(3.0))); - } - - EdgeInsets padding() { - return EdgeInsets.symmetric( - horizontal: screenWidth / 30, vertical: screenHeight / 80); - } - - _filePicker() async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(allowMultiple: false); - if (result != null) { - file = File(result.files[0].path!); - var _filename = file.path.toString(); - var split = _filename.split('/'); - Map values = { - for (int i = 0; i < split.length; i++) i: split[i] - }; - setState(() { - fileNameController.text = values[7].toString(); - }); - } else {} - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return buildUserBlock(); - } else if (_currentTabIndex == 1) { - return buildAddressBlock(); - } else if (_currentTabIndex == 2) { - return buildDescriptionBlock(); - } - } - - Widget buildUserBlock() { - return Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _userFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'First Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - userBloc.currentEditUser['first_name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['first_name'] = - value; - }, - ), - ), - _errors['first_name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['first_name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - verticalDirection: VerticalDirection.down, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Lase Name ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - userBloc.currentEditUser['last_name'], - cursorWidth: 3.0, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['last_name'] = value; - }, - ), - ), - _errors['last_name'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['last_name'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Phone ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: userBloc.currentEditUser['phone'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - hintText: '+91XXXXXXXXXX', - ), - keyboardType: TextInputType.phone, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['phone'] = value; - }, - ), - ), - _errors['phone'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['phone'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Primary Email ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: userBloc.currentEditUser['email'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - if (value.isNotEmpty && - !RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") - .hasMatch(value)) { - return 'Enter valid email address.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['email'] = value; - }, - ), - ), - _errors['email'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['email'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Secondary Email ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: userBloc.currentEditUser['email'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - if (value.isNotEmpty && - !RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") - .hasMatch(value)) { - return 'Enter valid email address.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['alternate_email'] = - value; - }, - ), - ), - _errors['alternate_email'] != null - ? Container( - margin: EdgeInsets.only(top: 5.0), - alignment: Alignment.centerLeft, - child: Text( - _errors['alternate_email'][0], - style: TextStyle( - color: Colors.red[700], - fontSize: 12.0), - ), - ) - : Container(), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Role", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownButtonFormField( - decoration: InputDecoration( - border: boxBorder(), - contentPadding: EdgeInsets.all(12.0)), - style: TextStyle(color: Colors.black), - hint: Text('select Role'), - value: userBloc.currentEditUser['role'], - onChanged: (value) { - userBloc.currentEditUser['role'] = value; - }, - items: userBloc.rolesObjForDropdown.map((item) { - return DropdownMenuItem( - child: new Text(item), - value: item, - ); - }).toList(), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Status", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - margin: EdgeInsets.only(bottom: 5.0), - child: DropdownButtonFormField( - decoration: InputDecoration( - border: boxBorder(), - contentPadding: EdgeInsets.all(12.0)), - style: TextStyle(color: Colors.black), - hint: Text('select Status'), - value: userBloc.currentEditUser['status'], - onChanged: (value) { - userBloc.currentEditUser['status'] = value; - }, - items: - userBloc.statusObjForDropdown.map((item) { - return DropdownMenuItem( - child: new Text(item), - value: item, - ); - }).toList(), - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Attachment", - style: buildLableTextStyle(), - ), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - controller: fileNameController, - readOnly: true, - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 5.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: - buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - suffixIcon: IconButton( - onPressed: _filePicker, - icon: Icon(Icons.upload))), - ), - ), - ])), - ]))))); - } - - Widget buildAddressBlock() { - return SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Form( - key: _addressFormKey, - child: Container( - child: Column(children: [ - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Billing Address Line ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: - userBloc.currentEditUser['address_line '], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['address_line '] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Billing Street ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: userBloc.currentEditUser['street'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['street'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'City ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: userBloc.currentEditUser['city'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['city'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'State ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: userBloc.currentEditUser['state'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['state'] = value; - }, - ), - ), - ])), - Container( - padding: padding(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 5.0), - child: RichText( - text: TextSpan( - text: 'Pincode ', - style: buildLableTextStyle(), - children: [ - TextSpan( - text: '* ', - style: TextStyle( - color: Colors.red, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500)) - ], - ), - )), - SizedBox(height: screenHeight / 70), - Container( - width: screenWidth * 0.92, - child: TextFormField( - initialValue: userBloc.currentEditUser['pincode'], - decoration: new InputDecoration( - contentPadding: new EdgeInsets.symmetric( - vertical: 15.0, horizontal: 10.0), - enabledBorder: buildBorder(Colors.black54), - focusedErrorBorder: buildBorder(Colors.black54), - focusedBorder: buildBorder(Colors.black54), - errorBorder: buildBorder(Colors.black54), - border: buildBorder(Colors.black54), - ), - keyboardType: TextInputType.text, - validator: (value) { - if (value!.isEmpty) { - return 'This field is required.'; - } - return null; - }, - onSaved: (value) { - userBloc.currentEditUser['pincode'] = value; - }, - ), - ), - ])), - // Container( - // padding: padding(), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Container( - // alignment: Alignment.centerLeft, - // margin: EdgeInsets.only(bottom: 5.0), - // child: RichText( - // text: TextSpan( - // text: 'Country ', - // style: buildLableTextStyle(), - // children: [ - // TextSpan( - // text: '* ', - // style: TextStyle( - // color: Colors.red, - // fontSize: screenWidth / 25, - // fontWeight: FontWeight.w500)) - // ], - // ), - // )), - // SizedBox(height: screenHeight / 70), - // Container( - // width: screenWidth * 0.92, - // child: DropdownSearch( - // mode: Mode.BOTTOM_SHEET, - // items: leadBloc.countries, - // onChanged: print, - // selectedItem: userBloc.currentEditUser['country'], - // showSearchBox: true, - // showSelectedItems: false, - // showClearButton: false, - // searchFieldProps: TextFieldProps( - // decoration: InputDecoration( - // border: boxBorder(), - // enabledBorder: boxBorder(), - // focusedErrorBorder: boxBorder(), - // focusedBorder: boxBorder(), - // errorBorder: boxBorder(), - // contentPadding: EdgeInsets.all(12), - // hintText: "Search a Country", - // )), - // popupTitle: Container( - // decoration: BoxDecoration( - // color: Theme.of(context).primaryColorDark, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Center( - // child: Text( - // 'County', - // style: TextStyle( - // fontSize: screenWidth / 20, - // color: Colors.white), - // ), - // ), - // ), - // popupItemBuilder: (context, item, isSelected) { - // return Container( - // padding: EdgeInsets.symmetric( - // horizontal: 15.0, vertical: 10.0), - // child: Text(item!, - // style: TextStyle( - // fontSize: screenWidth / 22)), - // ); - // }, - // popupShape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(24), - // topRight: Radius.circular(24), - // ), - // ), - // validator: (value) { - // if (value == null || value.isEmpty) { - // return 'This field is required.'; - // } - // return null; - // }, - // onSaved: (newValue) { - // userBloc.currentEditUser['country'] = newValue; - // }, - // )), - // ])) - ])))); - } - - Widget buildDescriptionBlock() { - return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: SizedBox() - // Column( - // children: [ - // quill.QuillToolbar.basic( - // controller: _controller, - // showAlignmentButtons: true, - // showBackgroundColorButton: false, - // showCameraButton: false, - // showImageButton: false, - // showVideoButton: false, - // showDividers: false, - // showColorButton: false, - // showUndo: false, - // showRedo: false, - // showQuote: false, - // showClearFormat: false, - // showIndent: false, - // showLink: false, - // showCodeBlock: false, - // showInlineCode: false, - // showListCheck: false, - // ), - // Expanded( - // child: Container( - // child: quill.QuillEditor.basic( - // controller: _controller, - // readOnly: true, - // ), - // ), - // ) - // ], - // ) - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Container( - decoration: BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - size: screenWidth / 18, color: Colors.white), - onTap: () { - // userBloc.cancelCurrentEditUser(); - // userBloc.currentEditUserId = ""; - // FocusScope.of(context).unfocus(); - // Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Add User', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () { - // if (_userFormKey.currentState != null) - // _userFormKey.currentState!.save(); - // if (_addressFormKey.currentState != null) - // _addressFormKey.currentState!.save(); - // FocusScope.of(context).unfocus(); - // if (!_isLoading) _submitForm(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon(Icons.check, - color: Theme.of(context).primaryColor, - size: screenWidth / 18), - Container( - child: Text( - "Save", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'User', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context).secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_userFormKey.currentState != null) - _userFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.20, - child: Text( - 'Address', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context).secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - if (_userFormKey.currentState != null) - _userFormKey.currentState!.save(); - if (_addressFormKey.currentState != null) - _addressFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.25, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context).secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - Expanded( - child: Stack( - fit: StackFit.expand, - children: [ - Container( - color: Colors.white, - child: buildTopBar(), - ), - new Align( - child: _isLoading - ? Container( - color: Colors.white, - width: screenWidth, - height: screenHeight * 0.9, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center( - child: new CircularProgressIndicator())), - ) - : Container(), - alignment: FractionalOffset.center, - ) - ], - )) - ], - ), - ), - ), - ); - } - - _submitForm() async { - setState(() { - _errors = {}; - _isLoading = true; - }); - _currentTabIndex = 0; - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_userFormKey.currentState != null) { - if (!_userFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _userFormKey.currentState!.save(); - setState(() { - _currentTabIndex = 1; - }); - await Future.delayed(const Duration(seconds: 1), () async {}); - if (_addressFormKey.currentState != null) { - if (!_addressFormKey.currentState!.validate()) { - setState(() { - _isLoading = false; - }); - showToaster('โš  Please enter required fields.', context); - return; - } - _addressFormKey.currentState!.save(); - Map _result = {}; - if (userBloc.currentEditUserId != null && - userBloc.currentEditUserId != "") { - _result = await userBloc.editUser(); - } else { - _result = await userBloc.createUser(file: file); - } - setState(() { - _isLoading = false; - }); - if (_result['error'] == false) { - setState(() { - _errors = {}; - }); - userBloc.cancelCurrentEditUser()(); - userBloc.currentEditUserId = ""; - showToaster(_result['message'], context); - Navigator.pushReplacementNamed(context, '/users_list'); - } else if (_result['error'] == true) { - setState(() { - _errors = _result['errors']; - }); - for (var key in _userFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 0; - }); - showToaster(_errors[key][0], context); - return; - } - } - for (var key in _addressFormKeys) { - if (_errors.containsKey(key)) { - setState(() { - _currentTabIndex = 1; - }); - showToaster(_errors[key][0], context); - return; - } - } - } else { - setState(() { - _errors = {}; - }); - showErrorMessage(context, _result['message'].toString()); - } - } - } - } - - showErrorMessage(BuildContext context, msg) { - return showDialog( - context: context, - builder: (_) => AlertDialog( - title: Text('Alert'), - content: Text(msg), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - _submitForm(); - }, - child: Text('RETRY')) - ], - )); - } -} diff --git a/lib/ui/screens/users/user_details.dart b/lib/ui/screens/users/user_details.dart deleted file mode 100644 index 2f04a4f..0000000 --- a/lib/ui/screens/users/user_details.dart +++ /dev/null @@ -1,581 +0,0 @@ -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:bottle_crm/ui/widgets/loader.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class UserDetails extends StatefulWidget { - UserDetails(); - @override - State createState() => _UserDetailsState(); -} - -class _UserDetailsState extends State { - var _currentTabIndex = 0; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - Widget loadingIndicator = _isLoading ? Loader() : new Container(); - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row(children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'User Details', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ]), - ), - GestureDetector( - onTap: () async { - if (!_isLoading) { - userBloc.currentEditUserId = - userBloc.currentUser!.id.toString(); - await userBloc - .updateCurrentEditUser(userBloc.currentUser!); - Navigator.pushNamed(context, '/user_create'); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(3.0)), - color: Colors.white, - ), - width: screenWidth * 0.18, - height: screenHeight * 0.04, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SvgPicture.asset( - 'assets/images/Icon_edit_color.svg', - colorFilter: ColorFilter.mode(Theme.of(context).primaryColor, BlendMode.srcIn), - width: screenWidth / 25, - ), - Container( - child: Text( - "Edit", - style: TextStyle( - fontSize: screenWidth / 25, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - ) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 23.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 0; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.40, - child: Text( - 'User Information', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 1; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.22, - child: Text( - 'Attachment', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - setState(() { - _currentTabIndex = 2; - }); - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 2 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.21, - child: Text( - 'Description', - style: TextStyle( - color: _currentTabIndex == 2 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - ], - ), - ), - ], - ), - ), - Expanded( - child: Container( - child: buildTopBar(), - color: Colors.white, - )) - ], - ), - ), - ), - new Align( - child: loadingIndicator, - alignment: FractionalOffset.center, - ) - ], - ), - ); - } - - buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildUserInfoBlock()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 2; - }); - }, - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: buildAttachmentBlock()); - } else if (_currentTabIndex == 2) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 1; - }); - }, - child: buildDescriptionBlock()); - } - } - - buildAttachmentBlock() { - return Container( - alignment: Alignment.center, - color: Colors.white, - height: screenHeight, - width: screenWidth, - child: Text("No Attachments Found."), - ); - } - - buildDescriptionBlock() { - return Container( - alignment: Alignment.center, - color: Colors.white, - height: screenHeight, - width: screenWidth, - child: Text("No Notes Found."), - ); - } - - buildUserInfoBlock() { - return Container( - padding: EdgeInsets.all(10.0), - child: SingleChildScrollView( - child: Column(children: [ - Container( - width: screenWidth, - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "First Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.firstName! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Last Name :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.lastName == "" - ? "----" - : userBloc.currentUser!.lastName! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - SizedBox(height: 10.0), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Role :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.role!, - //.capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - ], - )), - ], - ), - ], - ), - ), - SizedBox(height: 10.0), - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Organization :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authBloc.selectedOrganization!.name! - .capitalizeFirstofEach(), - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - SizedBox(height: 5.0), - Text( - "https//:www.micropyramid.com", - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 25), - ), - ], - )), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Contact :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10.0), - ], - ), - SizedBox(height: 10), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: - Icon(Icons.email_outlined, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.email!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Icon(Icons.phone, size: screenWidth / 20)), - SizedBox(width: 10.0), - Container( - width: screenWidth * 0.50, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userBloc.currentUser!.phone!, - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - ), - Container( - padding: EdgeInsets.symmetric(vertical: 5.0), - child: Divider()) - ], - )), - ], - ), - Row( - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Image.asset( - "assets/images/skype.png", - width: screenWidth / 22, - )), - // SizedBox(width: 10.0), - // Container( - // width: screenWidth * 0.50, - // child: Text( - // " skype@Id", - // style: TextStyle( - // color: Colors.blue, - // fontWeight: FontWeight.w600, - // fontSize: screenWidth / 24), - // )), - ], - ), - ], - ), - ), - SizedBox(height: 10), - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.black12), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column(children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerRight, - width: screenWidth * 0.28, - child: Text( - "Address :", - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - SizedBox(width: 10), - Container( - width: screenWidth * 0.50, - child: Text( - "${userBloc.currentUser!.adressLine} ${userBloc.currentUser!.street!}, ${userBloc.currentUser!.city!}, ${userBloc.currentUser!.state!}, ${userBloc.currentUser!.country!}, ${userBloc.currentUser!.pincode!}.", - style: TextStyle( - color: Colors.black45, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24), - )), - ], - ), - ])), - SizedBox(height: 10.0), - ]))); - } -} diff --git a/lib/ui/screens/users/users_list.dart b/lib/ui/screens/users/users_list.dart deleted file mode 100644 index ef8aba8..0000000 --- a/lib/ui/screens/users/users_list.dart +++ /dev/null @@ -1,427 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:bottle_crm/model/user.dart'; -import 'package:bottle_crm/ui/widgets/bottom_navigation_bar.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -class UsersList extends StatefulWidget { - UsersList(); - @override - State createState() => _UsersListState(); -} - -class _UsersListState extends State { - var _currentTabIndex = 0; - ScrollController? scrollController; - - bool _isFilter = false; - - List _activeUsers = []; - List _inActiveUsers = []; - @override - void initState() { - _activeUsers = userBloc.activeUsers; - _inActiveUsers = userBloc.inActiveUsers; - super.initState(); - } - - _buildTopBar() { - if (_currentTabIndex == 0) { - return SwipeDetector( - onSwipeLeft: (offset) { - setState(() { - _currentTabIndex = 1; - _inActiveUsers = userBloc.inActiveUsers; - }); - }, - child: _buildActiveUsersList()); - } else if (_currentTabIndex == 1) { - return SwipeDetector( - onSwipeRight: (offset) { - setState(() { - _currentTabIndex = 0; - }); - }, - child: _buildInActiveUsersList()); - } - } - - Widget _buildActiveUsersList() { - return _activeUsers.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _activeUsers.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - userBloc.currentUser = _activeUsers[index]; - Navigator.pushNamed(context, '/user_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - children: [ - _activeUsers[index].profilePic != "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage( - _activeUsers[index].profilePic!), - ) - : CircleAvatar( - radius: screenWidth / 25, - backgroundColor: - Theme.of(context).primaryColor, - child: Text( - _activeUsers[index] - .firstName![0] - .allInCaps, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), - ), - SizedBox(width: 10.0), - Column( - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _activeUsers[index] - .firstName! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Container( - width: screenWidth * 0.55, - child: Text(_activeUsers[index].role!, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.grey, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 30)), - ), - ], - ), - ], - ), - ), - SizedBox(height: 5.0), - ], - ), - )); - }), - ) - : Container( - alignment: Alignment.center, - color: Colors.white, - height: screenHeight, - width: screenWidth, - child: Text('No Users Found.'), - ); - } - - Widget _buildInActiveUsersList() { - return _inActiveUsers.length != 0 - ? Container( - padding: EdgeInsets.all(10.0), - child: ListView.builder( - itemCount: _inActiveUsers.length, - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: scrollController, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - userBloc.currentUser = _inActiveUsers[index]; - Navigator.pushNamed(context, '/user_details'); - }, - child: Container( - margin: EdgeInsets.only(bottom: 8.0), - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - children: [ - _inActiveUsers[index].profilePic != "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage( - _inActiveUsers[index] - .profilePic!), - ) - : CircleAvatar( - radius: screenWidth / 25, - backgroundColor: - Theme.of(context).primaryColor, - child: Text( - _inActiveUsers[index] - .firstName![0] - .allInCaps, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), - ), - SizedBox(width: 10.0), - Column( - children: [ - Container( - width: screenWidth * 0.55, - child: Text( - _inActiveUsers[index] - .firstName! - .capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w600, - fontSize: screenWidth / 24)), - ), - Container( - width: screenWidth * 0.55, - child: Text(_inActiveUsers[index].role!, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.grey, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 30)), - ), - ], - ), - ], - ), - ), - SizedBox(height: 5.0), - ], - ), - )); - }), - ) - : Container( - alignment: Alignment.center, - color: Colors.white, - height: screenHeight, - width: screenWidth, - child: Text('No Users Found.')); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack(fit: StackFit.expand, children: [ - SafeArea( - child: Container( - decoration: - BoxDecoration(color: Color.fromRGBO(73, 128, 255, 1.0)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: - EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Row( - children: [ - GestureDetector( - child: new Icon(Icons.arrow_back_ios, - color: Colors.white), - onTap: () { - Navigator.pop(context, true); - }), - SizedBox(width: 10.0), - Text( - 'Users', - style: TextStyle( - color: Colors.white, - fontSize: screenWidth / 20, - fontWeight: FontWeight.bold), - ), - ], - ), - ), - GestureDetector( - onTap: () { - setState(() { - _isFilter = !_isFilter; - }); - }, - child: Container( - child: SvgPicture.asset( - !_isFilter - ? 'assets/images/filter.svg' - : 'assets/images/icon_close.svg', - width: screenWidth / 20, - ))) - ], - ), - ), - Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 25.0), - height: screenHeight * 0.06, - decoration: BoxDecoration( - color: bottomNavBarSelectedTextColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - if (_currentTabIndex != 0) { - setState(() { - _currentTabIndex = 0; - _activeUsers = userBloc.activeUsers; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 0 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Open', - style: TextStyle( - color: _currentTabIndex == 0 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ), - GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - if (_currentTabIndex != 1) { - setState(() { - _currentTabIndex = 1; - _inActiveUsers = userBloc.inActiveUsers; - }); - } - }, - child: Container( - alignment: Alignment.center, - height: screenHeight * 0.06, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white, - width: _currentTabIndex == 1 ? 3.0 : 0.0, - ))), - width: screenWidth * 0.15, - child: Text( - 'Closed', - style: TextStyle( - color: _currentTabIndex == 1 - ? Colors.white - : Theme.of(context) - .secondaryHeaderColor, - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500), - ), - ), - ) - ], - ), - ), - ], - ), - ), - Expanded( - child: Container( - color: Colors.white, - child: _buildTopBar(), - ), - ), - ], - ), - ), - ), - ]), - // floatingActionButton: FloatingActionButton( - // onPressed: () { - // if (userBloc.activeUsers.length == 0) { - // } else { - // Navigator.pushNamed(context, '/user_create'); - // } - // }, - // child: Icon(Icons.add, color: Colors.white), - // backgroundColor: Theme.of(context).primaryColor, - // ), - bottomNavigationBar: BottomNavigationBarWidget()); - } - - void showAlertDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text( - "Alert", - style: TextStyle(color: Colors.black), - ), - content: Text( - "You don't have any users, Please create user first.", - style: TextStyle(fontSize: 15.0), - ), - actions: [ - CupertinoDialogAction( - textStyle: TextStyle(color: Colors.red), - isDefaultAction: true, - onPressed: () async { - Navigator.pop(context); - }, - child: Text("Cancel")), - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () { - currentBottomNavigationIndex = "4"; - Navigator.pop(context); - Navigator.pushNamed(context, "/user_create"); - }, - child: Text("Create")), - ], - ); - }); - } -} diff --git a/lib/ui/widgets/bottom_navigation_bar.dart b/lib/ui/widgets/bottom_navigation_bar.dart deleted file mode 100644 index cf8ad1e..0000000 --- a/lib/ui/widgets/bottom_navigation_bar.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'dart:core'; - -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -class BottomNavigationBarWidget extends StatefulWidget { - @override - State createState() => _BottomNavigationBarWidgetState(); -} - -class _BottomNavigationBarWidgetState extends State { - String _currentBottomBarIndex = "0"; - @override - void initState() { - setState(() { - _currentBottomBarIndex = currentBottomNavigationIndex; - }); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Container( - height: screenHeight * 0.08, - //color: Colors.white, - child: Row( - children: [ - GestureDetector( - onTap: () { - if (_currentBottomBarIndex != '0') { - Navigator.pushNamedAndRemoveUntil( - context, '/dashboard', (route) => false); - setState(() { - _currentBottomBarIndex = "0"; - }); - currentBottomNavigationIndex = _currentBottomBarIndex; - } - }, - child: Container( - decoration: BoxDecoration( - color: _currentBottomBarIndex == "0" - ? bottomNavBarSelectedBGColor - : Colors.white, - borderRadius: - BorderRadius.only(topLeft: Radius.circular(30))), - width: screenWidth * 0.20, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: SvgPicture.asset( - 'assets/images/home.svg', - width: screenWidth / 15, - colorFilter: ColorFilter.mode( - _currentBottomBarIndex == "0" - ? bottomNavBarSelectedTextColor - : bottomNavBarTextColor, - BlendMode.srcIn, - ), - ), - ), - ], - ), - ), - ), - GestureDetector( - onTap: () { - if (_currentBottomBarIndex != '1') { - Navigator.pushNamedAndRemoveUntil( - context, '/accounts_list', (route) => false); - setState(() { - _currentBottomBarIndex = "1"; - }); - currentBottomNavigationIndex = _currentBottomBarIndex; - } - }, - child: Container( - color: _currentBottomBarIndex == "1" - ? bottomNavBarSelectedBGColor - : Colors.white, - width: screenWidth * 0.20, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: SvgPicture.asset( - 'assets/images/accounts.svg', - width: screenWidth / 12, - colorFilter: ColorFilter.mode( - _currentBottomBarIndex == "1" - ? bottomNavBarSelectedTextColor - : bottomNavBarTextColor, - BlendMode.srcIn, - ), - ), - ), - ], - ), - ), - ), - GestureDetector( - onTap: () { - // if (_currentBottomBarIndex != '') { - // setState(() { - // _currentBottomBarIndex = ""; - // }); - // currentBottomNavigationIndex = _currentBottomBarIndex; - // } - }, - child: Container( - color: _currentBottomBarIndex == "" - ? bottomNavBarSelectedBGColor - : Colors.white, - width: screenWidth * 0.20, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: SvgPicture.asset( - 'assets/images/search.svg', - width: screenWidth / 16, - colorFilter: ColorFilter.mode( - _currentBottomBarIndex == "" - ? bottomNavBarSelectedTextColor - : bottomNavBarTextColor, - BlendMode.srcIn, - ), - ), - ), - ], - ), - ), - ), - GestureDetector( - onTap: () { - if (_currentBottomBarIndex != '2') { - Navigator.pushNamedAndRemoveUntil( - context, '/tasks_list', (route) => false); - setState(() { - _currentBottomBarIndex = "2"; - }); - currentBottomNavigationIndex = _currentBottomBarIndex; - } - }, - child: Container( - color: _currentBottomBarIndex == "2" - ? bottomNavBarSelectedBGColor - : Colors.white, - width: screenWidth * 0.20, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: SvgPicture.asset( - 'assets/images/tasks.svg', - width: screenWidth / 18, - colorFilter: ColorFilter.mode( - _currentBottomBarIndex == "2" - ? bottomNavBarSelectedTextColor - : bottomNavBarTextColor, - BlendMode.srcIn, - ), - ), - ), - ], - ), - ), - ), - GestureDetector( - onTap: () { - if (_currentBottomBarIndex != '3') { - Navigator.pushNamedAndRemoveUntil( - context, '/more_options', (route) => false); - setState(() { - _currentBottomBarIndex = "3"; - }); - currentBottomNavigationIndex = _currentBottomBarIndex; - } - }, - child: Container( - decoration: BoxDecoration( - color: _currentBottomBarIndex == "3" - ? bottomNavBarSelectedBGColor - : Colors.white, - borderRadius: - BorderRadius.only(topRight: Radius.circular(30))), - width: screenWidth * 0.20, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: SvgPicture.asset( - 'assets/images/more.svg', - width: screenWidth / 16, - colorFilter: ColorFilter.mode( - _currentBottomBarIndex == "3" - ? bottomNavBarSelectedTextColor - : bottomNavBarTextColor, - BlendMode.srcIn, - ), - ), - ), - ], - ), - ), - ) - ], - ), - ); - } -} diff --git a/lib/ui/widgets/dashboard_count_card.dart b/lib/ui/widgets/dashboard_count_card.dart deleted file mode 100644 index ad42114..0000000 --- a/lib/ui/widgets/dashboard_count_card.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; -import 'package:flutter_svg/svg.dart'; - -class CountCard extends StatelessWidget { - final String? icon; - final Color? color; - final String? lable; - final int? count; - final String? routeName; - final int? index; - CountCard( - {this.icon, - this.color, - this.lable, - this.count, - this.routeName, - this.index}); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - currentBottomNavigationIndex = index.toString(); - Navigator.pushNamed(context, routeName!); - }, - child: Container( - width: screenWidth * 0.46, - height: screenHeight * 0.12, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.all(Radius.circular(10.0)), - border: Border.all(color: Colors.grey.shade300, width: 2.0)), - padding: EdgeInsets.all(10.0), - child: Row( - children: [ - CircleAvatar( - radius: screenWidth / 14, - child: SvgPicture.asset( - icon!, - colorFilter: ColorFilter.mode(Colors.blueGrey[800]!, BlendMode.srcIn), - width: screenWidth / 12, - ), - backgroundColor: Colors.white, - ), - Container( - margin: EdgeInsets.only(left: 10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.only(bottom: 5.0), - child: Text(lable!, - style: TextStyle( - color: Colors.blueGrey[800], - fontWeight: FontWeight.w500, - fontSize: lable == "Opportunities" - ? screenWidth / 30 - : screenWidth / 25)), - ), - Container( - child: Text( - count!.toString(), - style: TextStyle( - color: Colors.blueGrey[800], - fontSize: screenWidth / 15, - fontWeight: FontWeight.bold), - ), - ) - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/ui/widgets/loader.dart b/lib/ui/widgets/loader.dart deleted file mode 100644 index 8c01558..0000000 --- a/lib/ui/widgets/loader.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; - -class Loader extends StatelessWidget { - Loader(); - - @override - Widget build(BuildContext context) { - return Container( - color: Colors.transparent, - width: 300.0, - height: 300.0, - child: new Padding( - padding: const EdgeInsets.all(5.0), - child: new Center(child: new CircularProgressIndicator())), - ); - } -} diff --git a/lib/ui/widgets/profile_pic_widget.dart b/lib/ui/widgets/profile_pic_widget.dart deleted file mode 100644 index 6e20d0f..0000000 --- a/lib/ui/widgets/profile_pic_widget.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class ProfilePicViewWidget extends StatelessWidget { - final List profilePicsList; - ProfilePicViewWidget(this.profilePicsList); - - List buildProfile() { - List tagWidgets = []; - for (int i = 0; i < profilePicsList.length; i++) { - tagWidgets.add(createProfile(i)); - } - return tagWidgets; - } - - Widget createProfile(profilePicIndex) { - return Container( - margin: EdgeInsets.only(bottom: 5.0), - padding: EdgeInsets.symmetric(horizontal: 1.0, vertical: 2.0), - child: profilePicsList[profilePicIndex] != null && - profilePicsList[profilePicIndex] != "" && - Uri.parse(profilePicsList[profilePicIndex]).isAbsolute - ? CircleAvatar( - radius: screenWidth / 20, - backgroundImage: NetworkImage(profilePicsList[profilePicIndex]), - ) - : CircleAvatar( - radius: screenWidth / 20, - child: Text(profilePicsList[profilePicIndex], - style: TextStyle( - color: Colors.white, fontWeight: FontWeight.bold)), - backgroundColor: randomColor.randomColor( - colorBrightness: ColorBrightness.dark), - ), - ); - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Wrap( - alignment: WrapAlignment.start, - spacing: 5.0, - direction: Axis.horizontal, - children: buildProfile(), - ), - ); - } -} diff --git a/lib/ui/widgets/recent_card_widget.dart b/lib/ui/widgets/recent_card_widget.dart deleted file mode 100644 index 78271c0..0000000 --- a/lib/ui/widgets/recent_card_widget.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:bottle_crm/ui/widgets/tags_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class RecentCardWidget extends StatelessWidget { - final String? source; - final String? name; - final String? date; - final String? city; - final String? email; - final String? photoUrl; - final String? createdBy; - final List? tags; - - RecentCardWidget( - {this.source, - this.name, - this.date, - this.city, - this.email, - this.photoUrl, - this.createdBy, - this.tags}); - - @override - Widget build(BuildContext context) { - return Container( - // height: screenHeight * 0.09, - //color: Colors.white, - padding: EdgeInsets.all(10.0), - margin: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), - decoration: BoxDecoration( - border: Border.all(width: 1.0, color: Colors.grey), - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: screenWidth * 0.5, - child: Text(name!.capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.black87, - fontWeight: FontWeight.w600, - fontSize: screenWidth / 21))), - Container( - width: screenWidth * 0.3, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - photoUrl != "" - ? CircleAvatar( - radius: screenWidth / 28, - backgroundImage: NetworkImage(photoUrl!), - ) - : CircleAvatar( - radius: screenWidth / 30, - backgroundColor: Theme.of(context).primaryColor, - child: Text( - createdBy![0].allInCaps, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), - ), - SizedBox(width: 5.0), - Text(createdBy!.capitalizeFirstofEach(), - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Colors.blueGrey[800], - fontWeight: FontWeight.w500, - fontSize: screenWidth / 25)) - ], - ), - ) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container(width: screenWidth * 0.5, child: TagViewWidget(tags!)), - Text(date!, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: bottomNavBarTextColor, - fontSize: screenWidth / 26)), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/tags_widget.dart b/lib/ui/widgets/tags_widget.dart deleted file mode 100644 index b8efd9e..0000000 --- a/lib/ui/widgets/tags_widget.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:bottle_crm/utils/utils.dart'; - -class TagViewWidget extends StatelessWidget { - final List tags; - TagViewWidget(this.tags); - - List buildTags() { - List tagWidgets = []; - for (int i = 0; i < tags.length; i++) { - tagWidgets.add(createTag(i)); - } - return tagWidgets; - } - - Widget createTag(tagIndex) { - return Container( - margin: EdgeInsets.only(bottom: 5.0), - padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 3.0), - color: randomColor.randomColor(colorBrightness: ColorBrightness.light), - child: Text( - tags[tagIndex]['name'], - style: TextStyle(color: Colors.white, fontSize: 12.0), - ), - ); - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Wrap( - alignment: WrapAlignment.start, - spacing: 5.0, - direction: Axis.horizontal, - children: buildTags(), - ), - ); - } -} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart deleted file mode 100644 index b12f04b..0000000 --- a/lib/utils/utils.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'dart:io'; -import 'dart:math'; - -import 'package:bottle_crm/bloc/account_bloc.dart'; -import 'package:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/bloc/case_bloc.dart'; -import 'package:bottle_crm/bloc/contact_bloc.dart'; -import 'package:bottle_crm/bloc/dashboard_bloc.dart'; -import 'package:bottle_crm/bloc/event_bloc.dart'; -import 'package:bottle_crm/bloc/lead_bloc.dart'; -import 'package:bottle_crm/bloc/opportunity_bloc.dart'; -import 'package:bottle_crm/bloc/setting_bloc.dart'; -import 'package:bottle_crm/bloc/task_bloc.dart'; -import 'package:bottle_crm/bloc/team_bloc.dart'; -import 'package:bottle_crm/bloc/user_bloc.dart'; -import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:flutter_styled_toast/flutter_styled_toast.dart'; - -var screenWidth; -var screenHeight; -// Color submitButtonColor = Color.fromRGBO(75, 153, 90, 1); -// Color bottomNavBarSelectedBGColor = Color.fromRGBO(219, 232, 249, 1); -// Color bottomNavBarSelectedTextColor = Color.fromRGBO(15, 36, 62, 1); -// Color bottomNavBarTextColor = Color.fromRGBO(75, 75, 78, 1); -String currentBottomNavigationIndex = "0"; -String currentTopBarModuleName = "Dashboard"; -Color bottomNavBarTextColor = Color.fromRGBO(75, 75, 78, 1); -Color submitButtonColor = Color.fromRGBO(75, 153, 90, 1); -Color bottomNavBarSelectedBGColor = Color.fromRGBO(219, 232, 249, 1); -Color bottomNavBarSelectedTextColor = Color.fromRGBO(15, 36, 62, 1); - -// Simple random color generator to replace RandomColor package -class SimpleRandomColor { - static final Random _random = Random(); - - Color randomColor({ColorBrightness? colorBrightness}) { - if (colorBrightness == ColorBrightness.light) { - // Generate lighter colors - return Color.fromRGBO( - _random.nextInt(128) + 128, // 128-255 - _random.nextInt(128) + 128, // 128-255 - _random.nextInt(128) + 128, // 128-255 - 1.0, - ); - } else { - // Generate any random color - return Color.fromRGBO( - _random.nextInt(256), - _random.nextInt(256), - _random.nextInt(256), - 1.0, - ); - } - } -} - -enum ColorBrightness { light, dark } - -SimpleRandomColor randomColor = SimpleRandomColor(); - -fetchRequiredData() async { - print("fetching data โ– โ–‚ โ–ƒ โ–„ โ–… โ–†"); - await authBloc.getProfileDetails(); - await dashboardBloc.fetchDashboardDetails(); - await leadBloc.fetchLeads(); - await accountBloc.fetchAccounts(); - await contactBloc.fetchContacts(); - await opportunityBloc.fetchOpportunities(); - await caseBloc.fetchCases(); - await userBloc.fetchUsers(); - await taskBloc.fetchTasks(); - await teamBloc.fetchTeams(); - await eventBloc.fetchEvents(); - await settingsBloc.fetchApiSettings(); - print("โœ“ data fetched successfully"); -} - -OutlineInputBorder boxBorder() { - return OutlineInputBorder( - // borderRadius: BorderRadius.all(Radius.circular(15)), - borderSide: BorderSide(width: 0, color: Colors.white), - ); -} - -extension CapExtension on String { - String get inCaps => '${this[0].toUpperCase()}${this.substring(1)}'; - String get allInCaps => this.toUpperCase(); - String capitalizeFirstofEach() => - this.split(" ").map((str) => str.inCaps).join(" "); -} - -DateTime? currentBackPressTime; -Future onWillPop() { - DateTime now = DateTime.now(); - if (currentBackPressTime == null || - now.difference(currentBackPressTime!) > Duration(seconds: 2)) { - currentBackPressTime = now; - Fluttertoast.showToast(msg: 'Press again to close Bottle CRM'); - return Future.value(false); - } - exit(0); -} - -// showToast(message) { -// Fluttertoast.showToast( -// msg: message, -// toastLength: Toast.LENGTH_LONG, -// gravity: ToastGravity.CENTER, -// timeInSecForIosWeb: 1, -// backgroundColor: Colors.blue, -// textColor: Colors.white, -// fontSize: screenWidth / 26, -// ); -// } - -showToaster(message, context) { - showToast( - message, - context: context, - animation: StyledToastAnimation.scale, - reverseAnimation: StyledToastAnimation.fade, - position: StyledToastPosition.bottom, - animDuration: Duration(seconds: 1), - duration: Duration(seconds: 4), - curve: Curves.elasticOut, - reverseCurve: Curves.linear, - ); -} - -TextStyle buildLableTextStyle() { - return TextStyle( - fontSize: screenWidth / 25, - fontWeight: FontWeight.w500, - color: Colors.blueGrey); -} diff --git a/lib/utils/validations.dart b/lib/utils/validations.dart deleted file mode 100644 index 1a528f9..0000000 --- a/lib/utils/validations.dart +++ /dev/null @@ -1,18 +0,0 @@ -class FieldValidators { - static String? emailFieldValidation(String value) { - if (value.isEmpty) return "Please Enter Email "; - - RegExp regExp = new RegExp( - r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"); - - if (value.isNotEmpty && !regExp.hasMatch(value)) { - return "Please Enter Valid Email "; - } - return null; - } - - static String? passwordValidation(String value) { - if (value.isEmpty) return "Please Enter Password"; - return null; - } -} diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..32e0f2c --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "bottlecrm") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "io.bottlecrm") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..b0235cd --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "bottlecrm"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "bottlecrm"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..22724f6 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import google_sign_in_ios +import package_info_plus +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..edaec34 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* bottlecrm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "bottlecrm.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* bottlecrm.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* bottlecrm.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bottlecrm.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bottlecrm"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bottlecrm.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bottlecrm"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bottlecrm.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bottlecrm"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..3a881c0 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..56a5245 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = bottlecrm + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = io.bottlecrm + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright ยฉ 2025 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..65d84a5 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,562 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a + url: "https://pub.dev" + source: hosted + version: "10.8.0" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: "939a8b58f84c4053811b8c1bc9adbcb59449a15b37958264bbf60020698cca0e" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: f256b8f0e6c09d135c166fe20b25256e24d60fe1a72e6bdc112a200bd0d555b4 + url: "https://pub.dev" + source: hosted + version: "7.0.3" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: c7ee744ebbcd98353966dbdee735d4fca085226f6bf725c6bea8a5c8fe0055bc + url: "https://pub.dev" + source: hosted + version: "6.1.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "8736443134d2cccadd4f228d600177cb3947e36683466a6ab96877ce6932885a" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "09ac306b2787b48f19c857b9f93375b654f774643c75bd6a1a078c85f4f7b468" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + jwt_decoder: + dependency: "direct main" + description: + name: jwt_decoder + sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + url: "https://pub.dev" + source: hosted + version: "5.14.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.8.1 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index ffae55f..931415b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,49 +1,53 @@ name: bottle_crm -description: A new Flutter project. +description: "CRM for everyone." +# 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 -publish_to: 'none' - -version: 1.0.16+16 +version: 1.0.0+9 environment: - sdk: '>=3.0.0 <4.0.0' + sdk: ^3.8.1 dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.8 - http: ^1.2.2 - file_picker: ^10.2.0 - connectivity_plus: ^6.1.0 - flutter_svg: ^2.0.16 - intl: ^0.20.2 - shared_preferences: ^2.3.3 - fluttertoast: ^8.2.8 - flutter_styled_toast: ^2.2.1 - dropdown_search: ^6.0.2 - textfield_tags: ^3.0.1 - multiselect_formfield: ^0.1.7 - flutter_quill: ^11.4.2 - firebase_analytics: ^12.0.0 - firebase_core: ^4.0.0 - flutter_swipe_detector: ^2.0.0 - - -flutter_icons: - android: "launcher_icon" - ios: true - image_path: "assets/images/new_logo.png" + http: ^1.1.0 + google_sign_in: ^7.1.1 + shared_preferences: ^2.2.2 + jwt_decoder: ^2.0.1 + font_awesome_flutter: ^10.7.0 + intl: ^0.19.0 + package_info_plus: ^8.0.2 dev_dependencies: flutter_test: sdk: flutter - flutter_launcher_icons: ^0.14.1 + # 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: ^6.0.0 + flutter_launcher_icons: "^0.14.4" + +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/icon/icon.png" + min_sdk_android: 31 # android min sdk Android 12 (API 31) + remove_alpha_ios: true # remove alpha channel from iOS icons, default false + +# The following section is specific to Flutter packages. flutter: uses-material-design: true + # To add assets to your application, add an assets section, like this: assets: + - assets/icon/ - assets/images/ diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 18a56a6..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. 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:bottle_crm/bloc/auth_bloc.dart'; -import 'package:bottle_crm/utils/validations.dart'; -import 'package:flutter_test/flutter_test.dart'; - -// import 'package:mobile2/main.dart'; - -void main() { - group("Login Page", () { - test("Email should Not be Empty", () async{ - Map result = await authBloc.login({"email": "", "password": ""}); - expect(true, result['error']); - }); - - test("Password should Not be Empty", () { - var result = FieldValidators.passwordValidation(""); - expect("Please Enter Password", result); - }); - }); - // 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); - // }); -} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..1f751ad --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + bottlecrm + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..51cf1a6 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "bottlecrm", + "short_name": "bottlecrm", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..dec64d9 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(bottlecrm LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "bottlecrm") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..7f090ce --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "bottlecrm" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "bottlecrm" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "bottlecrm.exe" "\0" + VALUE "ProductName", "bottlecrm" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..35b1721 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"bottlecrm", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_