diff --git a/.env b/.env new file mode 100644 index 00000000..e69de29b diff --git a/android/app/build.gradle b/android/app/build.gradle index a97f5335..7d9d82ac 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -47,7 +47,7 @@ android { applicationId "com.atsign.atsign_atmosphere_pro" multiDexEnabled true minSdkVersion 24 - targetSdkVersion 30 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2459d853..1412bebe 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ android:icon="@mipmap/ic_launcher"> + + + + + + + + diff --git a/assets/svg/contacts.svg b/assets/svg/contacts.svg new file mode 100644 index 00000000..25b1ba3c --- /dev/null +++ b/assets/svg/contacts.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/history.svg b/assets/svg/history.svg new file mode 100644 index 00000000..81d29e8f --- /dev/null +++ b/assets/svg/history.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/home.svg b/assets/svg/home.svg new file mode 100644 index 00000000..ed614a2b --- /dev/null +++ b/assets/svg/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_activate.svg b/assets/svg/ic_activate.svg new file mode 100644 index 00000000..fada1032 --- /dev/null +++ b/assets/svg/ic_activate.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_add.svg b/assets/svg/ic_add.svg new file mode 100644 index 00000000..73e8ce7f --- /dev/null +++ b/assets/svg/ic_add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_arrow.svg b/assets/svg/ic_arrow.svg new file mode 100644 index 00000000..dde75b5f --- /dev/null +++ b/assets/svg/ic_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_arrow_asc.svg b/assets/svg/ic_arrow_asc.svg new file mode 100644 index 00000000..f1d06ae9 --- /dev/null +++ b/assets/svg/ic_arrow_asc.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_arrow_desc.svg b/assets/svg/ic_arrow_desc.svg new file mode 100644 index 00000000..b3b146d4 --- /dev/null +++ b/assets/svg/ic_arrow_desc.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_arrow_down.svg b/assets/svg/ic_arrow_down.svg new file mode 100644 index 00000000..736ecb1d --- /dev/null +++ b/assets/svg/ic_arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_arrow_down_outline.svg b/assets/svg/ic_arrow_down_outline.svg new file mode 100644 index 00000000..8d22fc8d --- /dev/null +++ b/assets/svg/ic_arrow_down_outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_arrow_right.svg b/assets/svg/ic_arrow_right.svg new file mode 100644 index 00000000..ad3865cd --- /dev/null +++ b/assets/svg/ic_arrow_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_arrow_up_outline.svg b/assets/svg/ic_arrow_up_outline.svg new file mode 100644 index 00000000..5d56af49 --- /dev/null +++ b/assets/svg/ic_arrow_up_outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_audio.svg b/assets/svg/ic_audio.svg new file mode 100644 index 00000000..fe2856f0 --- /dev/null +++ b/assets/svg/ic_audio.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/ic_back.svg b/assets/svg/ic_back.svg new file mode 100644 index 00000000..7666374a --- /dev/null +++ b/assets/svg/ic_back.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_banner_overlay.svg b/assets/svg/ic_banner_overlay.svg new file mode 100644 index 00000000..b215c654 --- /dev/null +++ b/assets/svg/ic_banner_overlay.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_big_trust.svg b/assets/svg/ic_big_trust.svg new file mode 100644 index 00000000..6578e037 --- /dev/null +++ b/assets/svg/ic_big_trust.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_big_trust_activated.svg b/assets/svg/ic_big_trust_activated.svg new file mode 100644 index 00000000..14c15f08 --- /dev/null +++ b/assets/svg/ic_big_trust_activated.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_block.svg b/assets/svg/ic_block.svg new file mode 100644 index 00000000..8385597a --- /dev/null +++ b/assets/svg/ic_block.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_cancel.svg b/assets/svg/ic_cancel.svg new file mode 100644 index 00000000..b60eef94 --- /dev/null +++ b/assets/svg/ic_cancel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_category_files.svg b/assets/svg/ic_category_files.svg new file mode 100644 index 00000000..858dac32 --- /dev/null +++ b/assets/svg/ic_category_files.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_category_folder.svg b/assets/svg/ic_category_folder.svg new file mode 100644 index 00000000..4ca98506 --- /dev/null +++ b/assets/svg/ic_category_folder.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_category_image.svg b/assets/svg/ic_category_image.svg new file mode 100644 index 00000000..6bf33e72 --- /dev/null +++ b/assets/svg/ic_category_image.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_category_other.svg b/assets/svg/ic_category_other.svg new file mode 100644 index 00000000..ef1d7af4 --- /dev/null +++ b/assets/svg/ic_category_other.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/ic_category_play.svg b/assets/svg/ic_category_play.svg new file mode 100644 index 00000000..d6fdfc58 --- /dev/null +++ b/assets/svg/ic_category_play.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_category_volume.svg b/assets/svg/ic_category_volume.svg new file mode 100644 index 00000000..975afe3e --- /dev/null +++ b/assets/svg/ic_category_volume.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/ic_check.svg b/assets/svg/ic_check.svg new file mode 100644 index 00000000..2493a29b --- /dev/null +++ b/assets/svg/ic_check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_checked.svg b/assets/svg/ic_checked.svg new file mode 100644 index 00000000..e48e856d --- /dev/null +++ b/assets/svg/ic_checked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_close.svg b/assets/svg/ic_close.svg new file mode 100644 index 00000000..2ea64810 --- /dev/null +++ b/assets/svg/ic_close.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/ic_cloud_downloaded.svg b/assets/svg/ic_cloud_downloaded.svg new file mode 100644 index 00000000..7b7f3be6 --- /dev/null +++ b/assets/svg/ic_cloud_downloaded.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/svg/ic_cloud_downloading.svg b/assets/svg/ic_cloud_downloading.svg new file mode 100644 index 00000000..6289210b --- /dev/null +++ b/assets/svg/ic_cloud_downloading.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/svg/ic_contact_group.svg b/assets/svg/ic_contact_group.svg new file mode 100644 index 00000000..c24d2bf2 --- /dev/null +++ b/assets/svg/ic_contact_group.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/ic_delete_file.svg b/assets/svg/ic_delete_file.svg new file mode 100644 index 00000000..d588a6a2 --- /dev/null +++ b/assets/svg/ic_delete_file.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/svg/ic_download_file.svg b/assets/svg/ic_download_file.svg new file mode 100644 index 00000000..e233af3b --- /dev/null +++ b/assets/svg/ic_download_file.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/ic_file.svg b/assets/svg/ic_file.svg new file mode 100644 index 00000000..8d780952 --- /dev/null +++ b/assets/svg/ic_file.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_files.svg b/assets/svg/ic_files.svg new file mode 100644 index 00000000..d8eccee7 --- /dev/null +++ b/assets/svg/ic_files.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_filter.svg b/assets/svg/ic_filter.svg new file mode 100644 index 00000000..0eb8ce15 --- /dev/null +++ b/assets/svg/ic_filter.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_filter_opened.svg b/assets/svg/ic_filter_opened.svg new file mode 100644 index 00000000..a7fcd3da --- /dev/null +++ b/assets/svg/ic_filter_opened.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_image.svg b/assets/svg/ic_image.svg new file mode 100644 index 00000000..e43c4ce2 --- /dev/null +++ b/assets/svg/ic_image.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/svg/ic_note.svg b/assets/svg/ic_note.svg new file mode 100644 index 00000000..136b43ed --- /dev/null +++ b/assets/svg/ic_note.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_other.svg b/assets/svg/ic_other.svg new file mode 100644 index 00000000..5812c943 --- /dev/null +++ b/assets/svg/ic_other.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/ic_photos.svg b/assets/svg/ic_photos.svg new file mode 100644 index 00000000..e33e07ec --- /dev/null +++ b/assets/svg/ic_photos.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_plus_11px.svg b/assets/svg/ic_plus_11px.svg new file mode 100644 index 00000000..8a28206c --- /dev/null +++ b/assets/svg/ic_plus_11px.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_receive.svg b/assets/svg/ic_receive.svg new file mode 100644 index 00000000..ddc12fbc --- /dev/null +++ b/assets/svg/ic_receive.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_receive_border.svg b/assets/svg/ic_receive_border.svg new file mode 100644 index 00000000..0c2c1e1d --- /dev/null +++ b/assets/svg/ic_receive_border.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_received.svg b/assets/svg/ic_received.svg new file mode 100644 index 00000000..33a01b10 --- /dev/null +++ b/assets/svg/ic_received.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_reload.svg b/assets/svg/ic_reload.svg new file mode 100644 index 00000000..b8d8df71 --- /dev/null +++ b/assets/svg/ic_reload.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_search.svg b/assets/svg/ic_search.svg new file mode 100644 index 00000000..a0fc8607 --- /dev/null +++ b/assets/svg/ic_search.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_send.svg b/assets/svg/ic_send.svg new file mode 100644 index 00000000..123fb378 --- /dev/null +++ b/assets/svg/ic_send.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_send_border.svg b/assets/svg/ic_send_border.svg new file mode 100644 index 00000000..0c1ee148 --- /dev/null +++ b/assets/svg/ic_send_border.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_send_file.svg b/assets/svg/ic_send_file.svg new file mode 100644 index 00000000..8b833b73 --- /dev/null +++ b/assets/svg/ic_send_file.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_sent.svg b/assets/svg/ic_sent.svg new file mode 100644 index 00000000..07783da8 --- /dev/null +++ b/assets/svg/ic_sent.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_setting_backup.svg b/assets/svg/ic_setting_backup.svg new file mode 100644 index 00000000..5914a8e0 --- /dev/null +++ b/assets/svg/ic_setting_backup.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_setting_block.svg b/assets/svg/ic_setting_block.svg new file mode 100644 index 00000000..941f35a0 --- /dev/null +++ b/assets/svg/ic_setting_block.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_setting_contact_us.svg b/assets/svg/ic_setting_contact_us.svg new file mode 100644 index 00000000..ae591052 --- /dev/null +++ b/assets/svg/ic_setting_contact_us.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_setting_delete.svg b/assets/svg/ic_setting_delete.svg new file mode 100644 index 00000000..3d9ff145 --- /dev/null +++ b/assets/svg/ic_setting_delete.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/svg/ic_setting_faq.svg b/assets/svg/ic_setting_faq.svg new file mode 100644 index 00000000..5d581bd6 --- /dev/null +++ b/assets/svg/ic_setting_faq.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_setting_privacy.svg b/assets/svg/ic_setting_privacy.svg new file mode 100644 index 00000000..29c7125f --- /dev/null +++ b/assets/svg/ic_setting_privacy.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_setting_switch.svg b/assets/svg/ic_setting_switch.svg new file mode 100644 index 00000000..fbf7b2c2 --- /dev/null +++ b/assets/svg/ic_setting_switch.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_sort.svg b/assets/svg/ic_sort.svg new file mode 100644 index 00000000..e0ce4708 --- /dev/null +++ b/assets/svg/ic_sort.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_trash.svg b/assets/svg/ic_trash.svg new file mode 100644 index 00000000..d4719184 --- /dev/null +++ b/assets/svg/ic_trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_trust.svg b/assets/svg/ic_trust.svg new file mode 100644 index 00000000..27103ea7 --- /dev/null +++ b/assets/svg/ic_trust.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_trust_activated.svg b/assets/svg/ic_trust_activated.svg new file mode 100644 index 00000000..30c41039 --- /dev/null +++ b/assets/svg/ic_trust_activated.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_uncheck.svg b/assets/svg/ic_uncheck.svg new file mode 100644 index 00000000..2a111542 --- /dev/null +++ b/assets/svg/ic_uncheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_unchecked.svg b/assets/svg/ic_unchecked.svg new file mode 100644 index 00000000..4a25b358 --- /dev/null +++ b/assets/svg/ic_unchecked.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_videos.svg b/assets/svg/ic_videos.svg new file mode 100644 index 00000000..91f3bcc4 --- /dev/null +++ b/assets/svg/ic_videos.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_white_trust.svg b/assets/svg/ic_white_trust.svg new file mode 100644 index 00000000..df0762e5 --- /dev/null +++ b/assets/svg/ic_white_trust.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_zips.svg b/assets/svg/ic_zips.svg new file mode 100644 index 00000000..479a0ea1 --- /dev/null +++ b/assets/svg/ic_zips.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/my_files.svg b/assets/svg/my_files.svg new file mode 100644 index 00000000..e3925589 --- /dev/null +++ b/assets/svg/my_files.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/navigation_rectangle.svg b/assets/svg/navigation_rectangle.svg new file mode 100644 index 00000000..b77ce64d --- /dev/null +++ b/assets/svg/navigation_rectangle.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/plus.svg b/assets/svg/plus.svg new file mode 100644 index 00000000..83a18aee --- /dev/null +++ b/assets/svg/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/settings.svg b/assets/svg/settings.svg new file mode 100644 index 00000000..42bd6a9d --- /dev/null +++ b/assets/svg/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 25fb7fb6..c5ff5d37 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 13.0 diff --git a/ios/Podfile b/ios/Podfile index b74ea69c..b44d63a9 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -31,6 +31,8 @@ target 'Runner' do use_frameworks! use_modular_headers! + pod 'DKImagePickerController', '4.3.4' + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 18b6777e..6203d15c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -11,14 +11,33 @@ PODS: - Flutter - biometric_storage (0.0.1): - Flutter - - DKImagePickerController/Core (4.3.3): + - CropViewController (2.6.1) + - DKCamera (1.6.7) + - DKImagePickerController (4.3.4): + - DKImagePickerController/Camera (= 4.3.4) + - DKImagePickerController/Core (= 4.3.4) + - DKImagePickerController/ImageDataManager (= 4.3.4) + - DKImagePickerController/InlineCamera (= 4.3.4) + - DKImagePickerController/PhotoEditor (= 4.3.4) + - DKImagePickerController/PhotoGallery (= 4.3.4) + - DKImagePickerController/Resource (= 4.3.4) + - DKImagePickerController/Camera (4.3.4): + - DKCamera + - DKImagePickerController/Core + - DKImagePickerController/Core (4.3.4): - DKImagePickerController/ImageDataManager - DKImagePickerController/Resource - - DKImagePickerController/ImageDataManager (4.3.3) - - DKImagePickerController/PhotoGallery (4.3.3): + - DKImagePickerController/ImageDataManager (4.3.4) + - DKImagePickerController/InlineCamera (4.3.4): + - DKCamera + - DKImagePickerController/Core + - DKImagePickerController/PhotoEditor (4.3.4): + - CropViewController (~> 2.5) + - DKImagePickerController/Core + - DKImagePickerController/PhotoGallery (4.3.4): - DKImagePickerController/Core - DKPhotoGallery - - DKImagePickerController/Resource (4.3.3) + - DKImagePickerController/Resource (4.3.4) - DKPhotoGallery (0.0.17): - DKPhotoGallery/Core (= 0.0.17) - DKPhotoGallery/Model (= 0.0.17) @@ -57,11 +76,11 @@ PODS: - Flutter - flutter_local_notifications (0.0.1): - Flutter - - flutter_qr_reader (0.0.1): - - Flutter - fluttertoast (0.0.2): - Flutter - Toast + - image_picker_ios (0.0.1): + - Flutter - libwebp (1.2.1): - libwebp/demux (= 1.2.1) - libwebp/mux (= 1.2.1) @@ -79,8 +98,9 @@ PODS: - Flutter - package_info_plus (0.4.5): - Flutter - - path_provider_ios (0.0.1): + - path_provider_foundation (0.0.1): - Flutter + - FlutterMacOS - permission_handler_apple (9.0.4): - Flutter - qr_code_scanner (0.2.0): @@ -96,9 +116,10 @@ PODS: - SDWebImage/Core (~> 5.10) - share_plus (0.0.1): - Flutter - - shared_preferences_ios (0.0.1): + - shared_preferences_foundation (0.0.1): - Flutter - - SwiftyGif (5.4.3) + - FlutterMacOS + - SwiftyGif (5.4.4) - Toast (4.0.0) - url_launcher_ios (0.0.1): - Flutter @@ -117,22 +138,23 @@ DEPENDENCIES: - at_file_saver (from `.symlinks/plugins/at_file_saver/ios`) - at_onboarding_flutter (from `.symlinks/plugins/at_onboarding_flutter/ios`) - biometric_storage (from `.symlinks/plugins/biometric_storage/ios`) + - DKImagePickerController (= 4.3.4) - emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) - flutter_keychain (from `.symlinks/plugins/flutter_keychain/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - flutter_qr_reader (from `.symlinks/plugins/flutter_qr_reader/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - open_file (from `.symlinks/plugins/open_file/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_compress (from `.symlinks/plugins/video_compress/ios`) - video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`) @@ -140,6 +162,8 @@ DEPENDENCIES: SPEC REPOS: trunk: + - CropViewController + - DKCamera - DKImagePickerController - DKPhotoGallery - libwebp @@ -175,16 +199,16 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_keychain/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" - flutter_qr_reader: - :path: ".symlinks/plugins/flutter_qr_reader/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" open_file: :path: ".symlinks/plugins/open_file/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_ios: - :path: ".symlinks/plugins/path_provider_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" qr_code_scanner: @@ -193,8 +217,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/receive_sharing_intent/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" - shared_preferences_ios: - :path: ".symlinks/plugins/shared_preferences_ios/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" video_compress: @@ -211,36 +235,38 @@ SPEC CHECKSUMS: at_file_saver: c0e052c72d8c0296318bd70f2ae7f510887014ce at_onboarding_flutter: e8219b6d0bfb236d3837ec3528871aebdcc56e8d biometric_storage: 1400f1382af3a4cc2bf05340e13c3d8de873ceb9 - DKImagePickerController: 72fd378f244cef3d27288e0aebf217a4467e4012 + CropViewController: 58fb440f30dac788b129d2a1f24cffdcb102669c + DKCamera: a902b66921fca14b7a75266feb8c7568aa7caa71 + DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 emoji_picker_flutter: df19dac03a2b39ac667dc8d1da939ef3a9e21347 - file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + file_picker: ce3938a0df3cc1ef404671531facef740d03f920 + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433 flutter_keychain: 01aabf894ffe8b01adfda1d9df21c210c1b4b452 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 - flutter_qr_reader: d930dde3b2cfe2b3d0bb7d66e5ff3e514300a5e5 - fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037 + fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 + image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1 SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad - SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 + shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 + SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 - url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1 webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f -PODFILE CHECKSUM: db8a7794fb1b0bee1e3803fed876c7cf9577099b +PODFILE CHECKSUM: a69c71e5c6dcbb517b96ee8fed9f0c82ee0b181a COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index fc1e35ee..05e74628 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -282,10 +282,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -296,6 +298,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -415,7 +418,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 55; DEVELOPMENT_TEAM = 5XUSS6C2DF; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Share Extension/Info.plist"; @@ -425,7 +428,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.10; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.atsign.atmosphere-pro.ShareExtension"; @@ -448,7 +451,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 55; DEVELOPMENT_TEAM = 5XUSS6C2DF; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Share Extension/Info.plist"; @@ -458,7 +461,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.10; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.atsign.atmosphere-pro.ShareExtension"; PRODUCT_NAME = "@mosphere-pro"; @@ -478,7 +481,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 55; DEVELOPMENT_TEAM = 5XUSS6C2DF; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Share Extension/Info.plist"; @@ -488,7 +491,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.10; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.atsign.atmosphere-pro.ShareExtension"; PRODUCT_NAME = "@mosphere-pro"; @@ -560,7 +563,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 55; DEVELOPMENT_TEAM = 5XUSS6C2DF; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -577,7 +580,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.10; PRODUCT_BUNDLE_IDENTIFIER = "com.atsign.atmosphere-pro"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -708,7 +711,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 55; DEVELOPMENT_TEAM = 5XUSS6C2DF; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -725,7 +728,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.10; PRODUCT_BUNDLE_IDENTIFIER = "com.atsign.atmosphere-pro"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -747,7 +750,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 55; DEVELOPMENT_TEAM = 5XUSS6C2DF; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -764,7 +767,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.10; PRODUCT_BUNDLE_IDENTIFIER = "com.atsign.atmosphere-pro"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 22f1584c..aeb15328 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -60,6 +60,8 @@ Enable atmospherePro to save images and videos in photos after being downloaded. NSPhotoLibraryUsageDescription Enable atmospherePro to access your photos to upload the ones you choose in file transfer. + UIApplicationSupportsIndirectInputEvents + UIFileSharingEnabled UILaunchStoryboardName diff --git a/lib/app.dart b/lib/app.dart index 4fdbee87..592f1af7 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,3 +1,5 @@ +import 'package:atsign_atmosphere_pro/view_models/add_contact_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/create_group_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/file_download_checker.dart'; import 'package:atsign_atmosphere_pro/desktop_routes/desktop_routes.dart'; import 'package:atsign_atmosphere_pro/view_models/file_progress_provider.dart'; @@ -50,13 +52,15 @@ class _MyAppState extends State { ChangeNotifierProvider( create: (context) => SideBarProvider()), ChangeNotifierProvider(create: (context) => TrustedContactProvider()), + ChangeNotifierProvider(create: (context) => AddContactProvider()), ChangeNotifierProvider(create: (context) => NestedRouteProvider()), ChangeNotifierProvider(create: (context) => SwitchAtsignProvider()), ChangeNotifierProvider(create: (context) => FileDownloadChecker()), ChangeNotifierProvider(create: (context) => FileProgressProvider()), ChangeNotifierProvider( create: (context) => InternetConnectivityChecker()), - ChangeNotifierProvider(create: (context) => MyFilesProvider()) + ChangeNotifierProvider(create: (context) => MyFilesProvider()), + ChangeNotifierProvider(create: (context) => CreateGroupProvider()) ], child: MaterialApp( builder: (BuildContext context, Widget? child) { @@ -79,7 +83,7 @@ class _MyAppState extends State { initialRoute: initialRoute, navigatorKey: NavService.navKey, theme: ThemeData( - fontFamily: 'HelveticaNeu', + fontFamily: 'Poppins', scaffoldBackgroundColor: Colors.white, primaryColor: Color.fromARGB(255, 240, 94, 62), appBarTheme: AppBarTheme( diff --git a/lib/data_models/enums/contact_filter_type.dart b/lib/data_models/enums/contact_filter_type.dart new file mode 100644 index 00000000..d3f1d100 --- /dev/null +++ b/lib/data_models/enums/contact_filter_type.dart @@ -0,0 +1,14 @@ +enum ContactFilter { all, contacts, groups } + +extension ContactFilterExtension on ContactFilter { + String get display { + switch (this) { + case ContactFilter.all: + return "All Contacts"; + case ContactFilter.contacts: + return "Contacts"; + case ContactFilter.groups: + return "Groups"; + } + } +} diff --git a/lib/data_models/enums/contact_type.dart b/lib/data_models/enums/contact_type.dart new file mode 100644 index 00000000..1b083513 --- /dev/null +++ b/lib/data_models/enums/contact_type.dart @@ -0,0 +1,16 @@ +enum ListContactType { contact, trusted, groups, all } + +extension ContactsTypeExtension on ListContactType { + String get display { + switch (this) { + case ListContactType.contact: + return "Contacts"; + case ListContactType.trusted: + return "Trusted"; + case ListContactType.groups: + return "Groups"; + case ListContactType.all: + return "All"; + } + } +} diff --git a/lib/data_models/enums/file_category_type.dart b/lib/data_models/enums/file_category_type.dart new file mode 100644 index 00000000..9f9a66b7 --- /dev/null +++ b/lib/data_models/enums/file_category_type.dart @@ -0,0 +1,112 @@ +import 'package:atsign_atmosphere_pro/utils/file_types.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; + +enum FileType { photo, file, audio, video, zips, other } + +extension GenderTypeExtension on FileType { + String get text { + switch (this) { + case FileType.photo: + return 'Photos'; + case FileType.file: + return 'Files'; + case FileType.audio: + return 'Audio'; + case FileType.video: + return 'Videos'; + case FileType.zips: + return 'Zips'; + case FileType.other: + return 'Other'; + } + } + + String get image { + switch (this) { + case FileType.photo: + return AppVectors.icCategoryImage; + case FileType.file: + return AppVectors.icCategoryFiles; + case FileType.audio: + return AppVectors.icCategoryVolume; + case FileType.video: + return AppVectors.icCategoryPlay; + case FileType.zips: + return AppVectors.icCategoryFolder; + case FileType.other: + return AppVectors.icCategoryOther; + } + } + + String get icon { + switch (this) { + case FileType.photo: + return AppVectors.icPhotos; + case FileType.file: + return AppVectors.icFiles; + case FileType.audio: + return AppVectors.icAudio; + case FileType.video: + return AppVectors.icVideos; + case FileType.zips: + return AppVectors.icZips; + case FileType.other: + return AppVectors.icOther; + } + } + + List get suffixName { + switch (this) { + case FileType.photo: + return FileTypes.IMAGE_TYPES; + case FileType.file: + return FileTypes.PDF_TYPES + + FileTypes.WORD_TYPES + + FileTypes.EXEL_TYPES; + case FileType.audio: + return FileTypes.AUDIO_TYPES; + case FileType.video: + return FileTypes.VIDEO_TYPES; + case FileType.zips: + return FileTypes.ZIP_TYPES; + default: + return []; + } + } + + List get backgroundColor { + switch (this) { + case FileType.photo: + return [ + Color(0xFFF07C50), + Color(0xFFD86033), + ]; + case FileType.file: + return [ + Color(0xFFE98C49), + Color(0xFFFE8228), + ]; + case FileType.audio: + return [ + Color(0xFFFFB13D), + Color(0xFFFFAD33), + ]; + case FileType.video: + return [ + Color(0xFFE47140), + Color(0xFFF67137), + ]; + case FileType.zips: + return [ + Color(0xFFF09650), + Color(0xFFFD8E28), + ]; + case FileType.other: + return [ + Color(0xFFF1B65C), + Color(0xFFFFB545), + ]; + } + } +} diff --git a/lib/data_models/enums/file_types.dart b/lib/data_models/enums/file_types.dart new file mode 100644 index 00000000..4e73bd89 --- /dev/null +++ b/lib/data_models/enums/file_types.dart @@ -0,0 +1,24 @@ +// enum FileType { all, photo, video, audio, apk, document, unknown } +// +// extension GenderTypeExtension on FileType { +// String get text { +// switch (this) { +// case FileType.all: +// return 'All'; +// case FileType.photo: +// return 'Photo'; +// case FileType.video: +// return 'Video'; +// case FileType.audio: +// return 'Audio'; +// case FileType.apk: +// return 'APK'; +// case FileType.document: +// return 'Document'; +// case FileType.unknown: +// return 'Unknown'; +// default: +// return ''; +// } +// } +// } diff --git a/lib/data_models/file_entity.dart b/lib/data_models/file_entity.dart new file mode 100644 index 00000000..5e939a24 --- /dev/null +++ b/lib/data_models/file_entity.dart @@ -0,0 +1,29 @@ +import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_transfer_object.dart'; + +class FileEntity { + final FileData? file; + final String? date; + final HistoryType? historyType; + final String? atSign; + final String? note; + final String transferId; + final FileTransferObject fileTransferObject; + + // to manage file upload + bool isUploading; + bool isUploaded; + + FileEntity({ + this.file, + this.date, + this.historyType, + this.atSign, + this.note, + required this.transferId, + required this.fileTransferObject, + this.isUploading = false, + this.isUploaded = false, + }); +} diff --git a/lib/data_models/file_modal.dart b/lib/data_models/file_modal.dart index 9076ece5..c9069030 100644 --- a/lib/data_models/file_modal.dart +++ b/lib/data_models/file_modal.dart @@ -1,8 +1,37 @@ import 'dart:convert'; import 'package:atsign_atmosphere_pro/data_models/file_transfer_status.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; -enum HistoryType { send, received } +enum HistoryType { all, received, send } + +extension HistoryTypeExtension on HistoryType { + String get text { + switch (this) { + case HistoryType.all: + return 'All'; + case HistoryType.received: + return 'Received'; + case HistoryType.send: + return 'Sent'; + default: + return ''; + } + } + + String? get icon { + switch (this) { + case HistoryType.all: + return null; + case HistoryType.received: + return AppVectors.icReceived; + case HistoryType.send: + return AppVectors.icSent; + default: + return ''; + } + } +} class FilesDetail { String? fileName; @@ -12,8 +41,10 @@ class FilesDetail { String? contactName; int? id; String? date; + String? message; String? fileTransferId; FileTransferStatus? status; + FilesDetail( {this.fileName, this.filePath, @@ -23,6 +54,7 @@ class FilesDetail { this.contactName, this.id, this.date, + this.message, this.fileTransferId}); FilesDetail copyWith({ @@ -31,6 +63,7 @@ class FilesDetail { double? size, String? type, String? date, + String? message, FileTransferStatus? status, String? contactName, int? id, @@ -41,6 +74,7 @@ class FilesDetail { size: size ?? this.size, type: type ?? this.type, date: date ?? this.date, + message: message ?? this.message, status: status ?? this.status, id: id ?? this.id, contactName: contactName ?? this.contactName); @@ -53,6 +87,7 @@ class FilesDetail { 'size': size, 'type': type, 'date': date, + 'message': message, 'id': id, 'status': status, 'contactName': contactName @@ -68,6 +103,7 @@ class FilesDetail { size: map['size'], type: map['type'], date: map['date'], + message: map['message'], id: map['id'], status: map['status'], contactName: map['contactName']); @@ -80,7 +116,7 @@ class FilesDetail { @override String toString() { - return 'FilesDetail(fileName: $fileName, filePath: $filePath, size: $size, type: $type, date: $date, id:$id, contactName:$contactName, status:$status)'; + return 'FilesDetail(fileName: $fileName, filePath: $filePath, size: $size, type: $type, date: $date, message: $message, id:$id, contactName:$contactName, status:$status)'; } @override @@ -93,6 +129,7 @@ class FilesDetail { o.size == size && o.type == type && o.date == date && + o.message == message && o.status == status && o.contactName == contactName && o.id == id; @@ -105,6 +142,7 @@ class FilesDetail { size.hashCode ^ type.hashCode ^ date.hashCode ^ + message.hashCode ^ id.hashCode ^ status.hashCode ^ contactName.hashCode; diff --git a/lib/data_models/file_transfer.dart b/lib/data_models/file_transfer.dart index f1fc8a81..89fa08dc 100644 --- a/lib/data_models/file_transfer.dart +++ b/lib/data_models/file_transfer.dart @@ -12,17 +12,18 @@ class FileTransfer { bool? isUpdate; bool? isWidgetOpen; String? notes; - FileTransfer({ - required this.url, - this.files, - this.expiry, - this.platformFiles, - this.date, - required this.key, - this.isUpdate = false, - this.isWidgetOpen = false, - this.notes, - }) { + String? fileEncryptionKey; + FileTransfer( + {required this.url, + this.files, + this.expiry, + this.platformFiles, + this.date, + required this.key, + this.isUpdate = false, + this.isWidgetOpen = false, + this.notes, + required this.fileEncryptionKey}) { this.expiry = expiry ?? DateTime.now().add(Duration(days: 6)); this.date = date ?? DateTime.now(); @@ -46,6 +47,7 @@ class FileTransfer { FileData file = FileData.fromJson(jsonDecode(element)); files!.add(file); }); + fileEncryptionKey = json['fileEncryptionKey']; notes = json['notes']; } @@ -63,6 +65,7 @@ class FileTransfer { }); data['expiry'] = this.expiry!.toUtc().toString(); data['date'] = this.date!.toUtc().toString(); + data['fileEncryptionKey'] = this.fileEncryptionKey; return data; } diff --git a/lib/data_models/file_transfer_object.dart b/lib/data_models/file_transfer_object.dart index 935a241e..8ad7e5de 100644 --- a/lib/data_models/file_transfer_object.dart +++ b/lib/data_models/file_transfer_object.dart @@ -8,7 +8,8 @@ class FileTransferObject { bool? sharedStatus; DateTime? date; String? error; - //// [atClientException] not saved in any key, only used to display the error in frontend + + /// [atClientException] not saved in any key, only used to display the error in frontend Exception? atClientException; FileTransferObject(this.transferId, this.fileEncryptionKey, this.fileUrl, diff --git a/lib/desktop_routes/desktop_route_names.dart b/lib/desktop_routes/desktop_route_names.dart index 2299f268..e5ce2487 100644 --- a/lib/desktop_routes/desktop_route_names.dart +++ b/lib/desktop_routes/desktop_route_names.dart @@ -15,6 +15,7 @@ class DesktopRoutes { static const DESKTOP_GROUP_VIEW = 'desktop_group_view'; static const DESKT_FAQ = 'desktop_faq'; static const DESKTOP_DOWNLOAD_ALL = 'desktop_download_all_files'; + static const String DESKTOP_SETTINGS = 'desktop_settings'; /// Group left half routes static const String DESKTOP_GROUP_LEFT_INITIAL = 'desktop_group_left_initial'; diff --git a/lib/desktop_routes/desktop_routes.dart b/lib/desktop_routes/desktop_routes.dart index bb99ea49..8704d74a 100644 --- a/lib/desktop_routes/desktop_routes.dart +++ b/lib/desktop_routes/desktop_routes.dart @@ -7,6 +7,7 @@ import 'package:atsign_atmosphere_pro/desktop_screens/desktop_download_all_files import 'package:atsign_atmosphere_pro/desktop_screens/desktop_history/desktop_history.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/desktop_home/desktop_home.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/desktop_my_files.dart'; +import 'package:atsign_atmosphere_pro/desktop_screens/desktop_settings/desktop_settings.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/trusted_sender/desktop_empty_trusted_sender.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/trusted_sender/desktop_trusted_sender.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/desktop_welcome_screen/desktop_welcome_screen.dart'; @@ -72,6 +73,7 @@ class DesktopSetupRoutes { ); }, DesktopRoutes.DESKTOP_TRUSTED_SENDER: (context) => DesktopTrustedSender(), + DesktopRoutes.DESKTOP_SETTINGS: (context) => DesktopSettings(), DesktopRoutes.DESKTOP_EMPTY_TRUSTED_SENDER: (context) => DesktopEmptySender(), DesktopRoutes.DESKTOP_GROUP: (context) { @@ -87,7 +89,7 @@ class DesktopSetupRoutes { DesktopRoutes.DESKT_FAQ: (context) => WebsiteScreen( title: 'FAQ', url: '${MixedConstants.WEBSITE_URL}/faqs', - ) + ), }; } diff --git a/lib/desktop_screens/desktop_history/widgets/desktop_received_file_details.dart b/lib/desktop_screens/desktop_history/widgets/desktop_received_file_details.dart index 37a50dc2..088cbc36 100644 --- a/lib/desktop_screens/desktop_history/widgets/desktop_received_file_details.dart +++ b/lib/desktop_screens/desktop_history/widgets/desktop_received_file_details.dart @@ -525,7 +525,11 @@ class _DesktopReceivedFileDetailsState } Widget getDownloadStatus(FileTransferProgress? fileTransferProgress) { - Widget spinner = CircularProgressIndicator(); + Widget spinner = CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ); if (fileTransferProgress == null) { return spinner; diff --git a/lib/desktop_screens/desktop_my_files/desktop_my_files.dart b/lib/desktop_screens/desktop_my_files/desktop_my_files.dart index a965d2b1..4a4cfff9 100644 --- a/lib/desktop_screens/desktop_my_files/desktop_my_files.dart +++ b/lib/desktop_screens/desktop_my_files/desktop_my_files.dart @@ -48,7 +48,12 @@ class _DesktopMyFilesState extends State children: [ SingleChildScrollView( child: (isLoading) - ? Center(child: CircularProgressIndicator()) + ? Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + )) : Container( // reducing size by 75 , so that last list item will be shown height: SizeConfig().screenHeight, diff --git a/lib/desktop_screens/desktop_settings/desktop_settings.dart b/lib/desktop_screens/desktop_settings/desktop_settings.dart new file mode 100644 index 00000000..ff7a5421 --- /dev/null +++ b/lib/desktop_screens/desktop_settings/desktop_settings.dart @@ -0,0 +1,169 @@ +import 'package:at_backupkey_flutter/widgets/backup_key_widget.dart'; +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/desktop_screens/desktop_common_widgets/desktop_header.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/switch_at_sign.dart'; +import 'package:atsign_atmosphere_pro/screens/settings/widgets/settings_buttons.dart'; +import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; +import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; +import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class DesktopSettings extends StatefulWidget { + const DesktopSettings({Key? key}) : super(key: key); + + @override + State createState() => _DesktopSettingsState(); +} + +class _DesktopSettingsState extends State { + final List optionTitle = [ + TextStrings().switchatSign, + TextStrings().backUpKeys, + TextStrings().faqs, + TextStrings().contactUs, + TextStrings().termsAppBar, + TextStrings().deleteAtsigns, + ]; + + final List optionIcons = [ + ImageConstants.switchAtSign, + ImageConstants.backupKeys, + ImageConstants.faqs, + ImageConstants.contactUsLogo, + ImageConstants.termsAndConditions, + ImageConstants.deleteAtsigns, + ]; + + void switchAtsign() async { + var atSignList = await KeychainUtil.getAtsignList(); + await showModalBottomSheet( + context: NavService.navKey.currentContext!, + backgroundColor: Colors.transparent, + builder: (context) => AtSignBottomSheet( + atSignList: atSignList, + ), + ); + } + + @override + void initState() { + super.initState(); + + _initPackageInfo(); + } + + PackageInfo _packageInfo = PackageInfo( + appName: 'Unknown', + packageName: 'Unknown', + version: 'Unknown', + buildNumber: 'Unknown', + ); + + Future _initPackageInfo() async { + final PackageInfo info = await PackageInfo.fromPlatform(); + if (mounted) { + setState(() { + _packageInfo = info; + }); + } + } + + @override + Widget build(BuildContext context) { + SizeConfig().init(context); + return Scaffold( + body: Stack( + children: [ + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ImageConstants.backgroundDesktop, + ), + fit: BoxFit.fill, + ), + ), + ), + SafeArea( + child: Column( + children: [ + SizedBox(height: 20), + DesktopHeader( + title: TextStrings().sidebarSettings, + showBackIcon: false, + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 36, vertical: 18), + child: ListView( + shrinkWrap: true, + children: [ + Text( + 'App Version ${_packageInfo.version} (${_packageInfo.buildNumber})', + style: CustomTextStyles.black12, + ), + SizedBox( + height: 28, + ), + SettingsButton( + buttonText: optionTitle[0], + onPressed: switchAtsign, + image: optionIcons[0], + ), + SizedBox( + height: 12, + ), + SettingsButton( + buttonText: optionTitle[1], + onPressed: () async { + BackupKeyWidget( + atsign: AtClientManager.getInstance() + .atClient + .getCurrentAtSign()!, + ).showBackupDialog(context); + }, + image: optionIcons[1], + ), + Divider( + height: 58, + color: ColorConstants.dividerGrey, + ), + SettingsButton( + buttonText: optionTitle[3], + onPressed: () async { + await launchUrl(Uri( + scheme: 'mailto', + path: 'atmospherepro@atsign.com')); + }, + image: optionIcons[3], + ), + SizedBox( + height: 24, + ), + SettingsButton( + buttonText: optionTitle[5], + onPressed: () async { + CommonUtilityFunctions().showResetAtsignDialog(); + }, + image: optionIcons[5], + ), + SizedBox( + height: 12, + ), + ], + ), + ), + ], + ), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/desktop_screens/desktop_welcome_screen/desktop_welcome_screen.dart b/lib/desktop_screens/desktop_welcome_screen/desktop_welcome_screen.dart index 8fb3d2fd..92f8725f 100644 --- a/lib/desktop_screens/desktop_welcome_screen/desktop_welcome_screen.dart +++ b/lib/desktop_screens/desktop_welcome_screen/desktop_welcome_screen.dart @@ -6,6 +6,7 @@ import 'package:atsign_atmosphere_pro/desktop_screens/desktop_common_widgets/des import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_circle_avatar.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_onboarding.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/gradient_button.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/loading_widget.dart'; import 'package:atsign_atmosphere_pro/services/backend_service.dart'; import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; @@ -19,12 +20,9 @@ import 'package:atsign_atmosphere_pro/view_models/switch_atsign_provider.dart'; import 'package:flutter/material.dart'; import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/provider_handler.dart'; import 'package:provider/provider.dart'; import 'package:atsign_atmosphere_pro/utils/constants.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher.dart'; class DesktopWelcomeScreenStart extends StatefulWidget { @@ -39,31 +37,6 @@ class _DesktopWelcomeScreenStartState extends State { String? currentatSign; AtClient atClient = AtClientManager.getInstance().atClient; - void _showLoader(bool loaderState, String authenticatingForAtsign) { - if (mounted) { - setState(() { - if (loaderState) { - currentatSign = authenticatingForAtsign; - } - authenticating = loaderState; - }); - } - } - - /// returns list of menu items which contains list of onboarded atsigns and [add_new_atsign], [save_backup_key] - Future> getpopupMenuList() async { - var popupMenuList = []; - var atsignList = await BackendService.getInstance().getAtsignList(); - atsignList?.forEach((element) { - popupMenuList.add(element); - }); - - popupMenuList.add(TextStrings() - .addNewAtsign); //to show add option in switch atsign drop down menu. - popupMenuList.add(TextStrings().saveBackupKey); - return popupMenuList; - } - @override Widget build(BuildContext context) { SizeConfig().init(context); @@ -82,141 +55,33 @@ class _DesktopWelcomeScreenStartState extends State { print( 'ProviderHandler SwitchAtsignProvider build called ${AtClientManager.getInstance().atClient.getCurrentAtSign()}'); return Scaffold( - appBar: PreferredSize( - preferredSize: Size.fromHeight(MixedConstants.APPBAR_HEIGHT), - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - padding: const EdgeInsets.all(15.0), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.black, - width: 0.1, - ), - ), - ), - child: AppBar( - leading: InkWell( - onTap: () { - DesktopSetupRoutes.nested_pop(); - }, - child: Image.asset( - ImageConstants.logoIcon, - height: 50.toHeight, - width: 50.toHeight, - ), - ), - actions: [ - FutureBuilder( - key: Key(AtClientManager.getInstance() - .atClient - .getCurrentAtSign()!), - future: getpopupMenuList(), - builder: (context, - AsyncSnapshot> snapshot) { - if (snapshot.data != null) { - List? atsignList = snapshot.data; - var image = CommonUtilityFunctions() - .getCachedContactImage( - atClient.getCurrentAtSign()!); - return Container( - width: 100, - child: PopupMenuButton( - icon: Row( - children: [ - image == null - ? ContactInitial( - initials: atClient - .getCurrentAtSign(), - size: 35, - maxSize: (80.0 - 30.0), - minSize: 35, - ) - : CustomCircleAvatar( - byteImage: image, - nonAsset: true, - size: 35, - ), - Icon(Icons.arrow_drop_down) - ], - ), - elevation: 10, - itemBuilder: (BuildContext context) { - return getPopupMenuItem(atsignList!); - }, - onSelected: onAtsignChange), - ); - } else { - return SizedBox(); - } - }), - ], - ), - ), - ], - ), - ), body: Stack(clipBehavior: Clip.none, children: [ - DesktopWelcomeScreen(), + DesktopWelcomeScreen(atClient: atClient), authenticating ? LoadingDialog().showTextLoader( - '${TextStrings().initialisingFor} $currentatSign') + '${TextStrings().initialisingFor} $currentatSign') : SizedBox() ]), ); }); } - - getPopupMenuItem(List list) { - List> menuItems = []; - list.forEach((element) { - menuItems.add(PopupMenuItem( - value: element, - child: DesktopSwitchAtsign(key: Key(element), atsign: element), - )); - }); - - return menuItems; - } - - onAtsignChange(String selectedOption) async { - late var atClientPrefernce; - await BackendService.getInstance() - .getAtClientPreference() - .then((value) => atClientPrefernce = value) - .catchError((e) => print(e)); - - if (selectedOption == TextStrings().addNewAtsign) { - await CustomOnboarding.onboard( - atSign: '', - atClientPrefernce: atClientPrefernce, - showLoader: _showLoader, - ); - } else if (selectedOption == TextStrings().saveBackupKey) { - BackupKeyWidget( - atsign: AtClientManager.getInstance().atClient.getCurrentAtSign()!, - ).showBackupDialog(context); - } else if (selectedOption != - AtClientManager.getInstance().atClient.getCurrentAtSign()) { - await CustomOnboarding.onboard( - atSign: selectedOption, - atClientPrefernce: atClientPrefernce, - showLoader: _showLoader, - ); - } - } } class DesktopWelcomeScreen extends StatefulWidget { - const DesktopWelcomeScreen({Key? key}) : super(key: key); + const DesktopWelcomeScreen({ + Key? key, + required this.atClient, + }) : super(key: key); + + final AtClient atClient; + @override _DesktopWelcomeScreenState createState() => _DesktopWelcomeScreenState(); } class _DesktopWelcomeScreenState extends State { final List menuItemsIcons = [ + // general ImageConstants.homeIcon, ImageConstants.contactsIcon, ImageConstants.transferHistoryIcon, @@ -225,13 +90,16 @@ class _DesktopWelcomeScreenState extends State { ImageConstants.groups, // ImageConstants.transferHistoryIcon, ImageConstants.trustedSender, - ImageConstants.termsAndConditionsIcon, + // helpcenter ImageConstants.faqsIcon, - ImageConstants.trustedSendersIcon, - ImageConstants.contactUs, + ImageConstants.termsAndConditionsIcon, + ImageConstants.sidebarSettings, + // ImageConstants.trustedSendersIcon, + // ImageConstants.contactUs, ]; final List menuItemsTitle = [ + // general TextStrings().sidebarHome, TextStrings().sidebarContact, TextStrings().sidebarTransferHistory, @@ -240,9 +108,11 @@ class _DesktopWelcomeScreenState extends State { TextStrings().groups, // TextStrings().downloadAllFiles, TextStrings().sidebarTrustedSenders, - TextStrings().sidebarTermsAndConditions, + // helpcenter TextStrings().sidebarFaqs, - TextStrings().sidebarContactUs, + TextStrings().sidebarTermsAndConditions, + TextStrings().sidebarSettings, + // TextStrings().sidebarContactUs, ]; final List routes = [ @@ -254,6 +124,8 @@ class _DesktopWelcomeScreenState extends State { DesktopRoutes.DESKTOP_GROUP, // DesktopRoutes.DESKTOP_DOWNLOAD_ALL, DesktopRoutes.DESKTOP_EMPTY_TRUSTED_SENDER, + DesktopRoutes.DESKTOP_SETTINGS, + '', '', '', '', @@ -265,49 +137,109 @@ class _DesktopWelcomeScreenState extends State { Widget build(BuildContext context) { return Scaffold( body: Stack(children: [ - Row( - children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer(builder: (_context, _sideBarProvider, _) { + return SizedBox( + width: _sideBarProvider.isSidebarExpanded + ? MixedConstants.SIDEBAR_WIDTH_EXPANDED + : MixedConstants.SIDEBAR_WIDTH_COLLAPSED, + ); + }), + Expanded( + child: Navigator( + key: NavService.nestedNavKey, + initialRoute: DesktopRoutes.DESKTOP_HOME_NESTED_INITIAL, + onGenerateRoute: (routeSettings) { + var routeBuilders = + DesktopSetupRoutes.routeBuilders(context, routeSettings); + return MaterialPageRoute(builder: (context) { + return routeBuilders[routeSettings.name!]!(context); + }); + }, + ), + ), + ], + ), Consumer( builder: (_context, _sideBarProvider, _) { - if (_sideBarProvider.isSidebarExpanded) { - MixedConstants.SIDEBAR_WIDTH = 180; - } else { - MixedConstants.SIDEBAR_WIDTH = 70; - } - - return SingleChildScrollView( - child: Container( - width: MixedConstants.SIDEBAR_WIDTH, - padding: EdgeInsets.only( - left: _sideBarProvider.isSidebarExpanded ? 10 : 0), - decoration: BoxDecoration( - color: Colors.white, - border: Border( - right: BorderSide( - color: Colors.black, - width: 0.1, - ), - ), + return Container( + width: _sideBarProvider.isSidebarExpanded + ? MixedConstants.SIDEBAR_WIDTH_EXPANDED + : MixedConstants.SIDEBAR_WIDTH_COLLAPSED, + height: SizeConfig().screenHeight, + margin: EdgeInsets.only(right: 2), + padding: EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), ), - child: ProviderHandler( - functionName: 'routes', - showError: true, - load: (provider) { - provider.init(); - }, - successBuilder: (provider) => Column( - mainAxisAlignment: MainAxisAlignment.center, + boxShadow: [ + BoxShadow( + // color: Colors.grey.withOpacity(0.5), + color: ColorConstants.light_grey, + spreadRadius: 0, + blurRadius: 1, + offset: Offset(1, 2), // changes position of shadow + ), + ], + ), + child: ProviderHandler( + functionName: 'routes', + showError: true, + load: (provider) { + provider.init(); + }, + successBuilder: (provider) => SingleChildScrollView( + child: Column( crossAxisAlignment: _sideBarProvider.isSidebarExpanded - ? CrossAxisAlignment.center + ? CrossAxisAlignment.start : CrossAxisAlignment.center, children: [ + SizedBox(height: 20.toHeight), + InkWell( + onTap: () { + DesktopSetupRoutes.nested_pop(); + }, + child: Image.asset(ImageConstants.logoIcon, + height: 58.toHeight), + ), + SizedBox(height: 20.toHeight), + if (_sideBarProvider.isSidebarExpanded) + GradientButton( + onPressed: () {}, + height: 50.toHeight, + width: MixedConstants.SIDEBAR_WIDTH_EXPANDED - 41, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + TextStrings().sidebarSendFiles, + style: CustomTextStyles.desktopButton15, + ), + SizedBox(width: 8.toWidth), + Image.asset( + ImageConstants.sendIcon, + height: 20.toHeight, + fit: BoxFit.cover, + color: CustomTextStyles.desktopButton15.color, + ), + ], + ), + ), + SizedBox(height: 2.toHeight), + SidebarTitleText(TextStrings().sidebarGeneral), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[0], routes[0], title: menuItemsTitle[0], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[1], routes[1], @@ -317,14 +249,14 @@ class _DesktopWelcomeScreenState extends State { title: menuItemsTitle[1], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[2], routes[2], title: menuItemsTitle[2], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[3], routes[3], @@ -334,28 +266,28 @@ class _DesktopWelcomeScreenState extends State { title: menuItemsTitle[3], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[4], routes[4], title: menuItemsTitle[4], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[5], routes[5], title: menuItemsTitle[5], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[6], routes[6], title: menuItemsTitle[6], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SidebarTitleText(TextStrings().sidebarHelpCenter), // SideBarIcon( // menuItemsIcons[7], // routes[7], @@ -366,87 +298,85 @@ class _DesktopWelcomeScreenState extends State { // SizedBox(height: 40.toHeight), SideBarIcon( menuItemsIcons[7], - routes[7], + routes[10], isUrlLauncher: true, - arguments: {"url": MixedConstants.TERMS_CONDITIONS}, + arguments: {"url": MixedConstants.FAQ}, title: menuItemsTitle[7], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( menuItemsIcons[8], routes[8], isUrlLauncher: true, - arguments: {"url": MixedConstants.FAQ}, + arguments: {"url": MixedConstants.TERMS_CONDITIONS}, title: menuItemsTitle[8], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), - SizedBox(height: 40.toHeight), + SizedBox(height: 2.toHeight), SideBarIcon( - menuItemsIcons[10], + menuItemsIcons[9], routes[7], - isEmailLauncher: true, - arguments: {"email": 'atmospherepro@atsign.com'}, title: menuItemsTitle[9], isSidebarExpanded: _sideBarProvider.isSidebarExpanded, ), + // SideBarIcon( + // menuItemsIcons[10], + // routes[7], + // isEmailLauncher: true, + // arguments: {"email": 'atmospherepro@atsign.com'}, + // title: menuItemsTitle[9], + // isSidebarExpanded: _sideBarProvider.isSidebarExpanded, + // ), + SizedBox(height: 10.toHeight), + BuildAvatarWidget( + atClient: widget.atClient, + isSidebarExpanded: _sideBarProvider.isSidebarExpanded, + ), + SizedBox(height: 10.toHeight), ], ), - errorBuilder: (provider) => Center( - child: Text(TextStrings().errorOccured), - ), + ), + errorBuilder: (provider) => Center( + child: Text(TextStrings().errorOccured), ), ), ); }, ), - Expanded( - child: ClipRect( - child: Navigator( - key: NavService.nestedNavKey, - initialRoute: DesktopRoutes.DESKTOP_HOME_NESTED_INITIAL, - onGenerateRoute: (routeSettings) { - var routeBuilders = - DesktopSetupRoutes.routeBuilders(context, routeSettings); - return MaterialPageRoute(builder: (context) { - return routeBuilders[routeSettings.name!]!(context); - }); + Consumer(builder: (_context, _provider, _) { + return Positioned( + top: 40, + left: _provider.isSidebarExpanded + ? MixedConstants.SIDEBAR_WIDTH_EXPANDED - 20 + : MixedConstants.SIDEBAR_WIDTH_COLLAPSED - 20, + child: Builder( + builder: (context) { + return InkWell( + onTap: () { + Provider.of(context, listen: false) + .updateSidebarWidth(); + }, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.toWidth), + color: Colors.black), + padding: EdgeInsets.only(left: 4), + child: Icon( + _provider.isSidebarExpanded + ? Icons.arrow_back_ios + : Icons.arrow_forward_ios_sharp, + size: 20, + color: Colors.white), + ), + ); }, ), - ), - ), - ], - ), - Consumer(builder: (_context, _provider, _) { - return Positioned( - top: 40, - left: _provider.isSidebarExpanded ? 160 : 50, - child: Builder( - builder: (context) { - return InkWell( - onTap: () { - Provider.of(context, listen: false) - .updateSidebarWidth(); - }, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30.toWidth), - color: Colors.black), - child: Icon( - _provider.isSidebarExpanded - ? Icons.arrow_back_ios - : Icons.arrow_forward_ios_sharp, - size: 20, - color: Colors.white), - ), - ); - }, - ), - ); - }), - ])); + ); + }), + ])); } Widget sendFileTo({bool isSelectContacts = false}) { @@ -463,30 +393,192 @@ class _DesktopWelcomeScreenState extends State { child: ListTile( title: showContent ? Text( - (isSelectContacts - ? '18 contacts added' - : '2 files selected'), - style: CustomTextStyles.desktopSecondaryRegular18) + (isSelectContacts + ? '18 contacts added' + : '2 files selected'), + style: CustomTextStyles.desktopSecondaryRegular18) : SizedBox(), trailing: isSelectContacts ? Container( - padding: EdgeInsets.symmetric(vertical: 15), - child: Image.asset( - ImageConstants.contactsIcon, - color: Colors.black, - ), - ) + padding: EdgeInsets.symmetric(vertical: 15.toHeight), + child: Image.asset( + ImageConstants.contactsIcon, + color: Colors.black, + ), + ) : Container( - padding: EdgeInsets.symmetric(vertical: 15.toHeight), - child: Icon( - Icons.add_circle, - color: Colors.black, - ), - ), + padding: EdgeInsets.symmetric(vertical: 15.toHeight), + child: Icon( + Icons.add_circle, + color: Colors.black, + ), + ), ))); } } +class BuildAvatarWidget extends StatefulWidget { + const BuildAvatarWidget({ + Key? key, + required this.atClient, + required this.isSidebarExpanded, + }) : super(key: key); + + final AtClient atClient; + final bool isSidebarExpanded; + + @override + State createState() => _BuildAvatarWidgetState(); +} + +class _BuildAvatarWidgetState extends State { + bool authenticating = false; + + String? currentatSign; + + void _showLoader(bool loaderState, String authenticatingForAtsign) { + if (mounted) { + setState(() { + if (loaderState) { + currentatSign = authenticatingForAtsign; + } + authenticating = loaderState; + }); + } + } + + /// returns list of menu items which contains list of onboarded atsigns and [add_new_atsign], [save_backup_key] + Future> getpopupMenuList() async { + var popupMenuList = []; + var atsignList = await BackendService.getInstance().getAtsignList(); + atsignList?.forEach((element) { + popupMenuList.add(element); + }); + + popupMenuList.add(TextStrings() + .addNewAtsign); //to show add option in switch atsign drop down menu. + popupMenuList.add(TextStrings().saveBackupKey); + return popupMenuList; + } + + getPopupMenuItem(List list) { + List> menuItems = []; + list.forEach((element) { + menuItems.add(PopupMenuItem( + value: element, + child: DesktopSwitchAtsign(key: Key(element), atsign: element), + )); + }); + + return menuItems; + } + + onAtsignChange(String selectedOption) async { + late var atClientPrefernce; + await BackendService.getInstance() + .getAtClientPreference() + .then((value) => atClientPrefernce = value) + .catchError((e) => print(e)); + + if (selectedOption == TextStrings().addNewAtsign) { + await CustomOnboarding.onboard( + atSign: '', + atClientPrefernce: atClientPrefernce, + showLoader: _showLoader, + ); + } else if (selectedOption == TextStrings().saveBackupKey) { + BackupKeyWidget( + atsign: AtClientManager.getInstance().atClient.getCurrentAtSign()!, + ).showBackupDialog(context); + } else if (selectedOption != + AtClientManager.getInstance().atClient.getCurrentAtSign()) { + await CustomOnboarding.onboard( + atSign: selectedOption, + atClientPrefernce: atClientPrefernce, + showLoader: _showLoader, + ); + } + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + key: Key(AtClientManager.getInstance().atClient.getCurrentAtSign()!), + future: getpopupMenuList(), + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.data != null) { + List? atsignList = snapshot.data; + var image = CommonUtilityFunctions() + .getCachedContactImage(widget.atClient.getCurrentAtSign()!); + return Container( + width: widget.isSidebarExpanded + ? MixedConstants.SIDEBAR_WIDTH_EXPANDED + : MixedConstants.SIDEBAR_WIDTH_COLLAPSED, + height: 74.toHeight, + child: PopupMenuButton( + icon: Row( + mainAxisAlignment: widget.isSidebarExpanded + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + image == null + ? ContactInitialV2( + initials: widget.atClient.getCurrentAtSign(), + size: 50.toFont, + maxSize: (80.0 - 30.0), + minSize: 50, + ) + : CustomCircleAvatarV2( + byteImage: image, + nonAsset: true, + size: 50.toFont, + ), + // Icon(Icons.arrow_drop_down) + if (widget.isSidebarExpanded) + Flexible( + child: Text( + ' ' + widget.atClient.getCurrentAtSign()!, + style: + CustomTextStyles.desktopPrimaryBold12.copyWith( + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + elevation: 10, + itemBuilder: (BuildContext context) { + return getPopupMenuItem(atsignList!); + }, + onSelected: onAtsignChange), + ); + } else { + return SizedBox(); + } + }); + } +} + +class SidebarTitleText extends StatelessWidget { + const SidebarTitleText( + this.text, { + Key? key, + }) : super(key: key); + + final String text; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(vertical: 14.toHeight), + child: Text( + text, + style: CustomTextStyles.desktopSecondaryBold12, + ), + ); + } +} + // ignore: must_be_immutable class SideBarIcon extends StatelessWidget { final String? image, routeName, title; @@ -494,10 +586,10 @@ class SideBarIcon extends StatelessWidget { final bool isUrlLauncher, isSidebarExpanded, isEmailLauncher; SideBarIcon(this.image, this.routeName, {this.arguments, - this.isUrlLauncher = false, - this.isEmailLauncher = false, - this.isSidebarExpanded = true, - this.title}); + this.isUrlLauncher = false, + this.isEmailLauncher = false, + this.isSidebarExpanded = true, + this.title}); bool isHovered = false; bool isCurrentRoute = false; var nestedProvider = Provider.of( @@ -509,117 +601,104 @@ class SideBarIcon extends StatelessWidget { isCurrentRoute = nestedProvider.current_route == routeName ? true : false; if (!isCurrentRoute) { isCurrentRoute = (nestedProvider.current_route == null && - routeName == DesktopRoutes.DESKTOP_HOME) + routeName == DesktopRoutes.DESKTOP_HOME) ? true : false; } - return Container( - width: isSidebarExpanded ? null : 32, - height: 32, - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - shape: BoxShape.circle, - ), - child: InkWell( - onTap: () { - if (routeName != null && routeName != '') { - if (routeName == DesktopRoutes.DESKTOP_HOME) { - DesktopSetupRoutes.nested_pop(); - return; - } - DesktopSetupRoutes.nested_push(routeName, arguments: arguments); - } - if ((isUrlLauncher) && - (arguments != null) && - (arguments!['url'] != null)) { - _launchInBrowser(arguments!['url']); - } - if ((isEmailLauncher) && - (arguments != null) && - (arguments!['email'] != null)) { - _launchInEmail(arguments!['email']); - } - }, - child: routeName == DesktopRoutes.DESKTOP_HISTORY - ? Stack( - clipBehavior: Clip.none, - children: [ - Row( - children: [ - Image.asset( - image!, - height: 22, - color: isCurrentRoute - ? ColorConstants.orangeColor - : ColorConstants.fadedText, - ), - SizedBox(width: isSidebarExpanded ? 10 : 0), - isSidebarExpanded - ? Text( - title!, - softWrap: true, - style: TextStyle( - color: isCurrentRoute - ? ColorConstants.orangeColor - : ColorConstants.fadedText, - letterSpacing: 0.1, - fontSize: 12, - fontWeight: FontWeight.normal, - ), - ) - : SizedBox() - ], - ), - Consumer( - builder: (context, _fileDownloadChecker, _) { - return _fileDownloadChecker.undownloadedFilesExist - ? Positioned( - left: 10, - top: -8, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - padding: EdgeInsets.all(1.toHeight), - child: CircleAvatar( - backgroundColor: Colors.red, - radius: 5.toWidth, - ), - ), - ) - : SizedBox(); - }, - ), - ], - ) - : Row( - children: [ - Image.asset( - image!, - height: 22, - color: isCurrentRoute - ? ColorConstants.orangeColor - : ColorConstants.fadedText, - ), - SizedBox(width: isSidebarExpanded ? 10 : 0), - isSidebarExpanded - ? Text( - title!, - softWrap: true, - style: TextStyle( - color: isCurrentRoute - ? ColorConstants.orangeColor - : ColorConstants.fadedText, - letterSpacing: 0.1, - fontSize: 12, - fontWeight: FontWeight.normal, + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + isCurrentRoute + ? Image.asset( + ImageConstants.sidebarSelectedTile, + height: 50.toHeight, + width: 4, + ) + : SizedBox(width: 4), + Flexible( + child: Container( + width: isSidebarExpanded ? double.maxFinite : 50, + height: 50.toHeight, + padding: EdgeInsets.symmetric( + vertical: 14.toHeight, + horizontal: isSidebarExpanded ? 20 : 12, + ), + decoration: isCurrentRoute + ? BoxDecoration( + color: ColorConstants.sidebarTileSelected, + borderRadius: BorderRadius.only( + topRight: Radius.circular(8), + bottomRight: Radius.circular(8), + )) + : null, + child: InkWell( + onTap: () { + if (routeName != null && routeName != '') { + if (routeName == DesktopRoutes.DESKTOP_HOME) { + DesktopSetupRoutes.nested_pop(); + return; + } + DesktopSetupRoutes.nested_push(routeName, + arguments: arguments); + } + if ((isUrlLauncher) && + (arguments != null) && + (arguments!['url'] != null)) { + _launchInBrowser(arguments!['url']); + } + if ((isEmailLauncher) && + (arguments != null) && + (arguments!['email'] != null)) { + _launchInEmail(arguments!['email']); + } + }, + child: routeName == DesktopRoutes.DESKTOP_HISTORY + ? Stack( + clipBehavior: Clip.none, + children: [ + BuildSidebarIconTitle( + image: image, + isCurrentRoute: isCurrentRoute, + isSidebarExpanded: isSidebarExpanded, + title: title, + ), + Consumer( + builder: (context, _fileDownloadChecker, _) { + return _fileDownloadChecker + .undownloadedFilesExist + ? Positioned( + left: 12, + top: -4, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + padding: EdgeInsets.all(1.toHeight), + child: CircleAvatar( + backgroundColor: Colors.red, + radius: 5.toWidth, + ), ), ) - : SizedBox() - ], - ), - )); + : SizedBox(); + }, + ), + ], + ) + : BuildSidebarIconTitle( + image: image, + isCurrentRoute: isCurrentRoute, + isSidebarExpanded: isSidebarExpanded, + title: title, + ), + )), + ), + ], + ), + ); } Future _launchInBrowser(String url) async { @@ -644,3 +723,47 @@ class SideBarIcon extends StatelessWidget { ); } } + +class BuildSidebarIconTitle extends StatelessWidget { + const BuildSidebarIconTitle({ + Key? key, + required this.image, + required this.isCurrentRoute, + required this.isSidebarExpanded, + required this.title, + }) : super(key: key); + + final String? image; + final bool isCurrentRoute; + final bool isSidebarExpanded; + final String? title; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + image!, + height: 22.toFont, + color: isCurrentRoute + ? ColorConstants.sidebarTextSelected + : ColorConstants.sidebarTextUnselected, + ), + SizedBox(width: isSidebarExpanded ? 10 : 0), + isSidebarExpanded + ? Text( + title!, + softWrap: true, + style: CustomTextStyles.desktopPrimaryRegular12.copyWith( + color: isCurrentRoute + ? null + : ColorConstants.sidebarTextUnselected, + fontWeight: FontWeight.w500, + ), + ) + : SizedBox() + ], + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 597427ab..8d23115f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,9 @@ -import 'package:flutter/material.dart'; -import 'package:at_utils/at_logger.dart'; +import 'dart:io'; + import 'package:desktop_window/desktop_window.dart'; +import 'package:flutter/material.dart'; + import 'app.dart'; -import 'dart:io'; void main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/routes/route_names.dart b/lib/routes/route_names.dart index ddbd3a39..731dcd16 100644 --- a/lib/routes/route_names.dart +++ b/lib/routes/route_names.dart @@ -5,6 +5,7 @@ class Routes { static const String FAQ_SCREEN = 'faqScreen'; static const String BLOCKED_USERS = 'blockedUsers'; static const String HISTORY = 'history'; + static const String HISTORY_SCREEN = 'historyScreen'; static const String CONTACT_SCREEN = 'contactScreen'; static const String ADD_CONTACT_SCREEN = 'addContactScreen'; static const String SCAN_QR_SCREEN = 'scanQrScreen'; @@ -12,5 +13,7 @@ class Routes { static const String TRUSTED_CONTACTS = 'trustedContacts'; static const String EMPTY_TRUSTED_CONTACTS = 'emptyTrustedContacts'; static const String MY_FILES = 'myFiles'; + static const String MY_FILES_SCREEN = 'myFilesScreen'; static const String GROUPS = 'groups'; + static const String SETTINGS = 'settings'; } diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index cca75c11..1e8b01c2 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -2,10 +2,13 @@ import 'package:at_contacts_flutter/screens/blocked_screen.dart'; import 'package:at_contacts_group_flutter/screens/group_contact_view/group_contact_view.dart'; import 'package:at_contacts_group_flutter/screens/list/group_list.dart'; import 'package:atsign_atmosphere_pro/routes/route_names.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/website_webview.dart'; import 'package:atsign_atmosphere_pro/screens/history/history_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/history/transfer_history_screen.dart'; import 'package:atsign_atmosphere_pro/screens/home/home.dart'; import 'package:atsign_atmosphere_pro/screens/my_files/my_files.dart'; -import 'package:atsign_atmosphere_pro/screens/common_widgets/website_webview.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/my_files_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/settings/settings_screen.dart'; import 'package:atsign_atmosphere_pro/screens/trusted_contacts/trusted_contacts.dart'; import 'package:atsign_atmosphere_pro/screens/welcome_screen/welcome_screen.dart'; import 'package:atsign_atmosphere_pro/utils/constants.dart'; @@ -22,13 +25,23 @@ class SetupRoutes { ModalRoute.of(context)!.settings.arguments as Map; return WebsiteScreen(title: args["title"], url: args["url"]); }, - Routes.WELCOME_SCREEN: (context) => WelcomeScreen(), + Routes.WELCOME_SCREEN: (context) { + Map args = + (ModalRoute.of(context)!.settings.arguments ?? {}) + as Map; + + return WelcomeScreen( + indexBottomBarSelected: args['indexBottomBarSelected'], + ); + }, Routes.FAQ_SCREEN: (context) => WebsiteScreen( title: 'FAQ', url: '${MixedConstants.WEBSITE_URL}/faqs', ), Routes.MY_FILES: (context) => MyFiles(), + Routes.MY_FILES_SCREEN: (context) => MyFilesScreen(), Routes.HISTORY: (context) => HistoryScreen(tabIndex: 1), + Routes.HISTORY_SCREEN: (context) => TransferHistoryScreen(), Routes.BLOCKED_USERS: (context) => BlockedScreen(), Routes.CONTACT_SCREEN: (context) { Map args = @@ -48,6 +61,7 @@ class SetupRoutes { return GroupList(); }, Routes.TRUSTED_CONTACTS: (context) => TrustedContacts(), + Routes.SETTINGS: (context) => SettingsScreen() }; } } diff --git a/lib/screens/common_widgets/add_contact.dart b/lib/screens/common_widgets/add_contact.dart index 947877ca..87cc9726 100644 --- a/lib/screens/common_widgets/add_contact.dart +++ b/lib/screens/common_widgets/add_contact.dart @@ -5,6 +5,7 @@ import 'package:at_common_flutter/services/size_config.dart'; import 'package:at_common_flutter/widgets/custom_button.dart'; import 'package:at_contacts_flutter/services/contact_service.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_circle_avatar.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; import 'package:flutter/material.dart'; @@ -107,7 +108,11 @@ class _AddContactState extends State { ), isContactAdding ? Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ), ) : Container( alignment: Alignment.center, diff --git a/lib/screens/common_widgets/app_bar.dart b/lib/screens/common_widgets/app_bar.dart index 597ec3bb..b2721275 100644 --- a/lib/screens/common_widgets/app_bar.dart +++ b/lib/screens/common_widgets/app_bar.dart @@ -5,21 +5,17 @@ ///[title] is a [String] to display the title of the appbar ///[showTrailingButton] toggles the visibility of trailing button, default add icon ///therefore it has it's navigation embedded in the widget itself. - import 'dart:io'; -import 'package:at_contact/at_contact.dart'; + +import 'package:at_common_flutter/services/size_config.dart'; import 'package:at_contacts_flutter/screens/contacts_screen.dart'; import 'package:at_contacts_flutter/widgets/add_contacts_dialog.dart'; -import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; import 'package:atsign_atmosphere_pro/services/backend_service.dart'; -import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; -import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/utils/images.dart'; import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; import 'package:atsign_atmosphere_pro/view_models/file_download_checker.dart'; -import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; import 'package:filesystem_picker/filesystem_picker.dart'; import 'package:flutter/material.dart'; @@ -68,7 +64,7 @@ class _CustomAppBarState extends State { Widget build(BuildContext context) { return AppBar( elevation: widget.elevation, - centerTitle: true, + centerTitle: false, leading: (widget.showLeadingicon) ? Image.asset(ImageConstants.logoIcon) : (widget.showBackButton) @@ -109,11 +105,9 @@ class _CustomAppBarState extends State { ), Expanded( child: (widget.showTitle) - ? Center( - child: Text( - widget.title!, - style: CustomTextStyles.primaryBold18, - ), + ? Text( + widget.title!, + style: CustomTextStyles.blackBold25, ) : Container(), ), @@ -172,12 +166,12 @@ class _CustomAppBarState extends State { asSelectionScreen: true, selectedContactsHistory: [], selectedList: (s) async { - for(var element in s){ + for (var element in s) { await Provider.of< - TrustedContactProvider>( - context, - listen: false) - .addTrustedContacts(element!); + TrustedContactProvider>( + context, + listen: false) + .addTrustedContacts(element!); } }, ), @@ -200,7 +194,7 @@ class _CustomAppBarState extends State { ) ], automaticallyImplyLeading: false, - backgroundColor: ColorConstants.appBarColor, + backgroundColor: Colors.transparent, ); } diff --git a/lib/screens/common_widgets/app_bar_custom.dart b/lib/screens/common_widgets/app_bar_custom.dart new file mode 100644 index 00000000..655d2baf --- /dev/null +++ b/lib/screens/common_widgets/app_bar_custom.dart @@ -0,0 +1,92 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class AppBarCustom extends StatelessWidget implements PreferredSizeWidget { + final String? title; + final String? description; + final Widget? actionCustom; + final double? marginRightAction; + final double? height; + final bool isContent; + final Widget? suffixIcon; + final TextStyle? titleStyle; + + const AppBarCustom({ + Key? key, + this.title, + this.actionCustom, + this.marginRightAction, + this.height, + this.description, + this.isContent = false, + this.suffixIcon, + this.titleStyle, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + color: ColorConstants.background, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(left: 30, right: 16), + child: SvgPicture.asset( + AppVectors.appIcon, + color: Colors.black, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Expanded( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + title ?? '', + textAlign: TextAlign.left, + style: titleStyle ?? + TextStyle( + fontSize: 20.toFont, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox(width: 12), + Text( + description ?? '', + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 15.toFont, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + suffixIcon ?? SizedBox(), + ], + ), + ), + ), + ], + ), + ); + } + + @override + Size get preferredSize => Size.fromHeight(height ?? 130); +} diff --git a/lib/screens/common_widgets/avatar_widget.dart b/lib/screens/common_widgets/avatar_widget.dart new file mode 100644 index 00000000..23797eb1 --- /dev/null +++ b/lib/screens/common_widgets/avatar_widget.dart @@ -0,0 +1,74 @@ +import 'dart:typed_data'; + +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_flutter/widgets/custom_circle_avatar.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; +import 'package:flutter/material.dart'; + +class AvatarWidget extends StatefulWidget { + final AtContact contact; + final double? borderRadius; + final double size; + + const AvatarWidget({ + Key? key, + this.size = 40, + this.borderRadius, + required this.contact, + }) : super(key: key); + + @override + State createState() => _AvatarWidgetState(); +} + +class _AvatarWidgetState extends State { + String contactName = 'UG'; + Uint8List? image; + + @override + void initState() { + getNameAndImage(); + super.initState(); + } + + void getNameAndImage() { + try { + contactName = widget.contact.atSign ?? 'UG'; + + if (contactName[0] == '@') { + contactName = contactName.substring(1); + } + + if (widget.contact.tags != null && + widget.contact.tags?['image'] != null) { + List intList = widget.contact.tags!['image'].cast(); + image = Uint8List.fromList(intList); + } + } catch (e) { + contactName = 'UG'; + print('Error in getting image $e'); + } + } + + @override + Widget build(BuildContext context) { + return Container( + height: widget.size, + width: widget.size, + decoration: const BoxDecoration( + color: Colors.black, + shape: BoxShape.circle, + ), + child: image != null + ? CustomCircleAvatar( + byteImage: image, + nonAsset: true, + ) + : ContactInitial( + borderRadius: widget.borderRadius, + size: widget.size, + initials: contactName, + ), + ); + } +} diff --git a/lib/screens/common_widgets/card_widget.dart b/lib/screens/common_widgets/card_widget.dart new file mode 100644 index 00000000..72ef70e7 --- /dev/null +++ b/lib/screens/common_widgets/card_widget.dart @@ -0,0 +1,55 @@ +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class CardButton extends StatelessWidget { + final String icon; + final String title; + final TextStyle? style; + final Function()? onTap; + final Color? backgroundColor; + final Color? borderColor; + + const CardButton({ + Key? key, + required this.icon, + required this.title, + this.style, + this.onTap, + this.backgroundColor, + this.borderColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + height: 62, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: backgroundColor ?? ColorConstants.lightGrey, + border: Border.all( + color: borderColor ?? ColorConstants.grey, + ), + ), + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + SvgPicture.asset(icon), + const SizedBox(width: 8), + Text( + title, + style: style ?? + TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: ColorConstants.grey, + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/screens/common_widgets/contact_initial.dart b/lib/screens/common_widgets/contact_initial.dart index f09501c6..4b048f32 100644 --- a/lib/screens/common_widgets/contact_initial.dart +++ b/lib/screens/common_widgets/contact_initial.dart @@ -4,26 +4,28 @@ import 'package:flutter/material.dart'; import 'package:at_common_flutter/services/size_config.dart'; class ContactInitial extends StatelessWidget { - final double? size, maxSize, minSize; + final double? size, maxSize, minSize, borderRadius; final String? initials; int? index; Color? background; - ContactInitial( - {Key? key, - this.size = 40, - required this.initials, - this.index, - this.background, - this.maxSize, - this.minSize}) - : super(key: key); + ContactInitial({ + Key? key, + this.size = 40, + required this.initials, + this.index, + this.background, + this.maxSize, + this.minSize, + this.borderRadius, + }) : super(key: key); + @override Widget build(BuildContext context) { if (initials!.length < 3) { index = initials!.length; } else { - index = 3; + index = 2; } return Container( @@ -33,11 +35,56 @@ class ContactInitial extends StatelessWidget { color: background ?? ContactInitialsColors.getColor(initials!), // borderRadius: BorderRadius.circular(size.toWidth), // color: ContactInitialsColors.getColor(initials), - borderRadius: BorderRadius.circular((size!.toFont)), + borderRadius: BorderRadius.circular(( borderRadius ?? size!.toFont)), + ), + child: Center( + child: Text( + initials!.substring(0, index).toUpperCase(), + style: CustomTextStyles.whiteBold(size: (size! ~/ 3)), + ), + ), + ); + } +} + +class ContactInitialV2 extends StatelessWidget { + final double? size, maxSize, minSize; + final String? initials; + final int? index; + final Color? background; + + ContactInitialV2({ + Key? key, + this.size = 40, + required this.initials, + this.index, + this.background, + this.maxSize, + this.minSize, + }) : super(key: key); + + int get startIndex => (index == 1) ? 0 : 1; + int get endIndex => (initials!.length < 3) ? initials!.length : 3; + + Widget build(BuildContext context) { + return Container( + height: size!.toFont, + width: size!.toFont, + decoration: BoxDecoration( + color: background ?? ContactInitialsColors.getColor(initials!), + borderRadius: BorderRadius.circular((size!.toFont * 0.2)), + boxShadow: [ + BoxShadow( + color: ColorConstants.light_grey, + spreadRadius: 1, + blurRadius: 10, + offset: Offset(0, 4), // changes position of shadow + ), + ], ), child: Center( child: Text( - initials!.substring((index == 1) ? 0 : 1, index).toUpperCase(), + initials!.substring(startIndex, endIndex).toUpperCase(), style: CustomTextStyles.whiteBold(size: (size! ~/ 3)), ), ), diff --git a/lib/screens/common_widgets/custom_circle_avatar.dart b/lib/screens/common_widgets/custom_circle_avatar.dart index 84fed56b..24f736ae 100644 --- a/lib/screens/common_widgets/custom_circle_avatar.dart +++ b/lib/screens/common_widgets/custom_circle_avatar.dart @@ -2,6 +2,7 @@ /// [size] is set to [50] as default import 'dart:typed_data'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:flutter/material.dart'; import 'package:at_common_flutter/services/size_config.dart'; @@ -46,3 +47,50 @@ class CustomCircleAvatar extends StatelessWidget { ); } } + +class CustomCircleAvatarV2 extends StatelessWidget { + final String? image; + final double size; + final bool nonAsset; + final Uint8List? byteImage; + + const CustomCircleAvatarV2({ + Key? key, + this.image, + this.size = 50, + this.nonAsset = false, + this.byteImage, + }) : super(key: key); + @override + Widget build(BuildContext context) { + return Container( + height: size.toFont, + width: size.toFont, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size.toWidth * 0.2), + image: DecorationImage( + image: nonAsset + ? Image.memory( + byteImage!, + errorBuilder: (BuildContext _context, _, __) { + return Container( + child: Icon( + Icons.image, + size: size.toFont, + ), + ); + }, + ).image + : AssetImage(image!), + ), + boxShadow: [ + BoxShadow( + color: ColorConstants.light_grey, + spreadRadius: 1, + blurRadius: 10, + offset: Offset(0, 4), // changes position of shadow + ), + ], + )); + } +} diff --git a/lib/screens/common_widgets/custom_onboarding.dart b/lib/screens/common_widgets/custom_onboarding.dart index 13d0dcdc..7d2a036a 100644 --- a/lib/screens/common_widgets/custom_onboarding.dart +++ b/lib/screens/common_widgets/custom_onboarding.dart @@ -73,7 +73,7 @@ class CustomOnboarding { await initServices(); getTransferData(); await _backendService.startMonitor(); - _backendService.setPeriodicFileHistoryRefresh(); + // _backendService.setPeriodicFileHistoryRefresh(); if (showLoader != null) { showLoader(false, ''); diff --git a/lib/screens/common_widgets/file_card.dart b/lib/screens/common_widgets/file_card.dart new file mode 100644 index 00000000..671141b4 --- /dev/null +++ b/lib/screens/common_widgets/file_card.dart @@ -0,0 +1,89 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/utils/app_utils.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class FileCard extends StatelessWidget { + final PlatformFile fileDetail; + final Function? deleteFunc; + final Function? onTap; + + FileCard({ + Key? key, + required this.fileDetail, + this.deleteFunc, + this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + onTap?.call(); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: ColorConstants.textBoxBg, + ), + ), + margin: EdgeInsets.only(bottom: 10.toHeight), + padding: EdgeInsets.fromLTRB( + 16.toWidth, + 12.toHeight, + 14.toWidth, + 12.toHeight, + ), + child: Row( + children: [ + SvgPicture.asset( + AppVectors.icFile, + ), + SizedBox(width: 6.toWidth), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + fileDetail.name, + style: TextStyle( + color: Colors.black, + fontSize: 12.toFont, + fontWeight: FontWeight.w500, + ), + ), + ), + Text( + AppUtils.getFileSizeString( + bytes: fileDetail.size.toDouble(), + decimals: 2, + ), + style: TextStyle( + fontSize: 9.toFont, + color: ColorConstants.sidebarTextUnselected, + ), + ), + ], + ), + ), + InkWell( + onTap: () { + deleteFunc?.call(); + }, + child: SvgPicture.asset( + AppVectors.icClose, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/common_widgets/gradient_button.dart b/lib/screens/common_widgets/gradient_button.dart new file mode 100644 index 00000000..fc24ac07 --- /dev/null +++ b/lib/screens/common_widgets/gradient_button.dart @@ -0,0 +1,56 @@ +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class GradientButton extends StatelessWidget { + final double? width; + final double? height; + final VoidCallback? onPressed; + final Widget child; + final double radius; + + const GradientButton({ + Key? key, + required this.onPressed, + required this.child, + this.width, + this.radius = 8, + this.height, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: width, + height: height, + margin: EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + ColorConstants.orangeColor, + ColorConstants.yellow, + ], + ), + borderRadius: BorderRadius.circular(radius), + boxShadow: [ + BoxShadow( + color: ColorConstants.light_grey, + spreadRadius: 0, + blurRadius: 2, + offset: Offset(1, 2), // changes position of shadow + ), + ], + ), + child: ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius), + ), + ), + child: child, + ), + ); + } +} diff --git a/lib/screens/common_widgets/gradient_outline_input_border.dart b/lib/screens/common_widgets/gradient_outline_input_border.dart new file mode 100644 index 00000000..280532cd --- /dev/null +++ b/lib/screens/common_widgets/gradient_outline_input_border.dart @@ -0,0 +1,176 @@ +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class GradientOutlineInputBorder extends InputBorder { + const GradientOutlineInputBorder({ + required this.gradient, + this.width = 1.0, + this.gapPadding = 4.0, + this.borderRadius = const BorderRadius.all(Radius.circular(4)), + }); + + final double width; + + final BorderRadius borderRadius; + + final Gradient gradient; + + final double gapPadding; + + @override + InputBorder copyWith({BorderSide? borderSide}) { + return this; + } + + @override + bool get isOutline => true; + + @override + EdgeInsetsGeometry get dimensions => EdgeInsets.all(width); + + @override + Path getInnerPath(Rect rect, {TextDirection? textDirection}) { + return Path() + ..addRRect( + borderRadius + .resolve(textDirection) + .toRRect(rect) + .deflate(borderSide.width), + ); + } + + @override + Path getOuterPath(Rect rect, {TextDirection? textDirection}) { + return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); + } + + @override + void paint( + Canvas canvas, + Rect rect, { + double? gapStart, + double gapExtent = 0.0, + double gapPercentage = 0.0, + TextDirection? textDirection, + }) { + final paint = _getPaint(rect); + final outer = borderRadius.toRRect(rect); + final center = outer.deflate(borderSide.width / 2.0); + if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) { + canvas.drawRRect(center, paint); + } else { + final extent = + lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage)!; + switch (textDirection!) { + case TextDirection.rtl: + final path = _gapBorderPath( + canvas, + center, + math.max(0, gapStart + gapPadding - extent), + extent, + ); + canvas.drawPath(path, paint); + break; + + case TextDirection.ltr: + final path = _gapBorderPath( + canvas, + center, + math.max(0, gapStart - gapPadding), + extent, + ); + canvas.drawPath(path, paint); + break; + } + } + } + + @override + ShapeBorder scale(double t) { + return GradientOutlineInputBorder( + width: width * t, + borderRadius: borderRadius * t, + gradient: gradient, + ); + } + + Paint _getPaint(Rect rect) { + return Paint() + ..strokeWidth = width + ..shader = gradient.createShader(rect) + ..style = PaintingStyle.stroke; + } + + Path _gapBorderPath( + Canvas canvas, + RRect center, + double start, + double extent, + ) { + // When the corner radii on any side add up to be greater than the + // given height, each radius has to be scaled to not exceed the + // size of the width/height of the RRect. + final scaledRRect = center.scaleRadii(); + + final tlCorner = Rect.fromLTWH( + scaledRRect.left, + scaledRRect.top, + scaledRRect.tlRadiusX * 2.0, + scaledRRect.tlRadiusY * 2.0, + ); + final trCorner = Rect.fromLTWH( + scaledRRect.right - scaledRRect.trRadiusX * 2.0, + scaledRRect.top, + scaledRRect.trRadiusX * 2.0, + scaledRRect.trRadiusY * 2.0, + ); + final brCorner = Rect.fromLTWH( + scaledRRect.right - scaledRRect.brRadiusX * 2.0, + scaledRRect.bottom - scaledRRect.brRadiusY * 2.0, + scaledRRect.brRadiusX * 2.0, + scaledRRect.brRadiusY * 2.0, + ); + final blCorner = Rect.fromLTWH( + scaledRRect.left, + scaledRRect.bottom - scaledRRect.blRadiusY * 2.0, + scaledRRect.blRadiusX * 2.0, + scaledRRect.blRadiusX * 2.0, + ); + + const cornerArcSweep = math.pi / 2.0; + final tlCornerArcSweep = start < scaledRRect.tlRadiusX + ? math.asin((start / scaledRRect.tlRadiusX).clamp(-1.0, 1.0)) + : math.pi / 2.0; + + final path = Path() + ..addArc(tlCorner, math.pi, tlCornerArcSweep) + ..moveTo(scaledRRect.left + scaledRRect.tlRadiusX, scaledRRect.top); + + if (start > scaledRRect.tlRadiusX) { + path.lineTo(scaledRRect.left + start, scaledRRect.top); + } + + const trCornerArcStart = (3 * math.pi) / 2.0; + const trCornerArcSweep = cornerArcSweep; + if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) { + path + ..relativeMoveTo(extent, 0) + ..lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top) + ..addArc(trCorner, trCornerArcStart, trCornerArcSweep); + } else if (start + extent < scaledRRect.width) { + final dx = scaledRRect.width - (start + extent); + final sweep = math.acos(dx / scaledRRect.trRadiusX); + path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep); + } + + return path + ..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY) + ..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY) + ..addArc(brCorner, 0, cornerArcSweep) + ..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom) + ..addArc(blCorner, math.pi / 2.0, cornerArcSweep) + ..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY); + } +} \ No newline at end of file diff --git a/lib/screens/common_widgets/gradient_text_field_widget.dart b/lib/screens/common_widgets/gradient_text_field_widget.dart new file mode 100644 index 00000000..09fd88e5 --- /dev/null +++ b/lib/screens/common_widgets/gradient_text_field_widget.dart @@ -0,0 +1,82 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/gradient_outline_input_border.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class GradientTextFieldWidget extends StatefulWidget { + final String? hintText; + final TextStyle? hintTextStyle; + final TextEditingController? controller; + final Function(String value)? onchange; + final Function(String value)? onSubmitted; + final String? prefixText; + final TextStyle? prefixStyle; + + const GradientTextFieldWidget({ + Key? key, + this.hintText, + this.controller, + this.hintTextStyle, + this.onchange, + this.onSubmitted, + this.prefixText, + this.prefixStyle, + }) : super(key: key); + + @override + State createState() => + _GradientTextFieldWidgetState(); +} + +class _GradientTextFieldWidgetState extends State { + @override + Widget build(BuildContext context) { + return Container( + height: 52, + alignment: Alignment.centerLeft, + child: TextFormField( + controller: widget.controller, + onChanged: (value) { + widget.onchange?.call(value); + }, + onFieldSubmitted: (value) { + widget.onSubmitted?.call(value); + }, + style: TextStyle( + fontSize: 14.toFont, + ), + decoration: InputDecoration( + prefixText: widget.prefixText, + prefixStyle: widget.prefixStyle, + border: GradientOutlineInputBorder( + gradient: LinearGradient( + colors: [ + ColorConstants.orangeColor, + ColorConstants.yellow.withOpacity(0.65), + ], + ), + width: 2, + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: GradientOutlineInputBorder( + gradient: LinearGradient( + colors: [ + ColorConstants.orangeColor, + ColorConstants.yellow.withOpacity(0.65), + ], + ), + width: 2, + borderRadius: BorderRadius.circular(10), + ), + hintText: widget.hintText, + hintStyle: widget.hintTextStyle ?? + TextStyle( + fontSize: 12.toFont, + fontWeight: FontWeight.w400, + color: ColorConstants.grey, + ), + ), + ), + ); + } +} diff --git a/lib/screens/common_widgets/header_widget.dart b/lib/screens/common_widgets/header_widget.dart new file mode 100644 index 00000000..b54f23d1 --- /dev/null +++ b/lib/screens/common_widgets/header_widget.dart @@ -0,0 +1,154 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class HeaderWidget extends StatefulWidget { + final Function()? onReloadCallback; + final TextEditingController? controller; + final Function(String)? onSearch; + final EdgeInsetsGeometry? margin; + + const HeaderWidget({ + Key? key, + this.onReloadCallback, + this.controller, + this.onSearch, + this.margin, + }) : super(key: key); + + @override + State createState() => _HeaderWidgetState(); +} + +class _HeaderWidgetState extends State { + bool isSearch = false; + + @override + Widget build(BuildContext context) { + return Container( + margin: widget.margin ?? const EdgeInsets.symmetric(horizontal: 28), + padding: const EdgeInsets.fromLTRB(14, 11, 8, 14), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: ColorConstants.textBoxBg, + ), + child: Row( + children: [ + _buildButton( + title: "Refresh", + icon: AppVectors.icReload, + onTap: widget.onReloadCallback, + ), + const SizedBox(width: 24), + Expanded( + child: _buildSearchWidget(), + ), + ], + ), + ); + } + + Widget _buildButton({ + String? title, + required String icon, + Function()? onTap, + }) { + return InkWell( + onTap: () { + onTap?.call(); + }, + child: Column( + children: [ + Text( + title ?? '', + style: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w600, + color: ColorConstants.sidebarTextUnselected, + ), + ), + const SizedBox(height: 5), + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: ColorConstants.grey, + ), + color: Colors.white, + ), + child: Center( + child: SvgPicture.asset( + icon, + color: ColorConstants.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildSearchWidget() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Search", + style: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w600, + color: ColorConstants.sidebarTextUnselected, + ), + ), + const SizedBox(height: 5), + Container( + height: 48, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + border: Border.all( + color: ColorConstants.grey, + ), + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.only(left: 6, right: 8), + child: Row( + children: [ + Expanded( + child: TextField( + controller: widget.controller, + style: TextStyle( + fontSize: 14.toFont, + ), + decoration: InputDecoration.collapsed( + hintText: 'Search History by atSign', + hintStyle: TextStyle( + color: ColorConstants.grey, + fontSize: 14.toFont, + fontWeight: FontWeight.w500, + fontStyle: FontStyle.italic, + ), + ), + onChanged: widget.onSearch, + ), + ), + SizedBox(width: 4), + SizedBox( + width: 20, + height: 20, + child: SvgPicture.asset( + AppVectors.icSearch, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/screens/common_widgets/input_widget.dart b/lib/screens/common_widgets/input_widget.dart new file mode 100644 index 00000000..ea6b8457 --- /dev/null +++ b/lib/screens/common_widgets/input_widget.dart @@ -0,0 +1,79 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/gradient_outline_input_border.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class InputWidget extends StatefulWidget { + final String? hintText; + final TextStyle? hintTextStyle; + final TextEditingController? controller; + final Function(String value)? onchange; + final Function(String value)? onSubmitted; + final String? prefixText; + final TextStyle? prefixStyle; + + const InputWidget({ + Key? key, + this.hintText, + this.controller, + this.hintTextStyle, + this.onchange, + this.onSubmitted, + this.prefixText, + this.prefixStyle, + }) : super(key: key); + + @override + State createState() => _InputWidgetState(); +} + +class _InputWidgetState extends State { + @override + Widget build(BuildContext context) { + return Container( + height: 59.toHeight, + alignment: Alignment.centerLeft, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: TextFormField( + controller: widget.controller, + onChanged: (value) { + widget.onchange?.call(value); + }, + onFieldSubmitted: (value) { + widget.onSubmitted?.call(value); + }, + style: TextStyle( + fontSize: 14.toFont, + ), + decoration: InputDecoration( + prefixText: widget.prefixText, + prefixStyle: widget.prefixStyle, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + width: 1, + color: Colors.white, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + width: 1, + color: Colors.white, + ), + ), + hintText: widget.hintText, + hintStyle: widget.hintTextStyle ?? + TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w400, + color: ColorConstants.grey, + ), + ), + ), + ); + } +} diff --git a/lib/screens/common_widgets/labelled_circular_progress.dart b/lib/screens/common_widgets/labelled_circular_progress.dart index 19db0e0c..de80a6f1 100644 --- a/lib/screens/common_widgets/labelled_circular_progress.dart +++ b/lib/screens/common_widgets/labelled_circular_progress.dart @@ -1,3 +1,4 @@ +import 'package:at_common_flutter/services/size_config.dart'; import 'package:flutter/material.dart'; import '../../utils/colors.dart'; @@ -10,7 +11,12 @@ class LabelledCircularProgressIndicator extends StatelessWidget { return SizedBox( child: Stack( children: [ - CircularProgressIndicator(value: value), + CircularProgressIndicator( + value: value, + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ), value != null ? Positioned( top: 10, @@ -18,7 +24,7 @@ class LabelledCircularProgressIndicator extends StatelessWidget { padding: EdgeInsets.only(left: 7), child: Text((value! * 100).toStringAsFixed(0) + '%', style: TextStyle( - fontSize: 10, + fontSize: 8.toFont, fontWeight: FontWeight.bold, color: ColorConstants.blueText, )), diff --git a/lib/screens/common_widgets/linear_progress_bar.dart b/lib/screens/common_widgets/linear_progress_bar.dart new file mode 100644 index 00000000..f2ebb704 --- /dev/null +++ b/lib/screens/common_widgets/linear_progress_bar.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; + +/// Instantly animating progress bar. +/// Requires [duration] to set filling duration timer +/// [color] or [gradient] to fill the progress bar. Only one parameter is allowed. +/// Optional [backgroundColor], defaults to transparent +/// Optional [width] defaults to 200.0 +/// Optional [height] defaults to 10.0 +/// Optional [curve] defaults to [Curves.linear] + +const int _kIndeterminateLinearDuration = 1800; + +class ProgressBarAnimation extends StatefulWidget { + const ProgressBarAnimation({ + Key? key, + this.width = double.infinity, + this.height = 10.0, + this.color, + this.gradient, + this.backgroundColor = Colors.transparent, + this.curve = Curves.linear, + this.value, + }) : super(key: key); + + final double? value; + + ///progress bar width + final double width; + + ///progress bar height + final double height; + + ///progress bar color + final Color? color; + + ///progress bar gradient + final Gradient? gradient; + + ///progress bar backgroundColor + final Color backgroundColor; + + ///progress bar animation curve + final Curve curve; + + @override + State createState() => _ProgressBarAnimationState(); +} + +class _ProgressBarAnimationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: Duration(seconds: 3), + ); + + _controller.repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller.view, + builder: (context, _) { + return CustomPaint( + size: Size(widget.width, widget.height), + foregroundPainter: ProgressPainter( + value: widget.value, + color: widget.color, + gradient: widget.gradient, + animationValue: _controller.value, + ), + painter: BackgroundPainter( + backgroundColor: widget.backgroundColor, + ), + ); + }, + ); + } +} + +class BackgroundPainter extends CustomPainter { + const BackgroundPainter({required this.backgroundColor}); + + ///progress bar backgroundColor + final Color backgroundColor; + + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint()..color = backgroundColor; + canvas.drawRRect( + RRect.fromRectAndRadius( + Offset.zero & size, Radius.circular(size.height / 2)), + paint); + } + + @override + bool shouldRepaint(covariant BackgroundPainter oldDelegate) => false; +} + +class ProgressPainter extends CustomPainter { + const ProgressPainter({ + this.value, + this.gradient, + this.color, + required this.animationValue, + }); + + ///current progress bar value + final double? value; + + ///progress bar gradient infill + final Gradient? gradient; + + ///progress bar gradient color + final Color? color; + + final double animationValue; + + static const Curve line1Head = Interval( + 0.0, + 750.0 / _kIndeterminateLinearDuration, + curve: Cubic(0.2, 0.0, 0.8, 1.0), + ); + static const Curve line1Tail = Interval( + 333.0 / _kIndeterminateLinearDuration, + (333.0 + 750.0) / _kIndeterminateLinearDuration, + curve: Cubic(0.4, 0.0, 1.0, 1.0), + ); + static const Curve line2Head = Interval( + 1000.0 / _kIndeterminateLinearDuration, + (1000.0 + 567.0) / _kIndeterminateLinearDuration, + curve: Cubic(0.0, 0.0, 0.65, 1.0), + ); + static const Curve line2Tail = Interval( + 1267.0 / _kIndeterminateLinearDuration, + (1267.0 + 533.0) / _kIndeterminateLinearDuration, + curve: Cubic(0.10, 0.0, 0.45, 1.0), + ); + + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint(); + if (gradient != null) { + paint.shader = gradient?.createShader(Offset.zero & size); + } + + if (color != null) { + paint.color = color!; + } + + void drawBar(double x, double width) { + if (width <= 0.0) { + return; + } + + if (value != null) { + canvas.drawRRect( + RRect.fromRectAndCorners( + Offset(x, 0.0) & Size(width, size.height), + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + paint, + ); + } else { + canvas.drawRRect( + RRect.fromRectAndRadius( + Offset(x, 0.0) & Size(width, size.height), + Radius.circular(size.height / 2), + ), + paint, + ); + } + } + + if (value != null) { + drawBar(0.0, value!.clamp(0.0, 1.0) * size.width); + } else { + final double x1 = size.width * line1Tail.transform(animationValue); + final double width1 = + size.width * line1Head.transform(animationValue) - x1; + + final double x2 = size.width * line2Tail.transform(animationValue); + final double width2 = + size.width * line2Head.transform(animationValue) - x2; + + drawBar(x1, width1); + drawBar(x2, width2); + } + } + + @override + bool shouldRepaint(covariant ProgressPainter oldDelegate) { + return value != oldDelegate.value || + animationValue != oldDelegate.animationValue; + } +} diff --git a/lib/screens/common_widgets/loading_widget.dart b/lib/screens/common_widgets/loading_widget.dart index e3008060..053ba6ba 100644 --- a/lib/screens/common_widgets/loading_widget.dart +++ b/lib/screens/common_widgets/loading_widget.dart @@ -23,7 +23,11 @@ class LoadingDialog { return Center( child: (text != null) ? onlyText(text) - : CircularProgressIndicator(), + : CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ), ); }, barrierDismissible: false)) diff --git a/lib/screens/common_widgets/option_header_widget.dart b/lib/screens/common_widgets/option_header_widget.dart new file mode 100644 index 00000000..dc598af3 --- /dev/null +++ b/lib/screens/common_widgets/option_header_widget.dart @@ -0,0 +1,218 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class OptionHeaderWidget extends StatefulWidget { + final Function()? onReloadCallback; + final Function()? onSearchCallback; + final bool hideReloadIcon; + final Widget? filterWidget; + final TextEditingController? controller; + final Function(String)? onSearch; + final Function()? searchOffCallBack; + final EdgeInsetsGeometry? margin; + + OptionHeaderWidget({ + Key? key, + this.onReloadCallback, + this.onSearchCallback, + this.hideReloadIcon = true, + this.filterWidget, + this.controller, + this.onSearch, + this.margin, + this.searchOffCallBack, + }) : super(key: key); + + @override + State createState() => _OptionHeaderWidgetState(); +} + +class _OptionHeaderWidgetState extends State { + bool isSearch = false; + + @override + Widget build(BuildContext context) { + return Container( + margin: widget.margin ?? EdgeInsets.symmetric(horizontal: 28), + padding: EdgeInsets.fromLTRB(14, 11, 15, 14), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: ColorConstants.textBoxBg, + ), + child: isSearch + ? _buildSearchWidget() + : Row( + children: [ + Visibility( + visible: widget.hideReloadIcon, + child: _buildButton( + title: "Refresh", + icon: AppVectors.icReload, + onTap: widget.onReloadCallback, + ), + ), + SizedBox(width: 12), + _buildButton( + title: "Search", + icon: AppVectors.icSearch, + onTap: () { + setState(() { + isSearch = true; + }); + widget.onSearchCallback?.call(); + }, + ), + SizedBox(width: 15), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Delivery Type", + style: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w600, + color: ColorConstants.sidebarTextUnselected, + ), + ), + SizedBox(height: 5), + Container( + height: 48, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + border: Border.all( + color: ColorConstants.grey, + ), + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 18), + child: widget.filterWidget, + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildButton({ + String? title, + required String icon, + Function()? onTap, + }) { + return InkWell( + onTap: () { + onTap?.call(); + }, + child: Column( + children: [ + Text( + title ?? '', + style: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w600, + color: ColorConstants.sidebarTextUnselected, + ), + ), + SizedBox(height: 5), + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: ColorConstants.grey, + ), + ), + child: Center( + child: SvgPicture.asset( + icon, + color: ColorConstants.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildSearchWidget() { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Search", + style: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w600, + color: ColorConstants.sidebarTextUnselected, + ), + ), + SizedBox(height: 5), + Container( + height: 48, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + border: Border.all( + color: ColorConstants.grey, + ), + // color: Colors.green, + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.only(left: 6, right: 8), + child: Row( + children: [ + Expanded( + child: TextField( + controller: widget.controller, + style: TextStyle( + fontSize: 14.toFont, + color: Colors.black, + ), + decoration: InputDecoration.collapsed( + hintText: 'Search History by atSign', + hintStyle: TextStyle( + color: ColorConstants.sidebarTextUnselected, + fontSize: 14.toFont, + fontWeight: FontWeight.w500, + fontStyle: FontStyle.italic, + ), + ), + onChanged: widget.onSearch, + ), + ), + SizedBox( + width: 20, + height: 20, + child: SvgPicture.asset( + AppVectors.icSearch, + ), + ), + ], + ), + ), + ], + ), + ), + _buildButton( + onTap: () { + setState(() { + isSearch = false; + }); + widget.searchOffCallBack?.call(); + }, + icon: AppVectors.icCancel, + ) + ], + ); + } +} diff --git a/lib/screens/common_widgets/provider_handler.dart b/lib/screens/common_widgets/provider_handler.dart index dbf6e93e..d5184da4 100644 --- a/lib/screens/common_widgets/provider_handler.dart +++ b/lib/screens/common_widgets/provider_handler.dart @@ -4,6 +4,7 @@ /// [Status.Loading] renders a CircularProgressIndicator whereas /// [Status.Error] renders [errorBuilder] import 'package:atsign_atmosphere_pro/screens/common_widgets/error_dialog.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -32,7 +33,11 @@ class ProviderHandler extends StatelessWidget { child: Container( height: 50.toHeight, width: 50.toHeight, - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ), ), ); } else if (_provider.status[functionName!] == Status.Error) { @@ -56,7 +61,11 @@ class ProviderHandler extends StatelessWidget { child: Container( height: 50.toHeight, width: 50.toHeight, - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ), ), ); } diff --git a/lib/screens/common_widgets/search_widget.dart b/lib/screens/common_widgets/search_widget.dart new file mode 100644 index 00000000..caad7be7 --- /dev/null +++ b/lib/screens/common_widgets/search_widget.dart @@ -0,0 +1,97 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:flutter/material.dart'; +import '../../utils/colors.dart'; + +class SearchWidget extends StatelessWidget { + final Color? backgroundColor, borderColor; + final TextEditingController controller; + final String? hintText; + final TextStyle? hintStyle; + final EdgeInsetsGeometry? margin; + final Function(String value)? onChange; + final bool? readOnly; + final Function()? onTap; + final bool? autoFocus; + + const SearchWidget({ + Key? key, + this.backgroundColor, + this.borderColor, + required this.controller, + this.hintText, + this.hintStyle, + this.margin, + this.onChange, + this.readOnly, this.onTap, this.autoFocus, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 44.toHeight, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: backgroundColor, + ), + margin: margin ?? + EdgeInsets.symmetric( + horizontal: 32.toWidth, + vertical: 18.toHeight, + ), + child: TextFormField( + controller: controller, + autofocus: autoFocus ?? false, + readOnly: readOnly ?? false, + onTap: onTap, + onChanged: (value) { + onChange?.call(value); + }, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: borderColor ?? ColorConstants.grey, + ), + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: borderColor ?? ColorConstants.grey, + ), + borderRadius: BorderRadius.circular(10), + ), + contentPadding: const EdgeInsets.only(top: 12, left: 14), + hintStyle: hintStyle ?? + TextStyle( + fontSize: 14.toFont, + color: ColorConstants.grey, + fontWeight: FontWeight.normal, + ), + suffixIcon: controller.text.isEmpty + ? Icon( + Icons.search, + color: ColorConstants.darkSliver, + ) + : InkWell( + onTap: () { + controller.clear(); + onChange?.call(''); + }, + child: Icon( + Icons.close, + color: ColorConstants.darkSliver, + ), + ), + hintText: hintText ?? 'Search by atSign or nickname', + ), + textInputAction: TextInputAction.search, + style: TextStyle( + fontSize: 14.toFont, + color: ColorConstants.fontPrimary, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} diff --git a/lib/screens/common_widgets/side_bar.dart b/lib/screens/common_widgets/side_bar.dart index 08d5d2e1..79f29bee 100644 --- a/lib/screens/common_widgets/side_bar.dart +++ b/lib/screens/common_widgets/side_bar.dart @@ -1,33 +1,28 @@ -import 'dart:io'; import 'dart:typed_data'; import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_common_flutter/services/size_config.dart'; import 'package:at_contact/at_contact.dart'; import 'package:at_contacts_flutter/utils/init_contacts_service.dart'; -import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; -import 'package:atsign_atmosphere_pro/routes/route_names.dart'; -import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; -import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; -import 'package:atsign_atmosphere_pro/view_models/file_download_checker.dart'; -import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; import 'package:atsign_atmosphere_pro/routes/route_names.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/side_bar_backup_item.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/side_bar_list_item.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/switch_at_sign.dart'; import 'package:atsign_atmosphere_pro/services/backend_service.dart'; +import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; -import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/utils/constants.dart'; import 'package:atsign_atmosphere_pro/utils/images.dart'; import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; +import 'package:atsign_atmosphere_pro/view_models/file_download_checker.dart'; import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; class SideBarWidget extends StatefulWidget { @@ -48,7 +43,8 @@ class _SideBarWidgetState extends State { TextStrings().sidebarTermsAndConditions, TextStrings().sidebarPrivacyPolicy, TextStrings().sidebarFaqs, - TextStrings().sidebarTrustedSenders + TextStrings().sidebarTrustedSenders, + TextStrings().sidebarSettings, ]; final List menuItemsIcons = [ @@ -62,6 +58,7 @@ class _SideBarWidgetState extends State { ImageConstants.faqsIcon, ImageConstants.trustedSendersIcon, ImageConstants.trustedSender, + ImageConstants.settings, ]; final List targetScreens = [ @@ -73,7 +70,8 @@ class _SideBarWidgetState extends State { Routes.WEBSITE_SCREEN, Routes.WEBSITE_SCREEN, Routes.FAQ_SCREEN, - Routes.TRUSTED_CONTACTS + Routes.TRUSTED_CONTACTS, + Routes.SETTINGS, ]; String? activeAtSign; Uint8List? image; @@ -358,6 +356,14 @@ class _SideBarWidgetState extends State { showIconOnly: !isExpanded, ), SizedBox(height: isTablet ? 20.toHeight : 0), + SideBarItem( + isScale: true, + image: menuItemsIcons[10], + title: menuItemsTitle[9], + routeName: targetScreens[9], + showIconOnly: !isExpanded, + ), + SizedBox(height: isTablet ? 20.toHeight : 0), InkWell( onTap: () async { CommonUtilityFunctions().showResetAtsignDialog(); diff --git a/lib/screens/common_widgets/sliver_grid_delegate.dart b/lib/screens/common_widgets/sliver_grid_delegate.dart new file mode 100644 index 00000000..d4b8d478 --- /dev/null +++ b/lib/screens/common_widgets/sliver_grid_delegate.dart @@ -0,0 +1,66 @@ +import 'package:flutter/rendering.dart'; + +class SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight + extends SliverGridDelegate { + /// Creates a delegate that makes grid layouts with a fixed number of tiles in + /// the cross axis. + /// + /// All of the arguments must not be null. The `mainAxisSpacing` and + /// `crossAxisSpacing` arguments must not be negative. The `crossAxisCount` + /// and `childAspectRatio` arguments must be greater than zero. + const SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight({ + required this.crossAxisCount, + this.mainAxisSpacing = 0.0, + this.crossAxisSpacing = 0.0, + this.height = 56.0, + }) : assert(crossAxisCount > 0), + assert(mainAxisSpacing >= 0), + assert(crossAxisSpacing >= 0), + assert(height > 0); + + /// The number of children in the cross axis. + final int crossAxisCount; + + /// The number of logical pixels between each child along the main axis. + final double mainAxisSpacing; + + /// The number of logical pixels between each child along the cross axis. + final double crossAxisSpacing; + + /// The height of the crossAxis. + final double height; + + bool _debugAssertIsValid() { + assert(crossAxisCount > 0); + assert(mainAxisSpacing >= 0.0); + assert(crossAxisSpacing >= 0.0); + assert(height > 0.0); + return true; + } + + @override + SliverGridLayout getLayout(SliverConstraints constraints) { + assert(_debugAssertIsValid()); + final double usableCrossAxisExtent = + constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1); + final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; + final double childMainAxisExtent = height; + return SliverGridRegularTileLayout( + crossAxisCount: crossAxisCount, + mainAxisStride: childMainAxisExtent + mainAxisSpacing, + crossAxisStride: childCrossAxisExtent + crossAxisSpacing, + childMainAxisExtent: childMainAxisExtent, + childCrossAxisExtent: childCrossAxisExtent, + reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), + ); + } + + @override + bool shouldRelayout( + SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight oldDelegate) { + return oldDelegate.crossAxisCount != crossAxisCount || + oldDelegate.mainAxisSpacing != mainAxisSpacing || + oldDelegate.crossAxisSpacing != crossAxisSpacing || + oldDelegate.height != height; + } +} diff --git a/lib/screens/contact_new_version/add_contact_screen.dart b/lib/screens/contact_new_version/add_contact_screen.dart new file mode 100644 index 00000000..02a4f7fe --- /dev/null +++ b/lib/screens/contact_new_version/add_contact_screen.dart @@ -0,0 +1,226 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/input_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/add_contact_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +class AddContactScreen extends StatefulWidget { + const AddContactScreen({Key? key}) : super(key: key); + + @override + State createState() => _AddContactScreenState(); +} + +class _AddContactScreenState extends State { + late TextEditingController atSignController; + late TextEditingController nicknameController; + late AddContactProvider addContactProvider, state; + + @override + void initState() { + addContactProvider = context.read(); + atSignController = TextEditingController(); + nicknameController = TextEditingController(); + super.initState(); + addContactProvider.initData(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (_c, provider, _) { + state = context.watch(); + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: Container( + margin: EdgeInsets.only(top: 60), + width: double.infinity, + decoration: BoxDecoration( + color: ColorConstants.culturedColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + offset: const Offset(0, 4), + ) + ], + ), + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Padding( + padding: EdgeInsets.only(left: 31, top: 36), + child: SvgPicture.asset( + AppVectors.icBack, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 24, + horizontal: 38, + ), + child: Text( + "Add New Contact", + style: TextStyle( + fontSize: 20.toFont, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ), + Expanded( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 23), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InputWidget( + hintText: 'Enter atSign', + controller: atSignController, + prefixText: "@", + prefixStyle: TextStyle( + fontSize: 14.toFont, + color: Colors.black, + ), + onSubmitted: (value) async { + await state.checkValid(atSignController.text); + }, + ), + Visibility( + visible: state.atSignError.isNotEmpty, + child: Padding( + padding: const EdgeInsets.only(top: 6), + child: Text( + state.atSignError, + style: TextStyle( + color: Colors.red, + fontSize: 12.toFont, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + const SizedBox(height: 16), + InputWidget( + hintText: 'Enter nickname', + controller: nicknameController, + onSubmitted: (value) async { + await state.checkValid(atSignController.text); + }, + ), + const SizedBox(height: 30), + Container( + height: 1, + decoration: BoxDecoration( + color: ColorConstants.lightGray, + ), + ), + const SizedBox(height: 28), + Align( + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "atSign valid", + style: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 4), + Icon( + Icons.check_circle_outlined, + size: 23, + color: state.isVerify + ? Colors.green + : Colors.black, + ) + ], + ), + ), + ], + ), + ), + state.status['add_contact_status'] == Status.Loading + ? InkWell( + onTap: () {}, + child: SizedBox( + width: double.infinity, + height: double.infinity, + child: Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ), + ), + ), + ) + : SizedBox(), + ], + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(27, 0, 27, 16), + child: InkWell( + onTap: () async { + if (addContactProvider.isVerify) { + var response = await addContactProvider.addContact( + atSign: atSignController.text, + nickname: nicknameController.text, + ); + + if (response ?? false) { + Navigator.of(context).pop(true); + } + } + }, + child: Container( + height: 51.toHeight, + width: double.infinity, + decoration: BoxDecoration( + color: !state.isVerify + ? ColorConstants.buttonGrey + : Colors.black, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + "Add Contact", + style: TextStyle( + color: Colors.white, + fontSize: 16.toFont, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ) + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/screens/contact_new_version/blocked_contact_screen.dart b/lib/screens/contact_new_version/blocked_contact_screen.dart new file mode 100644 index 00000000..891312d5 --- /dev/null +++ b/lib/screens/contact_new_version/blocked_contact_screen.dart @@ -0,0 +1,294 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_flutter/models/contact_base_model.dart'; +import 'package:at_contacts_flutter/services/contact_service.dart'; +import 'package:at_contacts_flutter/utils/text_strings.dart'; +import 'package:at_contacts_flutter/utils/text_styles.dart'; +import 'package:at_contacts_group_flutter/widgets/confirmation_dialog.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/search_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/contact_card_widget.dart'; +import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class BlockedContactScreen extends StatefulWidget { + const BlockedContactScreen({Key? key}) : super(key: key); + + @override + State createState() => _BlockedContactScreenState(); +} + +class _BlockedContactScreenState extends State { + late ContactService _contactService; + late TextEditingController searchController; + bool isUnblocking = false; + + @override + void initState() { + super.initState(); + _contactService = ContactService(); + searchController = TextEditingController(); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await _contactService.fetchBlockContactList(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorConstants.background, + appBar: AppBar( + backgroundColor: ColorConstants.background, + title: Text( + "Blocked atSigns", + style: TextStyle( + color: Colors.black, + ), + ), + centerTitle: false, + ), + body: SafeArea( + child: Container( + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + SearchWidget( + controller: searchController, + borderColor: Colors.white, + backgroundColor: Colors.white, + hintText: "Search", + onChange: (value) { + setState(() {}); + }, + hintStyle: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.fromLTRB( + 44.toWidth, + 14.toHeight, + 29.toWidth, + 16, + ), + ), + Expanded( + child: StreamBuilder>( + stream: _contactService.blockedContactStream, + initialData: _contactService.baseBlockedList, + builder: (context, snapshot) { + if ((snapshot.connectionState == ConnectionState.waiting)) { + return const Center( + child: CircularProgressIndicator( + color: ColorConstants.orange, + ), + ); + } else { + var listContact = snapshot.data!; + listContact = listContact + .where( + (element) => (element?.contact?.atSign ?? '') + .contains(searchController.text), + ) + .toList(); + + if (listContact.isEmpty) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 122, + width: 226, + child: Image.asset( + ImageConstants.emptyBox, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 24, + ), + child: Text( + "Empty Contacts", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: ColorConstants.grey, + ), + ), + ), + ], + ), + ); + } + // renders contacts according to the initial alphabet + return Scrollbar( + radius: const Radius.circular(11), + child: RefreshIndicator( + color: ColorConstants.orange, + onRefresh: () async {}, + child: ListView.builder( + physics: const ClampingScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: 27, + shrinkWrap: true, + itemBuilder: (context, alphabetIndex) { + List contactsForAlphabet = []; + + var currentChar = + String.fromCharCode(alphabetIndex + 65) + .toUpperCase(); + + if (alphabetIndex == 26) { + currentChar = 'Others'; + } + + contactsForAlphabet = getContactsForAlphabets( + listContact, + currentChar, + alphabetIndex, + ); + + if (contactsForAlphabet.isEmpty) { + return const SizedBox(); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildChar(currentChar), + _buildBlockContacts(contactsForAlphabet) + ], + ); + }, + ), + ), + ); + } + }, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildBlockContacts( + List contactsForAlphabet, + ) { + return ListView.builder( + itemCount: contactsForAlphabet.length, + padding: EdgeInsets.only(left: 44, right: 28), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final contact = contactsForAlphabet[index].contact; + return ContactCardWidget( + contact: contact!, + onTap: () async { + print("unblock"); + await unblockAtsign( + contact, + ); + }, + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 8), + child: Icon( + Icons.block, + color: Colors.red, + ), + ), + ); + }, + ); + } + + Widget _buildChar(String currentChar) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 28), + child: Text( + currentChar, + style: TextStyle( + fontSize: 20.toFont, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 16.toWidth), + Expanded( + child: Divider( + color: ColorConstants.dividerGrey, + height: 1.toHeight, + ), + ), + SizedBox(width: 31.toWidth), + ], + ), + ); + } + + List getContactsForAlphabets( + List _filteredList, + String currentChar, + int alphabetIndex, + ) { + List contactsForAlphabet = []; + + /// contacts, groups that does not starts with alphabets + if (alphabetIndex == 26) { + for (var c in _filteredList) { + if (!RegExp(r'^[a-z]+$').hasMatch( + (c?.contact?.atSign?[1] ?? '').toLowerCase(), + )) { + contactsForAlphabet.add(c!); + } + } + } else { + for (var c in _filteredList) { + if (c?.contact != null) { + if (c?.contact?.atSign?[1].toUpperCase() == currentChar) { + contactsForAlphabet.add(c!); + } + } + } + } + + return contactsForAlphabet; + } + + Future unblockAtsign(AtContact atsign) async { + await showDialog( + context: context, + builder: (context) { + Uint8List? image; + if (atsign.tags != null && atsign.tags?['image'] != null) { + List intList = atsign.tags?['image'].cast(); + image = Uint8List.fromList(intList); + } + return ConfirmationDialog( + title: atsign.atSign!, + heading: 'Do you want to unblock this atSign?', + atsign: atsign.atSign, + image: image, + onYesPressed: () async { + await _contactService + .blockUnblockContact(contact: atsign, blockAction: false) + .then( + (value) => Navigator.pop(context), + ); + }, + ); + }, + ); + } +} diff --git a/lib/screens/contact_new_version/contact_detail_screen.dart b/lib/screens/contact_new_version/contact_detail_screen.dart new file mode 100644 index 00000000..d2c9bead --- /dev/null +++ b/lib/screens/contact_new_version/contact_detail_screen.dart @@ -0,0 +1,468 @@ +import 'package:at_common_flutter/at_common_flutter.dart'; +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_flutter/services/contact_service.dart'; +import 'package:at_contacts_group_flutter/models/group_contacts_model.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/avatar_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/contact_attachment_card.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/option_dialog.dart'; +import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/services/snackbar_service.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; +import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +import '../../data_models/file_transfer.dart'; +import '../common_widgets/provider_handler.dart'; + +class ContactDetailScreen extends StatefulWidget { + final AtContact contact; + final Function()? onTrustFunc; + + const ContactDetailScreen({ + Key? key, + required this.contact, + this.onTrustFunc, + }) : super(key: key); + + @override + State createState() => _ContactDetailScreenState(); +} + +class _ContactDetailScreenState extends State { + late TrustedContactProvider _trustedContactProvider; + late ContactService _contactService; + late WelcomeScreenProvider _welcomeScreenProvider; + late HistoryProvider historyProvider; + GlobalKey optionKey = GlobalKey(); + TextEditingController nicknameController = TextEditingController(); + bool isTrusted = false; + bool isLoading = false; + bool isEditNickname = false; + + @override + void initState() { + _trustedContactProvider = TrustedContactProvider(); + _welcomeScreenProvider = WelcomeScreenProvider(); + _contactService = ContactService(); + historyProvider = + Provider.of(NavService.navKey.currentContext!); + checkTrustedContact(); + nicknameController.text = widget.contact.tags?['nickname'] ?? ""; + super.initState(); + } + + void checkTrustedContact() { + _trustedContactProvider.trustedContacts.forEach((element) { + if (element.atSign == widget.contact.atSign) { + setState(() { + isTrusted = true; + }); + } + }); + } + + filterReceivedFiles(String atSign, List receivedFiles) { + var tempfiles = []; + + for (var file in receivedFiles) { + if (file.sender == atSign) { + tempfiles.add(file); + } + } + + return tempfiles; + } + + editNickname() async { + setState(() { + isLoading = true; + }); + AtContact contact = widget.contact; + contact.tags = + await _contactService.getContactDetails(contact.atSign, null); + contact.tags!['nickname'] = nicknameController.text; + var res = await _contactService.atContactImpl.add(contact); + if (res == true) { + await SnackbarService() + .showSnackbar(context, "Successfully updated nickname"); + } else { + await SnackbarService() + .showSnackbar(context, "Failed to update nickname"); + } + setState(() { + isEditNickname = false; + isLoading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorConstants.background, + appBar: AppBar( + backgroundColor: ColorConstants.background, + leading: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Padding( + padding: const EdgeInsets.only(left: 31), + child: Icon( + Icons.arrow_back_ios, + ), + ), + ), + actions: [ + _buildMoreIcon(), + ], + ), + body: SingleChildScrollView( + child: Container( + // height: double.infinity, + // width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 44), + margin: EdgeInsets.only(top: 8), + child: Row( + children: [ + AvatarWidget( + size: 100, + borderRadius: 50, + contact: widget.contact, + ), + const SizedBox(width: 22), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + isEditNickname + ? Text( + "Nickname", + style: TextStyle( + color: Colors.black, + fontSize: 8, + fontWeight: FontWeight.w500), + ) + : SizedBox(), + SizedBox(height: 4), + Flexible( + child: isEditNickname + ? Row( + children: [ + Flexible( + child: TextField( + maxLines: 1, + decoration: InputDecoration( + contentPadding: EdgeInsets.only( + left: 16, + ), + hintText: 'Enter Nickname', + hintStyle: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w500, + color: ColorConstants.textBlack, + ), + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(5), + borderSide: BorderSide.none, + ), + labelStyle: TextStyle( + fontSize: 14.toFont, + ), + fillColor: Colors.white, + filled: true, + suffixIcon: InkWell( + onTap: () { + nicknameController.clear(); + }, + child: Icon( + Icons.clear, + color: Colors.black, + size: 16, + ), + ), + ), + controller: nicknameController, + ), + ), + ], + ) + : Text( + widget.contact.tags?['nickname'] ?? + widget.contact.atSign!.substring(1), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(height: 8), + Flexible( + child: isEditNickname + ? _buildButtonIcon( + height: 36, + backgroundColor: Colors.black, + borderRadius: 5, + title: 'Save', + titleStyle: TextStyle( + color: Colors.white, + fontSize: 12.toFont, + fontWeight: FontWeight.bold, + ), + useLoadingIndicator: true, + onTap: () async { + await editNickname(); + }, + ) + : Text( + widget.contact.atSign ?? '', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ) + ], + ), + ), + const SizedBox(height: 25), + isTrusted + ? _buildButtonIcon( + title: "Trusted", + titleStyle: TextStyle( + color: Colors.white, + fontSize: 14.toFont, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.symmetric(horizontal: 44), + imageUrl: AppVectors.icWhiteTrust, + backgroundColor: ColorConstants.orange, + onTap: () async { + await _trustedContactProvider + .removeTrustedContacts(widget.contact); + setState(() { + isTrusted = false; + }); + }, + ) + : _buildButtonIcon( + title: "Add To Trusted", + titleStyle: TextStyle( + color: ColorConstants.portlandOrange, + fontSize: 14.toFont, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.symmetric(horizontal: 44), + imageUrl: AppVectors.icTrustActivated, + backgroundColor: ColorConstants.unbleachedSilk, + onTap: () async { + await _trustedContactProvider + .addTrustedContacts(widget.contact); + setState(() { + isTrusted = true; + }); + }, + ), + const SizedBox(height: 13), + _buildButtonIcon( + title: "Transfer File", + titleStyle: TextStyle( + color: Colors.white, + fontSize: 14.toFont, + fontWeight: FontWeight.bold, + ), + margin: EdgeInsets.symmetric(horizontal: 44), + imageUrl: AppVectors.icArrow, + backgroundColor: Colors.black, + onTap: () { + Navigator.of(context).pop(false); + widget.onTrustFunc?.call(); + _welcomeScreenProvider.selectedContacts = [ + GroupContactsModel( + contactType: ContactsType.CONTACT, + contact: widget.contact, + ), + ]; + _welcomeScreenProvider.changeBottomNavigationIndex(0); + }, + ), + Padding( + padding: EdgeInsets.only(top: 20, left: 38), + child: Text( + "Attachments", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 38), + child: Text( + "Files ${widget.contact.atSign ?? ''} has sent you", + style: TextStyle( + fontSize: 10.toFont, + fontWeight: FontWeight.w400, + color: ColorConstants.oldSliver, + ), + ), + ), + SizedBox(height: 20), + ProviderHandler( + functionName: historyProvider.RECEIVED_HISTORY, + showError: false, + successBuilder: (provider) { + var files = filterReceivedFiles(widget.contact.atSign ?? "", + provider.receivedHistoryLogs); + + return ListView.builder( + shrinkWrap: true, + itemCount: files.length, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + List list = []; + for (var file in files[index].files) { + print("path: ${file.path}"); + + list.add( + ContactAttachmentCard( + fileTransfer: files[index], + singleFile: file, + fromContact: true, + ), + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: list, + ); + }, + ); + }, + // errorBuilder: (provider) => Center( + // child: Text(TextStrings().errorOccured), + // ), + load: (provider) async { + await provider.getReceivedHistory(); + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildButtonIcon({ + String title = '', + TextStyle? titleStyle, + String? imageUrl, + Color backgroundColor = Colors.black, + EdgeInsetsGeometry? margin, + Function()? onTap, + double height = 51, + double borderRadius = 10, + bool useLoadingIndicator = false, + }) { + return Container( + height: height, + width: double.infinity, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderRadius), + ), + margin: margin, + child: InkWell( + onTap: onTap, + child: Center( + child: useLoadingIndicator && isLoading + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + style: titleStyle ?? + TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + if (imageUrl != null) const SizedBox(width: 14), + if (imageUrl != null) + SvgPicture.asset( + imageUrl, + ), + ], + ), + ), + ), + ); + } + + Widget _buildMoreIcon() { + return InkWell( + onTap: () { + RenderBox box = + optionKey.currentContext!.findRenderObject() as RenderBox; + Offset position = box.localToGlobal(Offset.zero); + showDialog( + context: context, + builder: (BuildContext contextDialog) { + return OptionDialog( + position: position, + editNickNameFunc: () { + setState(() { + isEditNickname = true; + }); + }, + blockFunc: () async { + await _contactService.blockUnblockContact( + contact: widget.contact, + blockAction: true, + ); + Navigator.of(context).pop(); + }, + deleteFunc: () async { + await _contactService.deleteAtSign( + atSign: widget.contact.atSign!, + ); + Navigator.of(context).pop(); + }, + ); + }, + ); + }, + child: Padding( + key: optionKey, + padding: EdgeInsets.only(right: 32), + child: Icon( + Icons.more_horiz, + ), + ), + ); + } +} diff --git a/lib/screens/contact_new_version/contact_screen.dart b/lib/screens/contact_new_version/contact_screen.dart new file mode 100644 index 00000000..994f4dae --- /dev/null +++ b/lib/screens/contact_new_version/contact_screen.dart @@ -0,0 +1,309 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:at_contacts_group_flutter/screens/group_view/group_view.dart'; +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/contact_type.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/app_bar_custom.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/search_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/add_contact_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/contact_detail_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/create_group_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/list_contact_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/view_models/create_group_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class ContactScreen extends StatefulWidget { + const ContactScreen({Key? key}) : super(key: key); + + @override + State createState() => _ContactScreenState(); +} + +class _ContactScreenState extends State + with SingleTickerProviderStateMixin { + late TrustedContactProvider trustedProvider; + late CreateGroupProvider createGroupProvider; + late GroupService _groupService; + late TabController _tabController; + late TextEditingController searchController; + int indexTab = 0; + + @override + void initState() { + trustedProvider = context.read(); + createGroupProvider = context.read(); + _groupService = GroupService(); + _tabController = TabController(length: 3, initialIndex: 0, vsync: this); + searchController = TextEditingController(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorConstants.background, + resizeToAvoidBottomInset: false, + appBar: AppBarCustom( + title: "Contacts", + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 30), + child: InkWell( + onTap: () async { + if (indexTab == 2) { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CreateGroupScreen( + trustContacts: trustedProvider.trustedContacts, + ); + }, + ); + if (createGroupProvider.selectedImageByteData != null) { + createGroupProvider.removeSelectedImage(); + } + if (result ?? false) { + await _groupService.fetchGroupsAndContacts(); + setState(() {}); + } + } else { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return AddContactScreen(); + }, + ); + + if (result == true) { + await reloadPage(); + } + } + }, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.orange, + borderRadius: BorderRadius.circular(46), + ), + padding: EdgeInsets.symmetric( + horizontal: 13, + vertical: 8, + ), + child: Text( + indexTab == 2 ? "Add Group" : "Add Contact", + style: TextStyle( + color: Colors.white, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + body: buildBody(), + ); + } + + Widget buildBody() { + return InkWell( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + onTap: () { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + }, + child: Column( + children: [ + SearchWidget( + controller: searchController, + borderColor: Colors.white, + backgroundColor: Colors.white, + hintText: "Search", + hintStyle: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.fromLTRB( + 36.toWidth, + 24.toHeight, + 36.toWidth, + 0, + ), + onChange: (value) { + setState(() {}); + }, + ), + Container( + height: 56.toHeight, + decoration: BoxDecoration( + color: ColorConstants.backgroundTab, + borderRadius: BorderRadius.circular(8), + ), + margin: EdgeInsets.symmetric( + horizontal: 36, + vertical: 16, + ), + child: TabBar( + controller: _tabController, + indicatorColor: Colors.transparent, + padding: EdgeInsets.symmetric( + horizontal: 13.toWidth, + vertical: 7.toHeight, + ), + labelPadding: EdgeInsets.zero, + physics: const ClampingScrollPhysics(), + tabs: [ + _buildTabBarItem(index: 0, currentIndex: indexTab), + _buildTabBarItem(index: 1, currentIndex: indexTab), + _buildTabBarItem(index: 2, currentIndex: indexTab), + ], + onTap: (index) { + setState(() { + indexTab = index; + }); + }, + ), + ), + Expanded( + child: TabBarView( + controller: _tabController, + physics: NeverScrollableScrollPhysics(), + children: [ + ListContactWidget( + contactsType: ListContactType.contact, + trustedContacts: trustedProvider.trustedContacts, + searchKeywords: searchController.text, + onTapContact: (contact) async { + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ContactDetailScreen( + contact: contact, + ), + ), + ); + + if (result != false) { + await reloadPage(); + } + }, + onTapAddButton: () async { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return AddContactScreen(); + }, + ); + if (result == true) { + await reloadPage(); + } + }, + ), + ListContactWidget( + contactsType: ListContactType.trusted, + trustedContacts: trustedProvider.trustedContacts, + searchKeywords: searchController.text, + onTapContact: (contact) async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ContactDetailScreen( + contact: contact, + onTrustFunc: () { + Navigator.of(context).pop(); + }, + ), + ), + ); + }, + ), + ListContactWidget( + contactsType: ListContactType.groups, + searchKeywords: searchController.text, + onTapGroup: (group) async { + WidgetsBinding.instance.addPostFrameCallback((_) async { + _groupService.groupViewSink.add(group); + }); + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GroupView( + group: group, + ), + ), + ); + }, + onTapAddButton: () async { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CreateGroupScreen( + trustContacts: trustedProvider.trustedContacts, + ); + }, + ); + if (createGroupProvider.selectedImageByteData != null) { + createGroupProvider.removeSelectedImage(); + } + if (result ?? false) { + await _groupService.fetchGroupsAndContacts(); + setState(() {}); + } + }, + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildTabBarItem({ + required int index, + required int currentIndex, + }) { + final bool isCurrentTab = index == currentIndex; + return Tab( + child: Container( + decoration: BoxDecoration( + color: isCurrentTab ? ColorConstants.yellow : Colors.transparent, + borderRadius: BorderRadius.circular(125), + ), + child: Center( + child: Text( + ListContactType.values[index].display, + style: TextStyle( + color: isCurrentTab ? Colors.white : Colors.black, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ); + } + + Future reloadPage() async { + await Future.delayed(Duration(milliseconds: 500), () async { + await _groupService.fetchGroupsAndContacts(); + setState(() {}); + }); + } +} diff --git a/lib/screens/contact_new_version/create_group_screen.dart b/lib/screens/contact_new_version/create_group_screen.dart new file mode 100644 index 00000000..6f96d46e --- /dev/null +++ b/lib/screens/contact_new_version/create_group_screen.dart @@ -0,0 +1,295 @@ +import 'package:at_common_flutter/at_common_flutter.dart'; +import 'package:at_commons/at_commons.dart'; +import 'package:at_contact/at_contact.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_toast.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/input_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/search_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/list_contact_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/create_group_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +class CreateGroupScreen extends StatefulWidget { + final List? trustContacts; + + const CreateGroupScreen({ + Key? key, + this.trustContacts, + }) : super(key: key); + + @override + State createState() => _CreateGroupScreenState(); +} + +class _CreateGroupScreenState extends State { + late TextEditingController groupNameController; + late TextEditingController searchController; + late CreateGroupProvider _provider; + + @override + void initState() { + groupNameController = TextEditingController(); + searchController = TextEditingController(); + _provider = context.read(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer(builder: (context, value, child) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Container( + margin: EdgeInsets.only(top: 60), + decoration: BoxDecoration( + color: ColorConstants.culturedColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + offset: const Offset(0, 4), + ) + ], + ), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + _provider.removeSelectedImage(); + Navigator.of(context).pop(); + }, + child: Padding( + padding: EdgeInsets.only(left: 31, top: 36), + child: SvgPicture.asset( + AppVectors.icBack, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 24, + left: 38, + bottom: 14, + ), + child: Text( + "Add New Group", + style: TextStyle( + fontSize: 20.toFont, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 27, + ), + child: InputWidget( + hintText: 'Group Name', + controller: groupNameController, + hintTextStyle: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w500, + color: ColorConstants.grey, + ), + onchange: (value) { + _provider.setGroupName(value); + }, + ), + ), + _buildImage(value.selectedImageByteData), + Padding( + padding: const EdgeInsets.only( + top: 22, + left: 31, + ), + child: Text( + "Select Members ${value.listContact.isNotEmpty ? value.listContact.length : ''}", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ), + SearchWidget( + controller: searchController, + borderColor: Colors.white, + backgroundColor: Colors.white, + hintText: "Search", + hintStyle: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.fromLTRB( + 28.toWidth, + 8.toHeight, + 28.toWidth, + 14.toHeight, + ), + onChange: (value) { + _provider.setSearchKeyword(value); + }, + ), + Flexible( + child: ListContactWidget( + searchKeywords: value.searchKeyword, + trustedContacts: widget.trustContacts, + isSelectMultiContacts: true, + onSelectContacts: (contacts) { + _provider.addGroupContacts(contacts); + }, + ), + ), + ], + ), + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 16, + ), + child: InkWell( + onTap: () { + _provider.createGroup( + whenComplete: (result) { + if (result is AtGroup) { + if (!mounted) return; + Navigator.of(context).pop(true); + } else if (result != null) { + if (result.runtimeType == + AlreadyExistsException) { + if (!mounted) return; + CustomToast().show( + TextStrings().groupAlreadyExists, + context); + } else if (result.runtimeType == + InvalidAtSignException) { + CustomToast().show(result.message, context); + } else { + if (!mounted) return; + CustomToast().show( + TextStrings().serviceError, context); + } + } else { + if (!mounted) return; + CustomToast() + .show(TextStrings().serviceError, context); + } + }, + whenNameIsEmpty: () { + if (!mounted) return; + CustomToast() + .show(TextStrings().groupEmptyName, context); + }, + ); + }, + child: Container( + height: 51.toHeight, + margin: const EdgeInsets.symmetric(horizontal: 27), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: value.groupName.isNotEmpty && + value.listContact.isNotEmpty + ? Colors.black + : ColorConstants.buttonGrey, + ), + child: const Center( + child: Text( + "Create Group", + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ), + ], + ), + value.isLoading + ? Align( + alignment: Alignment.center, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ), + ) + : const SizedBox(), + ], + ), + ), + ); + }); + } + + Widget _buildImage(Uint8List? selectedImage) { + return InkWell( + onTap: () async { + await _provider.selectCoverImage(); + }, + child: Container( + height: 89, + width: double.infinity, + margin: const EdgeInsets.fromLTRB(27, 14, 27, 0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Color(0xFFECECEC), + ), + child: selectedImage != null + ? ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.memory( + selectedImage, + fit: BoxFit.cover, + ), + ) + : Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Insert Cover Image", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: ColorConstants.grey, + ), + ), + SizedBox(height: 8), + Image.asset( + ImageConstants.icImage, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/contact_new_version/group_contact_screen.dart b/lib/screens/contact_new_version/group_contact_screen.dart new file mode 100644 index 00000000..f2ab23d1 --- /dev/null +++ b/lib/screens/contact_new_version/group_contact_screen.dart @@ -0,0 +1,197 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:at_contacts_group_flutter/screens/group_view/group_view.dart'; +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/contact_type.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/create_group_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/list_contact_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class GroupContactScreen extends StatefulWidget { + const GroupContactScreen({Key? key}) : super(key: key); + + @override + State createState() => _GroupContactScreenState(); +} + +class _GroupContactScreenState extends State { + late GroupService groupService; + late TrustedContactProvider trustedProvider; + + @override + void initState() { + groupService = GroupService(); + trustedProvider = context.read(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Align( + alignment: Alignment.bottomCenter, + child: Container( + height: MediaQuery.of(context).size.height - 120, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + offset: const Offset(0, 4), + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(27, 24, 27, 0), + child: Row( + children: [ + Container( + height: 2, + width: 45, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(20), + ), + ), + const Spacer(), + Align( + alignment: Alignment.topRight, + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + height: 31.toHeight, + alignment: Alignment.topRight, + padding: const EdgeInsets.symmetric( + horizontal: 30, + ), + decoration: BoxDecoration( + border: Border.all( + color: ColorConstants.grey, + ), + borderRadius: BorderRadius.circular(28), + ), + child: Center( + child: Text( + "Close", + style: TextStyle( + fontSize: 17.toFont, + fontWeight: FontWeight.w600, + color: ColorConstants.grey, + ), + ), + ), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 24), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 27), + child: Text( + "My Groups", + style: TextStyle( + fontSize: 25.toFont, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + const SizedBox(height: 30), + Expanded( + child: ListContactWidget( + contactsType: ListContactType.groups, + isShowAlpha: false, + onTapGroup: (group) async { + WidgetsBinding.instance + .addPostFrameCallback((_) async { + groupService.groupViewSink.add(group); + }); + + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GroupView( + group: group, + ), + ), + ); + }, + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.only(bottom: 24, top: 18), + child: InkWell( + onTap: () async { + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (_) => CreateGroupScreen( + trustContacts: + trustedProvider.trustedContacts, + ), + ), + ); + + if (result == true) { + await groupService.fetchGroupsAndContacts(); + setState(() {}); + } + }, + child: Container( + height: 67.toHeight, + margin: const EdgeInsets.symmetric(horizontal: 27), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + gradient: LinearGradient( + colors: [ + const Color(0xfff05e3f), + const Color(0xffeaa743).withOpacity(0.65), + ], + ), + ), + child: Center( + child: Text( + "Create Group", + style: TextStyle( + color: Colors.white, + fontSize: 20.toFont, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/contact_new_version/trusted_contact_screen.dart b/lib/screens/contact_new_version/trusted_contact_screen.dart new file mode 100644 index 00000000..510ab62a --- /dev/null +++ b/lib/screens/contact_new_version/trusted_contact_screen.dart @@ -0,0 +1,159 @@ +// import 'package:at_common_flutter/services/size_config.dart'; +// import 'package:at_contact/at_contact.dart'; +// import 'package:atsign_atmosphere_pro/screens/contact_new_version/contact_detail_screen.dart'; +// import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/list_contact_widget.dart'; +// import 'package:atsign_atmosphere_pro/utils/colors.dart'; +// import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; +// import 'package:flutter/material.dart'; +// import 'package:provider/provider.dart'; +// +// class TrustedContactScreen extends StatefulWidget { +// const TrustedContactScreen({Key? key}) : super(key: key); +// +// @override +// State createState() => _TrustedContactScreenState(); +// } +// +// class _TrustedContactScreenState extends State { +// late TrustedContactProvider provider; +// late TextEditingController searchController; +// +// List trustedContacts = []; +// +// @override +// void initState() { +// provider = context.read(); +// searchController = TextEditingController(); +// super.initState(); +// trustedContacts = provider.trustedContacts; +// } +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.transparent, +// body: Align( +// alignment: Alignment.bottomCenter, +// child: Container( +// margin: EdgeInsets.only(top: 120), +// height: double.infinity, +// width: double.infinity, +// decoration: BoxDecoration( +// color: Colors.white, +// borderRadius: const BorderRadius.only( +// topLeft: Radius.circular(20), +// topRight: Radius.circular(20), +// ), +// boxShadow: [ +// BoxShadow( +// color: Colors.black.withOpacity(0.25), +// offset: const Offset(0, 4), +// ) +// ], +// ), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisSize: MainAxisSize.min, +// children: [ +// _buildHeaderWidget(), +// const SizedBox(height: 24), +// Padding( +// padding: const EdgeInsets.symmetric(horizontal: 27), +// child: Text( +// "Trusted Senders", +// style: TextStyle( +// fontSize: 25.toFont, +// fontWeight: FontWeight.bold, +// color: Colors.black, +// ), +// ), +// ), +// const SizedBox(height: 30), +// Expanded( +// child: Consumer( +// builder: (context, myProvider, child) { +// return Scrollbar( +// child: ListContactWidget( +// isOnlyShowContactTrusted: true, +// trustedContacts: trustedContacts, +// onTapContact: (contact) async { +// await showModalBottomSheet( +// context: context, +// isScrollControlled: true, +// useRootNavigator: true, +// backgroundColor: Colors.transparent, +// builder: (BuildContext context) { +// return Padding( +// padding: const EdgeInsets.only(top: 120), +// child: ContactDetailScreen( +// contact: contact, +// onTrustFunc: () { +// Navigator.of(context).pop(); +// }, +// ), +// ); +// }, +// ); +// }, +// ), +// ); +// }, +// ), +// ), +// ], +// ), +// ), +// ), +// ); +// } +// +// Widget _buildHeaderWidget() { +// return Padding( +// padding: const EdgeInsets.fromLTRB(27, 24, 27, 0), +// child: Row( +// children: [ +// Container( +// height: 2, +// width: 45, +// decoration: BoxDecoration( +// color: Colors.black, +// borderRadius: BorderRadius.circular(20), +// ), +// ), +// const Spacer(), +// Align( +// alignment: Alignment.topRight, +// child: InkWell( +// onTap: () { +// Navigator.of(context).pop(); +// }, +// child: Container( +// height: 31.toHeight, +// alignment: Alignment.topRight, +// padding: const EdgeInsets.symmetric( +// horizontal: 30, +// ), +// decoration: BoxDecoration( +// border: Border.all( +// color: ColorConstants.grey, +// ), +// borderRadius: BorderRadius.circular(28), +// ), +// child: Center( +// child: Text( +// "Close", +// style: TextStyle( +// fontSize: 17.toFont, +// fontWeight: FontWeight.w600, +// color: ColorConstants.grey, +// ), +// ), +// ), +// ), +// ), +// ), +// ], +// ), +// ); +// } +// } diff --git a/lib/screens/contact_new_version/widget/contact_attachment_card.dart b/lib/screens/contact_new_version/widget/contact_attachment_card.dart new file mode 100644 index 00000000..e5fd42a2 --- /dev/null +++ b/lib/screens/contact_new_version/widget/contact_attachment_card.dart @@ -0,0 +1,651 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_contacts_group_flutter/at_contacts_group_flutter.dart'; +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/widgets/recents.dart'; +import 'package:atsign_atmosphere_pro/services/backend_service.dart'; +import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; +import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/services/snackbar_service.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/file_types.dart'; +import 'package:atsign_atmosphere_pro/utils/file_utils.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/file_progress_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/file_transfer_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/internet_connectivity_checker.dart'; +import 'package:atsign_atmosphere_pro/view_models/my_files_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; +import 'package:open_file/open_file.dart'; +import 'package:provider/provider.dart'; + +import '../../my_files/widgets/downloads_folders.dart'; + +class ContactAttachmentCard extends StatefulWidget { + final FileTransfer fileTransfer; + final FileData singleFile; + final bool isShowDate; + final EdgeInsetsGeometry? margin; + final Function()? onDownloaded; + final bool fromContact; + + const ContactAttachmentCard({ + Key? key, + required this.fileTransfer, + required this.singleFile, + this.isShowDate = true, + this.margin, + this.onDownloaded, + this.fromContact = false, + }) : super(key: key); + + @override + State createState() => _ContactAttachmentCardState(); +} + +class _ContactAttachmentCardState extends State { + bool isDownloaded = false; + bool isDownloading = false; + late WelcomeScreenProvider _welcomeScreenProvider; + late FileTransferProvider _fileTransferProvider; + + @override + void initState() { + super.initState(); + initDownloads(); + _welcomeScreenProvider = Provider.of(context, listen: false); + _fileTransferProvider = Provider.of(context, listen: false); + } + + void initDownloads() async { + isDownloaded = await isFilePresent(widget.singleFile.name ?? ""); + setState(() { + isDownloaded; + }); + } + + Future isFilePresent(String fileName) async { + String filePath = BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + fileName; + + File file = File(filePath); + bool fileExists = await file.exists(); + return fileExists; + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: isDownloading + ? null + : () async { + bool isExist = await isFilePresent(widget.singleFile.name ?? ''); + if (!isExist) { + await downloadFiles( + widget.fileTransfer, + fileName: widget.singleFile.name, + isPreview: true, + ); + } + await openPreview().whenComplete( + () => setState(() { + isDownloading = false; + }), + ); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + margin: widget.margin ?? + EdgeInsets.symmetric( + horizontal: 25, + vertical: 5, + ), + padding: EdgeInsets.all(15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + decoration: BoxDecoration( + color: ColorConstants.MILD_GREY, + borderRadius: BorderRadius.circular(5), + ), + child: Center( + child: thumbnail( + widget.singleFile.name?.split(".").last, + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + widget.singleFile.name!, + ), + ), + ), + SizedBox(width: 15), + Expanded( + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Text( + widget.singleFile.name ?? "", + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 10, + ), + ), + ), + Visibility( + visible: widget.isShowDate, + child: Text( + CommonUtilityFunctions() + .formatDateTime(widget.fileTransfer.date!), + style: TextStyle( + color: ColorConstants.grey, + fontSize: 10, + ), + ), + ), + ], + ), + SizedBox(height: 10), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Consumer( + builder: (_c, provider, _) { + var fileTransferProgress = provider + .receivedFileProgress[widget.fileTransfer.key]; + + return CommonUtilityFunctions() + .checkForDownloadAvailability( + widget.fileTransfer, + ) + ? fileTransferProgress != null + ? Image.asset( + ImageConstants.icCloudDownloading, + ) + : isDownloaded + ? SvgPicture.asset( + AppVectors.icCloudDownloaded, + ) + : InkWell( + onTap: () async { + await downloadFiles( + widget.fileTransfer, + fileName: widget.singleFile.name, + ); + }, + child: SvgPicture.asset( + AppVectors.icDownloadFile, + ), + ) + : SizedBox(); + }, + ), + const SizedBox( + width: 10, + ), + GestureDetector( + onTap: () async { + if (widget.fromContact) { + Navigator.pop(context); + } + await FileUtils.moveToSendFile( + BackendService.getInstance() + .downloadDirectory! + .path + + Platform.pathSeparator + + widget.singleFile.name!); + }, + child: SvgPicture.asset( + AppVectors.icSendFile, + ), + ), + Spacer(), + Text( + double.parse(widget.singleFile.size.toString()) <= 1024 + ? '${widget.singleFile.size} ' + TextStrings().kb + : '${(widget.singleFile.size! / (1024 * 1024)).toStringAsFixed(2)} ' + + TextStrings().mb, + style: TextStyle( + color: ColorConstants.grey, + fontSize: 10, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget thumbnail(String? extension, String path, + {bool? isFilePresent = true}) { + return FileTypes.IMAGE_TYPES.contains(extension) + ? ClipRRect( + borderRadius: BorderRadius.circular(10), + child: SizedBox( + height: 50, + width: 50, + child: isFilePresent! + ? Image.file( + File(path), + fit: BoxFit.cover, + errorBuilder: (BuildContext _context, _, __) { + return Container( + child: Icon( + Icons.image, + size: 30, + ), + ); + }, + ) + : Icon( + Icons.image, + size: 30, + ), + ), + ) + : FileTypes.VIDEO_TYPES.contains(extension) + ? FutureBuilder( + future: videoThumbnailBuilder(path), + builder: (context, snapshot) => ClipRRect( + borderRadius: BorderRadius.circular(10), + child: SizedBox( + height: 50, + width: 50, + child: (snapshot.data == null) + ? Image.asset( + ImageConstants.videoLogo, + fit: BoxFit.cover, + ) + : Image.memory( + videoThumbnail!, + fit: BoxFit.cover, + errorBuilder: (BuildContext _context, _, __) { + return Container( + child: Icon( + Icons.image, + size: 30, + ), + ); + }, + ), + ), + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(10), + child: SizedBox( + height: 50, + width: 50, + child: Center( + child: Image.asset( + FileTypes.PDF_TYPES.contains(extension) + ? ImageConstants.pdfLogo + : FileTypes.AUDIO_TYPES.contains(extension) + ? ImageConstants.musicLogo + : FileTypes.WORD_TYPES.contains(extension) + ? ImageConstants.wordLogo + : FileTypes.EXEL_TYPES.contains(extension) + ? ImageConstants.exelLogo + : FileTypes.TEXT_TYPES.contains(extension) + ? ImageConstants.txtLogo + : ImageConstants.unknownLogo, + fit: BoxFit.cover, + ), + ), + ), + ); + } + + Future downloadFiles( + FileTransfer? file, { + String? fileName, + bool isPreview = false, + }) async { + setState(() { + isDownloading = true; + }); + var fileTransferProgress = Provider.of( + NavService.navKey.currentContext!, + listen: false) + .receivedFileProgress[file!.key]; + + if (fileTransferProgress != null) { + return; //returning because download is still in progress + } + + var isConnected = Provider.of( + NavService.navKey.currentContext!, + listen: false) + .isInternetAvailable; + + if (!isConnected) { + SnackbarService().showSnackbar( + NavService.navKey.currentContext!, + TextStrings.noInternetMsg, + bgColor: ColorConstants.redAlert, + ); + return; + } + + var result; + if (fileName != null) { + result = await Provider.of( + NavService.navKey.currentContext!, + listen: false) + .downloadSingleFile( + file.key, + file.sender, + false, + fileName, + ); + } else { + result = await Provider.of( + NavService.navKey.currentContext!, + listen: false) + .downloadFiles( + file.key, + file.sender!, + false, + ); + } + + if (result is bool && result) { + if (mounted) { + setState(() { + if (!isPreview) isDownloading = false; + isDownloaded = true; + }); + } + await Provider.of(NavService.navKey.currentContext!, + listen: false) + .saveNewDataInMyFiles(file); + print(file.url); + + SnackbarService().showSnackbar( + NavService.navKey.currentContext!, + TextStrings().fileDownloadd, + bgColor: ColorConstants.successGreen, + ); + // send download acknowledgement + await Provider.of(NavService.navKey.currentContext!, + listen: false) + .sendFileDownloadAcknowledgement(file); + } else if (result is bool && !result) { + SnackbarService().showSnackbar( + NavService.navKey.currentContext!, + TextStrings().downloadFailed, + bgColor: ColorConstants.redAlert, + ); + if (mounted) { + setState(() { + if (!isPreview) isDownloading = false; + isDownloaded = false; + }); + } + } + } + + Future openPreview() async { + if (FileTypes.IMAGE_TYPES + .contains(widget.singleFile.name?.split(".").last)) { + String nickname = ""; + String filePath = BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + widget.singleFile.name!; + Uint8List imageBytes = base64Decode(await imageToBase64(filePath)); + final date = (widget.fileTransfer.date ?? DateTime.now()).toLocal(); + final shortDate = DateFormat('dd/MM/yy').format(date); + final time = DateFormat('HH:mm').format(date); + for (var contact in GroupService().allContacts) { + if (contact?.contact?.atSign == widget.fileTransfer.sender) { + nickname = contact?.contact?.tags?["nickname"] ?? ""; + break; + } + } + await showDialog( + context: NavService.navKey.currentContext!, + builder: (_) => Material( + type: MaterialType.transparency, + child: Column( + children: [ + SizedBox( + height: 10, + ), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(left: 32), + child: InkWell( + onTap: () { + Navigator.pop(NavService.navKey.currentContext!); + }, + child: Icon( + Icons.clear, + color: Colors.white, + size: 24, + ), + ), + ), + ), + SizedBox( + height: 10, + ), + Expanded( + child: Container( + // height: double.infinity, + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 33), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + image: DecorationImage( + image: MemoryImage( + imageBytes, + ), + fit: BoxFit.cover, + ), + ), + ), + ), + SizedBox( + height: 40, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + AppVectors.icCloudDownloaded, + height: 50, + width: 50, + ), + ), + SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + if (widget.fromContact) { + Navigator.pop(context); + } + Navigator.pop(context); + await FileUtils.moveToSendFile( + BackendService.getInstance() + .downloadDirectory! + .path + + Platform.pathSeparator + + widget.singleFile.name!); + }, + child: SvgPicture.asset( + AppVectors.icSendFile, + height: 50, + width: 50, + ), + ), + ), + SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + await FileUtils.deleteFile( + filePath, + fileTransferId: widget.fileTransfer.key, + ).then((value) => isDownloaded = false); + if (mounted) { + Navigator.pop(context); + } + setState(() {}); + }, + child: SvgPicture.asset( + AppVectors.icDeleteFile, + height: 50, + width: 50, + ), + ), + ), + ], + ), + SizedBox( + height: 40, + ), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + padding: EdgeInsets.all(20), + margin: EdgeInsets.symmetric(horizontal: 25), + width: double.infinity, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.topRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "$shortDate", + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ), + Container( + width: 1, + height: 8, + color: Color(0xFFD7D7D7), + margin: EdgeInsets.symmetric( + horizontal: 3, + ), + ), + Text( + "$time", + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ), + ], + ), + ), + SizedBox(height: 12), + Text( + (widget.singleFile.name ?? ''), + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.bold), + ), + SizedBox(height: 5), + Text( + double.parse(widget.singleFile.size.toString()) <= 1024 + ? '${widget.singleFile.size} ' + TextStrings().kb + : '${(widget.singleFile.size! / (1024 * 1024)).toStringAsFixed(2)} ' + + TextStrings().mb, + style: TextStyle( + color: ColorConstants.grey, + fontSize: 10, + ), + textAlign: TextAlign.left, + ), + SizedBox(height: 10), + nickname.isNotEmpty + ? Text( + nickname, + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.bold), + ) + : SizedBox(), + SizedBox(height: 5), + Text( + widget.fileTransfer.sender ?? '', + style: TextStyle( + fontSize: 12, + ), + ), + SizedBox(height: 10), + // fileDetail.message.isNotNull + // ? + Text( + "Message", + style: TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + // : SizedBox(), + SizedBox(height: 5), + Text( + widget.fileTransfer.notes ?? "", + style: TextStyle( + fontSize: 12, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } else { + await OpenFile.open(BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + (widget.singleFile.name ?? '')); + } + } + + Future imageToBase64(String imagePath) async { + File imageFile = File(imagePath); + List imageBytes = await imageFile.readAsBytes(); + String base64Image = base64Encode(imageBytes); + return base64Image; + } +} diff --git a/lib/screens/contact_new_version/widget/contact_card_widget.dart b/lib/screens/contact_new_version/widget/contact_card_widget.dart new file mode 100644 index 00000000..a8695898 --- /dev/null +++ b/lib/screens/contact_new_version/widget/contact_card_widget.dart @@ -0,0 +1,144 @@ +import 'dart:typed_data'; + +import 'package:at_contact/at_contact.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_circle_avatar.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class ContactCardWidget extends StatefulWidget { + final AtContact contact; + final double avatarSize, borderRadius; + final Function()? onTap; + final bool isSelected, isTrusted; + final Widget? suffixIcon; + + const ContactCardWidget({ + Key? key, + required this.contact, + this.avatarSize = 40, + this.borderRadius = 18, + this.onTap, + this.isSelected = false, + this.isTrusted = false, + this.suffixIcon, + }) : super(key: key); + + @override + State createState() => _ContactCardWidgetState(); +} + +class _ContactCardWidgetState extends State { + String contactName = 'UG'; + Uint8List? image; + + @override + void initState() { + getNameAndImage(); + super.initState(); + } + + void getNameAndImage() { + try { + contactName = widget.contact.atSign ?? 'UG'; + + if (contactName[0] == '@') { + contactName = contactName.substring(1); + } + + if (widget.contact.tags != null && + widget.contact.tags?['image'] != null) { + List intList = widget.contact.tags!['image'].cast(); + image = Uint8List.fromList(intList); + } + } catch (e) { + contactName = 'UG'; + print('Error in getting image $e'); + } + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: InkWell( + onTap: () { + widget.onTap?.call(); + }, + child: Container( + padding: const EdgeInsets.fromLTRB(20, 13, 12, 13), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: widget.isSelected + ? ColorConstants.orange + : ColorConstants.textBoxBg, + ), + color: widget.isSelected + ? ColorConstants.orange.withOpacity(0.2) + : Colors.white, + ), + child: Row( + children: [ + Container( + height: widget.avatarSize, + width: widget.avatarSize, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + widget.borderRadius, + ), + ), + child: image != null + ? CustomCircleAvatar( + byteImage: image, + nonAsset: true, + ) + : ContactInitial( + borderRadius: widget.borderRadius, + size: widget.avatarSize, + initials: contactName, + ), + ), + const SizedBox(width: 18), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.contact.atSign ?? '', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + Text( + widget.contact.tags?['nickname'] ?? + widget.contact.atSign!.substring(1), + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black, + ), + ), + ], + ), + ), + widget.isTrusted + ? Padding( + padding: const EdgeInsets.only(right: 4), + child: SvgPicture.asset( + AppVectors.icTrustActivated, + ), + ) + : widget.suffixIcon ?? const SizedBox(), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/contact_new_version/widget/contacts_widget.dart b/lib/screens/contact_new_version/widget/contacts_widget.dart new file mode 100644 index 00000000..c70b3a52 --- /dev/null +++ b/lib/screens/contact_new_version/widget/contacts_widget.dart @@ -0,0 +1,294 @@ +import 'package:at_common_flutter/at_common_flutter.dart'; +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_group_flutter/at_contacts_group_flutter.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/contact_type.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/contact_card_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/group_card_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class ContactsWidget extends StatefulWidget { + final bool isShowAlpha, isSelectMultiContacts; + + final Function(AtContact contact)? onTapContact; + final Function(AtGroup group)? onTapGroup; + final Function(List contacts)? onSelectContacts; + final List? selectedContacts; + final List? trustedContacts; + final List contacts; + final Function? onRefresh; + final EdgeInsetsGeometry? padding, contactPadding; + final ListContactType? contactsType; + + const ContactsWidget({ + Key? key, + required this.contacts, + this.isShowAlpha = true, + this.isSelectMultiContacts = false, + this.onTapContact, + this.onTapGroup, + this.onSelectContacts, + this.trustedContacts, + this.selectedContacts, + this.onRefresh, + this.padding, + this.contactPadding, + this.contactsType, + }) : super(key: key); + + @override + State createState() => _ContactsWidgetState(); +} + +class _ContactsWidgetState extends State { + List listContactSelected = []; + + @override + void initState() { + if ((widget.selectedContacts ?? []).isNotEmpty) { + listContactSelected.addAll(widget.selectedContacts!); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + color: ColorConstants.orange, + onRefresh: () async { + widget.onRefresh?.call(); + setState(() {}); + }, + child: ListView.builder( + physics: const ClampingScrollPhysics(), + itemCount: 27, + shrinkWrap: true, + itemBuilder: (context, alphabetIndex) { + List contactsForAlphabet = []; + List trustedContacts = []; + var currentChar = + String.fromCharCode(alphabetIndex + 65).toUpperCase(); + + if (alphabetIndex == 26) { + currentChar = 'Others'; + } + + if (widget.contactsType == ListContactType.trusted) { + for (var element in (widget.trustedContacts ?? [])) { + trustedContacts.add( + GroupContactsModel( + contact: element, + ), + ); + } + } + + final listContact = widget.contactsType == ListContactType.trusted + ? trustedContacts + : widget.contacts; + + contactsForAlphabet = getContactsForAlphabets( + listContact, + currentChar, + alphabetIndex, + ); + + if (contactsForAlphabet.isEmpty) { + return const SizedBox(); + } + + return Padding( + padding: widget.contactPadding ?? EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.isShowAlpha) ...[ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + currentChar, + style: TextStyle( + fontSize: 20.toFont, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 16.toWidth), + Expanded( + child: Divider( + color: ColorConstants.dividerGrey, + height: 1.toHeight, + ), + ), + ], + ), + ), + ], + contactListBuilder(contactsForAlphabet) + ], + ), + ); + }, + ), + ); + } + + Widget contactListBuilder( + List contactsForAlphabet, + ) { + return ListView.builder( + itemCount: contactsForAlphabet.length, + padding: widget.padding ?? EdgeInsets.only(left: 24), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final contact = contactsForAlphabet[index]!.contact; + return (contactsForAlphabet[index]!.contact != null) + ? ContactCardWidget( + contact: contact!, + isTrusted: _checkTrustedContact(contact), + isSelected: _checkContactSelected( + contactsForAlphabet[index]!, + ), + onTap: () { + widget.isSelectMultiContacts + ? _onSelectContact( + contactsForAlphabet[index]!, + ) + : widget.onTapContact?.call(contact); + }, + ) + : GroupCardWidget( + group: contactsForAlphabet[index]!.group!, + isSelected: _checkContactSelected( + contactsForAlphabet[index]!, + ), + onTap: () { + widget.isSelectMultiContacts + ? _onSelectContact( + contactsForAlphabet[index]!, + ) + : widget.onTapGroup?.call( + contactsForAlphabet[index]!.group!, + ); + }, + ); + }, + ); + } + + /// returns list of atsigns, that matches with [currentChar] in [_filteredList] + List getContactsForAlphabets( + List _filteredList, + String currentChar, + int alphabetIndex) { + var contactsForAlphabet = []; + + /// contacts, groups that does not starts with alphabets + if (alphabetIndex == 26) { + for (var c in _filteredList) { + if (c?.contact != null && + !RegExp(r'^[a-z]+$').hasMatch( + (c?.contact?.atSign?[1] ?? '').toLowerCase(), + )) { + contactsForAlphabet.add(c); + } + } + for (var c in _filteredList) { + if (c?.group != null && (c?.group?.displayName ?? '').isNotEmpty) { + if (!RegExp(r'^[a-z]+$').hasMatch( + (c?.group?.displayName?[0] ?? '').toLowerCase(), + )) { + contactsForAlphabet.add(c); + } + } + } + } else { + for (var c in _filteredList) { + if (c?.contact != null) { + if (c?.contact?.atSign?[1].toUpperCase() == currentChar) { + contactsForAlphabet.add(c); + } + } + } + + for (var c in _filteredList) { + if (c?.group != null && (c?.group?.displayName ?? '').isNotEmpty) { + if (c?.group?.displayName?[0].toUpperCase() == currentChar) { + contactsForAlphabet.add(c); + } + } + } + } + + return contactsForAlphabet; + } + + void _onSelectContact(GroupContactsModel contact) { + if (listContactSelected.isEmpty) { + listContactSelected.add(contact); + } else { + bool isAdd = true; + GroupContactsModel? contactExists; + + for (var element in listContactSelected) { + contactExists = element; + if (contact.contactType == ContactsType.CONTACT) { + if (contact.contact?.atSign == element.contact?.atSign) { + isAdd = false; + break; + } + } else { + if (contact.group?.groupId == element.group?.groupId) { + isAdd = false; + break; + } + } + } + + if (!isAdd) { + listContactSelected.remove(contactExists); + } else { + listContactSelected.add(contact); + } + } + + widget.onSelectContacts?.call( + listContactSelected, + ); + + setState(() {}); + } + + bool _checkContactSelected(GroupContactsModel contact) { + bool isSelected = false; + for (var element in listContactSelected) { + if (contact.contactType == ContactsType.CONTACT) { + if (contact.contact?.atSign == element.contact?.atSign) { + isSelected = true; + } + } else { + if (contact.group?.groupId == element.group?.groupId) { + isSelected = true; + } + } + } + + return isSelected; + } + + bool _checkTrustedContact(AtContact contact) { + bool isTrusted = false; + for (var element in (widget.trustedContacts ?? [])) { + if (contact.atSign == element.atSign) { + isTrusted = true; + } + } + + return isTrusted; + } +} diff --git a/lib/screens/contact_new_version/widget/empty_contact_widget.dart b/lib/screens/contact_new_version/widget/empty_contact_widget.dart new file mode 100644 index 00000000..51e53325 --- /dev/null +++ b/lib/screens/contact_new_version/widget/empty_contact_widget.dart @@ -0,0 +1,154 @@ +import 'package:atsign_atmosphere_pro/data_models/enums/contact_type.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/create_group_screen.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class EmptyContactsWidget extends StatelessWidget { + final ListContactType? contactsType; + final Function() onTapAddButton; + + const EmptyContactsWidget({ + Key? key, + this.contactsType, + required this.onTapAddButton, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return contactsType == ListContactType.groups || + contactsType == ListContactType.contact + ? _buildEmptyImage() + : contactsType == ListContactType.trusted + ? Padding( + padding: const EdgeInsets.only(top: 100), + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Add contacts to trusted by", + style: TextStyle( + fontSize: 18, + color: ColorConstants.grey, + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "selecting", + style: TextStyle( + fontSize: 18, + color: ColorConstants.grey, + ), + ), + SvgPicture.asset( + AppVectors.icBigTrustActivated, + ), + Text( + "next to their name!", + style: TextStyle( + fontSize: 18, + color: ColorConstants.grey, + ), + ), + ], + ), + ], + ), + ), + ) + : Center( + child: SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 122, + width: 226, + child: Image.asset( + ImageConstants.emptyBox, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 24), + child: Text( + "No Contacts", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: ColorConstants.grey, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildEmptyImage() { + return Center( + child: SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 122, + width: 226, + child: Image.asset( + ImageConstants.emptyBox, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 24), + child: Text( + contactsType == ListContactType.groups + ? "No Groups" + : "No Contacts", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: ColorConstants.grey, + ), + ), + ), + InkWell( + borderRadius: BorderRadius.circular(46), + onTap: onTapAddButton, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.orange, + borderRadius: BorderRadius.circular(46), + ), + padding: EdgeInsets.symmetric( + horizontal: 13, + vertical: 8, + ), + child: Text( + contactsType == ListContactType.groups + ? "Add Group" + : "Add Contact", + style: TextStyle( + color: Colors.white, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + SizedBox(height: 100), + ], + ), + ), + ); + } +} diff --git a/lib/screens/contact_new_version/widget/group_card_widget.dart b/lib/screens/contact_new_version/widget/group_card_widget.dart new file mode 100644 index 00000000..738dd734 --- /dev/null +++ b/lib/screens/contact_new_version/widget/group_card_widget.dart @@ -0,0 +1,124 @@ +import 'dart:typed_data'; +import 'package:at_contact/at_contact.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_circle_avatar.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class GroupCardWidget extends StatefulWidget { + final AtGroup group; + final double size; + final double borderRadius; + final Function()? onTap; + final bool isSelected; + + const GroupCardWidget({ + Key? key, + required this.group, + this.size = 44, + this.borderRadius = 10, + this.onTap, + this.isSelected = false, + }) : super(key: key); + + @override + State createState() => _GroupCardWidgetState(); +} + +class _GroupCardWidgetState extends State { + String groupName = 'UG'; + Uint8List? image; + + @override + void initState() { + getNameAndImage(); + super.initState(); + } + + getNameAndImage() { + try { + if (widget.group.groupPicture != null) { + image = Uint8List.fromList(widget.group.groupPicture?.cast()); + } + + groupName = widget.group.displayName ?? 'UG'; + } catch (e) { + groupName = 'UG'; + print('Error in getting image $e'); + } + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: InkWell( + onTap: () { + widget.onTap?.call(); + }, + child: Container( + padding: const EdgeInsets.fromLTRB(17, 10, 12, 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: widget.isSelected + ? ColorConstants.orange + : ColorConstants.textBoxBg, + ), + color: widget.isSelected + ? ColorConstants.orange.withOpacity(0.2) + : Colors.white, + ), + child: Row( + children: [ + Container( + height: widget.size, + width: widget.size, + decoration: const BoxDecoration( + color: Colors.black, + shape: BoxShape.circle, + ), + child: image != null + ? CustomCircleAvatar( + byteImage: image, + nonAsset: true, + ) + : ContactInitial( + borderRadius: widget.borderRadius, + size: widget.size, + initials: groupName, + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + (widget.group.displayName ?? widget.group.groupName) ?? + '', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + Text( + '${widget.group.members?.length ?? 0} Members', + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/contact_new_version/widget/list_contact_widget.dart b/lib/screens/contact_new_version/widget/list_contact_widget.dart new file mode 100644 index 00000000..bd66a63c --- /dev/null +++ b/lib/screens/contact_new_version/widget/list_contact_widget.dart @@ -0,0 +1,158 @@ +import 'package:at_common_flutter/at_common_flutter.dart'; +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_group_flutter/at_contacts_group_flutter.dart' + hide ContactsType; +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/contact_filter_type.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/contact_type.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/contacts_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; + +import 'empty_contact_widget.dart'; + +class ListContactWidget extends StatefulWidget { + final bool isShowAlpha, isSelectMultiContacts; + final Function(AtContact contact)? onTapContact; + final Function(AtGroup contact)? onTapGroup; + final Function(List contacts)? onSelectContacts; + final List? trustedContacts; + final List? selectedContacts; + final ListContactType? contactsType; + final String searchKeywords; + final Function()? onTapAddButton; + + const ListContactWidget({ + Key? key, + this.isShowAlpha = true, + this.isSelectMultiContacts = false, + this.onTapContact, + this.onTapGroup, + this.onSelectContacts, + this.trustedContacts, + this.selectedContacts, + this.contactsType, + this.searchKeywords = '', + this.onTapAddButton, + }) : super(key: key); + + @override + State createState() => _ListContactWidgetState(); +} + +class _ListContactWidgetState extends State { + late GroupService _groupService; + bool showContacts = false; + bool showGroups = false; + + @override + void initState() { + _groupService = GroupService(); + + if (widget.contactsType == ListContactType.all) { + showContacts = true; + showGroups = true; + } else if (widget.contactsType == ListContactType.groups) { + showContacts = false; + showGroups = true; + } else { + showContacts = true; + showGroups = false; + } + + _groupService.fetchGroupsAndContacts(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + SizeConfig().init(context); + return StreamBuilder>( + stream: _groupService.allContactsStream, + initialData: _groupService.allContacts, + builder: (context, snapshot) { + if ((snapshot.connectionState == ConnectionState.waiting)) { + return const Center( + child: CircularProgressIndicator( + color: ColorConstants.orange, + ), + ); + } else { + // filtering contacts and groups + var _filteredList = []; + List trustedContacts = []; + _filteredList = getAllContactList(snapshot.data ?? []); + + if (_filteredList.isEmpty) { + return EmptyContactsWidget( + contactsType: widget.contactsType, + onTapAddButton: widget.onTapAddButton ?? () {}, + ); + } + + if (widget.contactsType == ListContactType.trusted) { + for (var element in (widget.trustedContacts ?? [])) { + trustedContacts.add( + GroupContactsModel( + contact: element, + ), + ); + } + + if (trustedContacts.isEmpty) { + return EmptyContactsWidget( + contactsType: widget.contactsType, + onTapAddButton: widget.onTapAddButton ?? () {}, + ); + } + } + + // renders contacts according to the initial alphabet + return Scrollbar( + radius: const Radius.circular(11), + child: ContactsWidget( + contacts: _filteredList, + contactsType: widget.contactsType, + isShowAlpha: widget.isShowAlpha, + isSelectMultiContacts: widget.isSelectMultiContacts, + onTapContact: widget.onTapContact, + onTapGroup: widget.onTapGroup, + onSelectContacts: widget.onSelectContacts, + onRefresh: () async { + await _groupService.fetchGroupsAndContacts(); + }, + contactPadding: EdgeInsets.only(left: 18, right: 28), + selectedContacts: widget.selectedContacts, + trustedContacts: widget.trustedContacts, + ), + ); + } + }, + ); + } + + // creates a list of contacts by merging atsigns and groups. + List getAllContactList( + List allGroupContactData) { + var _filteredList = []; + for (var c in allGroupContactData) { + if (showContacts && + c?.contact != null && + (c?.contact?.atSign ?? '').toString().toUpperCase().contains( + widget.searchKeywords.toUpperCase(), + )) { + _filteredList.add(c); + } + + if (showGroups && + c?.group != null && + c?.group?.displayName != null && + (c?.group?.displayName ?? '').toUpperCase().contains( + widget.searchKeywords.toUpperCase(), + )) { + _filteredList.add(c); + } + } + return _filteredList; + } +} diff --git a/lib/screens/contact_new_version/widget/option_dialog.dart b/lib/screens/contact_new_version/widget/option_dialog.dart new file mode 100644 index 00000000..5743cf07 --- /dev/null +++ b/lib/screens/contact_new_version/widget/option_dialog.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; + +class OptionDialog extends StatelessWidget { + final Offset? position; + final Function? editNickNameFunc, blockFunc, deleteFunc; + + const OptionDialog({ + Key? key, + this.position, + this.editNickNameFunc, + this.blockFunc, + this.deleteFunc, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: InkWell( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + onTap: () { + Navigator.of(context).pop(); + }, + child: Stack( + fit: StackFit.expand, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only( + top: position?.dy ?? 0, + left: 24, + right: 24, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + _buildOptionCard( + context: context, + title: "Edit Nickname", + onTap: () { + editNickNameFunc?.call(); + }, + ), + _buildOptionCard( + context: context, + title: "Block", + onTap: () { + blockFunc?.call(); + }, + ), + _buildOptionCard( + context: context, + title: "Delete", + onTap: () { + deleteFunc?.call(); + }, + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildOptionCard({ + required String title, + required Function onTap, + required BuildContext context, + }) { + return InkWell( + onTap: () { + onTap.call(); + Navigator.of(context).pop(); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 44, + decoration: BoxDecoration( + color: Colors.white, + ), + child: Center( + child: Text( + title, + style: TextStyle( + color: Colors.black, + fontSize: 12, + ), + ), + ), + ), + Container( + height: 1, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.black, + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/group_contacts_screen/widgets/group_contact_list_tile.dart b/lib/screens/group_contacts_screen/widgets/group_contact_list_tile.dart index 1f41f5f7..e56f097e 100644 --- a/lib/screens/group_contacts_screen/widgets/group_contact_list_tile.dart +++ b/lib/screens/group_contacts_screen/widgets/group_contact_list_tile.dart @@ -4,8 +4,10 @@ /// all [isSelected] functionalities are disabled import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; import 'package:flutter/material.dart'; import 'package:at_common_flutter/services/size_config.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class ContactListTile extends StatefulWidget { final String? name; @@ -30,6 +32,7 @@ class ContactListTile extends StatefulWidget { this.plainView = false, this.onTileTap}) : super(key: key); + @override _ContactListTileState createState() => _ContactListTileState(); } @@ -53,7 +56,7 @@ class _ContactListTileState extends State { }); }, title: Text( - widget.name!, + widget.atSign!, style: TextStyle( color: Colors.black, fontSize: 14.toFont, @@ -62,7 +65,7 @@ class _ContactListTileState extends State { ), ), subtitle: Text( - widget.atSign!, + widget.name!, style: TextStyle( color: ColorConstants.fadedText, fontSize: 14.toFont, @@ -71,18 +74,14 @@ class _ContactListTileState extends State { ), ), trailing: (widget.plainView) - ? Container( - height: 0, - width: 0, - ) + ? SizedBox() : (widget.isSelected) ? GestureDetector( onTap: () { widget.onRemove(); }, - child: Icon( - Icons.close, - color: Color(0xffA8A8A8), + child: SvgPicture.asset( + AppVectors.icClose, ), ) : Icon( @@ -100,28 +99,6 @@ class _ContactListTileState extends State { ), child: widget.image, ), - Positioned( - bottom: 0, - right: 0, - child: (widget.onlyRemoveMethod) - ? Container() - : Container( - height: 15.toHeight, - width: 15.toHeight, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: (widget.isSelected) - ? Colors.black - : Colors.transparent), - child: (widget.isSelected) - ? Icon( - Icons.check, - color: Colors.white, - size: 10.toHeight, - ) - : Container(), - ), - ) ], ), ), diff --git a/lib/screens/history/history_screen.dart b/lib/screens/history/history_screen.dart index bae1a4a1..2abaa72b 100644 --- a/lib/screens/history/history_screen.dart +++ b/lib/screens/history/history_screen.dart @@ -1,5 +1,4 @@ import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; -import 'package:atsign_atmosphere_pro/screens/common_widgets/app_bar.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_button.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/provider_handler.dart'; import 'package:atsign_atmosphere_pro/screens/history/widgets/received_file_list_tile.dart'; @@ -93,6 +92,7 @@ class _HistoryScreenState extends State controller: _controller, children: [ RefreshIndicator( + color: ColorConstants.orange, onRefresh: () async { if (historyProvider! .status[historyProvider!.PERIODIC_REFRESH] != @@ -192,6 +192,7 @@ class _HistoryScreenState extends State ), ), RefreshIndicator( + color: ColorConstants.orange, onRefresh: () async { if (historyProvider! .status[historyProvider!.PERIODIC_REFRESH] != diff --git a/lib/screens/history/transfer_history_screen.dart b/lib/screens/history/transfer_history_screen.dart new file mode 100644 index 00000000..5239076b --- /dev/null +++ b/lib/screens/history/transfer_history_screen.dart @@ -0,0 +1,339 @@ +import 'dart:io'; + +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/app_bar_custom.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_button.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/provider_handler.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/search_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/history/widgets/filter_history_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/history/widgets/history_card_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/constants.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; +import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:open_file/open_file.dart'; +import 'package:provider/provider.dart'; + +import '../../data_models/file_entity.dart'; +import '../../services/navigation_service.dart'; +import '../../utils/text_strings.dart'; +import '../../view_models/file_transfer_provider.dart'; +import '../common_widgets/confirmation_dialog.dart'; + +class TransferHistoryScreen extends StatefulWidget { + const TransferHistoryScreen({Key? key}) : super(key: key); + + @override + State createState() => _TransferHistoryScreenState(); +} + +class _TransferHistoryScreenState extends State { + bool isFilterOpened = false; + late HistoryProvider historyProvider; + late TextEditingController searchController; + GlobalKey filterKey = GlobalKey(); + + @override + void initState() { + historyProvider = context.read(); + searchController = TextEditingController(); + WidgetsBinding.instance.addPostFrameCallback((_) { + reloadView(); + }); + super.initState(); + } + + void reloadView() async { + if (context.read().hadNewFile) { + await historyProvider.getAllFileTransferHistory(); + historyProvider.changeIsUpcomingEvent(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorConstants.background, + appBar: AppBarCustom( + height: 130, + title: "History", + ), + body: _buildBody(), + ); + } + + Widget _buildBody() { + return Column( + children: [ + Padding( + padding: EdgeInsets.only( + top: 18.toHeight, + right: 22.toWidth, + left: 34.toWidth, + bottom: 16.toHeight, + ), + child: Row( + children: [ + Expanded( + child: SearchWidget( + controller: searchController, + borderColor: Colors.white, + backgroundColor: Colors.white, + hintText: "Search", + onChange: (text) { + setState(() {}); + }, + hintStyle: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.zero, + ), + ), + SizedBox(width: 16), + InkWell( + onTap: () { + _onTapFilterIcon(); + setState(() { + isFilterOpened = true; + }); + }, + child: SvgPicture.asset( + isFilterOpened + ? AppVectors.icFilterOpened + : AppVectors.icFilter, + key: filterKey, + ), + ), + ], + ), + ), + Expanded( + child: RefreshIndicator( + color: ColorConstants.orange, + onRefresh: () async { + if (historyProvider.status[historyProvider.PERIODIC_REFRESH] != + Status.Loading) { + await historyProvider.getAllFileTransferHistory(); + } + }, + child: ProviderHandler( + functionName: historyProvider.GET_ALL_FILE_HISTORY, + showError: false, + load: (provider) async { + await historyProvider.getAllFileTransferHistory(); + }, + successBuilder: (provider) { + if ((provider.displayFilesHistory.isEmpty)) { + return ListView.separated( + padding: EdgeInsets.only(bottom: 170.toHeight), + physics: AlwaysScrollableScrollPhysics(), + separatorBuilder: (context, index) => + Divider(indent: 16.toWidth), + itemCount: 1, + itemBuilder: (context, index) => Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: SizeConfig().screenHeight - 120.toHeight, + child: Center( + child: Text( + 'No files sent', + style: TextStyle( + fontSize: 15.toFont, + fontWeight: FontWeight.normal, + ), + ), + ), + ), + ), + ); + } else { + List filteredFileHistory = []; + + provider.displayFilesHistory.forEach((element) { + if (element.type == HistoryType.send) { + if (element.sharedWith!.any( + (ShareStatus sharedStatus) => sharedStatus.atsign! + .contains(searchController.text), + ) || + (element.groupName != null && + element.groupName!.toLowerCase().contains( + searchController.text.toLowerCase()))) { + filteredFileHistory.add(element); + } + } else { + if (element.fileDetails!.sender!.contains( + searchController.text, + )) { + filteredFileHistory.add(element); + } + } + }); + + if (filteredFileHistory.isNotEmpty) { + return ListView.separated( + padding: EdgeInsets.only(bottom: 170.toHeight), + physics: AlwaysScrollableScrollPhysics(), + separatorBuilder: (context, index) { + return SizedBox(height: 10.toHeight); + }, + itemCount: filteredFileHistory.length, + itemBuilder: (context, index) { + return HistoryCardWidget( + fileHistory: filteredFileHistory[index], + onDownloaded: () async { + await provider.getAllFileTransferHistory(); + }, + ); + }, + ); + } else { + return Center( + child: Text('No results found'), + ); + } + } + }, + errorBuilder: (provider) => ListView.separated( + padding: EdgeInsets.only(bottom: 170.toHeight), + physics: AlwaysScrollableScrollPhysics(), + separatorBuilder: (context, index) => + Divider(indent: 16.toWidth), + itemCount: 1, + itemBuilder: (context, index) => Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: SizeConfig().screenHeight - 120.toHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Some error occured', + style: TextStyle( + fontSize: 15.toFont, + fontWeight: FontWeight.normal, + ), + ), + SizedBox(height: 10.toHeight), + CustomButton( + isOrange: true, + buttonText: TextStrings().retry, + height: 40.toHeight, + width: 115.toWidth, + onPressed: () { + historyProvider.getAllFileTransferHistory(); + }, + ) + ], + ), + ), + ), + ), + ), + ), + ), + ], + ); + } + + void _onTapFilterIcon() async { + RenderBox box = filterKey.currentContext!.findRenderObject() as RenderBox; + Offset position = box.localToGlobal(Offset.zero); + + await showDialog( + barrierDismissible: true, + useRootNavigator: true, + context: context, + builder: (context) { + return Consumer( + builder: (context, provider, _) { + provider.resetOptional(); + return FilterHistoryWidget( + position: position, + typeSelected: provider.typeSelected, + onSelectedFilter: (value) { + provider.changeFilterType(value); + print(value); + }, + onSelectedOptionalFilter: (value) async { + print(value); + await provider.filterByFileType(value); + }, + listFileType: provider.listType, + ); + }, + ); + }, + ).whenComplete(() { + setState(() { + isFilterOpened = false; + }); + }); + } + + void reUploadFileConfirmation(FileEntity fileEntity) async { + await showDialog( + context: NavService.navKey.currentContext!, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.toWidth), + ), + content: ConfirmationDialog( + TextStrings.reUploadFileMsg, + () async { + FileData fileData = FileData( + name: fileEntity.file!.name, + size: fileEntity.file!.size, + url: fileEntity.fileTransferObject.fileUrl, + ); + + var sentItemIndex = + Provider.of(context, listen: false) + .sentHistory + .indexWhere((element) => + element.fileTransferObject?.transferId == + fileEntity.transferId); + FileHistory? sentHistory; + + if (sentItemIndex != -1) { + sentHistory = + Provider.of(context, listen: false) + .sentHistory[sentItemIndex]; + } else { + throw ('sent history not found'); + } + + await Provider.of(context, listen: false) + .reuploadFiles([fileData], 0, sentHistory); + }, + ), + ); + }, + ); + } + + void openFile(FileEntity fileEntity) async { + String path = MixedConstants.RECEIVED_FILE_DIRECTORY + + Platform.pathSeparator + + (fileEntity.file!.name ?? ''); + + if (fileEntity.historyType == HistoryType.send) { + path = MixedConstants.SENT_FILE_DIRECTORY + + Platform.pathSeparator + + (fileEntity.file!.name ?? ''); + } + + File test = File(path); + bool fileExists = await test.exists(); + if (fileExists) { + await OpenFile.open(path); + } + } +} diff --git a/lib/screens/history/widgets/filter_history_widget.dart b/lib/screens/history/widgets/filter_history_widget.dart new file mode 100644 index 00000000..f67fe9f8 --- /dev/null +++ b/lib/screens/history/widgets/filter_history_widget.dart @@ -0,0 +1,192 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/file_category_type.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; +import 'package:atsign_atmosphere_pro/screens/history/widgets/filter_option_item.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class FilterHistoryWidget extends StatefulWidget { + final Offset? position; + final Function(HistoryType historyType)? onSelectedFilter; + final Function(List fileTypes)? onSelectedOptionalFilter; + final HistoryType? typeSelected; + final List? listFileType; + + FilterHistoryWidget({ + Key? key, + this.position, + this.onSelectedFilter, + this.typeSelected, + this.onSelectedOptionalFilter, + this.listFileType, + }) : super(key: key); + + @override + State createState() => _FilterHistoryWidgetState(); +} + +class _FilterHistoryWidgetState extends State { + bool isShowOptional = false; + List listFileType = []; + + final List historyTypes = [ + HistoryType.received, + HistoryType.send, + HistoryType.all, + ]; + + final List optionalHistoryTypes = [ + FileType.photo, + FileType.file, + FileType.audio, + FileType.video, + FileType.zips, + FileType.other, + ]; + + @override + void initState() { + listFileType = widget.listFileType ?? []; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Stack( + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + color: Colors.transparent, + height: double.infinity, + width: double.infinity, + ), + ), + Positioned( + right: 15, + top: widget.position?.dy, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width - 30.toWidth, + child: ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: historyTypes.length, + separatorBuilder: (context, index) { + return Divider( + color: ColorConstants.disableColor, + height: 0, + thickness: 1, + // thickness: 0.65, + ); + }, + itemBuilder: (context, index) { + if (historyTypes[index] == HistoryType.all) { + return FilterOptionItem( + icon: historyTypes[index].icon, + title: historyTypes[index].text, + isCheck: historyTypes[index] == widget.typeSelected, + isAllOption: true, + isShowOptional: isShowOptional, + allOptionOnTap: () { + setState(() { + isShowOptional = !isShowOptional; + }); + }, + borderRadius: isShowOptional + ? null + : BorderRadius.vertical( + bottom: Radius.circular(13), + ), + onTap: () { + widget.onSelectedFilter?.call( + historyTypes[index], + ); + }, + ); + } + return FilterOptionItem( + icon: historyTypes[index].icon, + title: historyTypes[index].text, + isCheck: historyTypes[index] == widget.typeSelected, + borderRadius: index == 0 + ? BorderRadius.vertical( + top: Radius.circular(13), + ) + : null, + onTap: () { + widget.onSelectedFilter?.call( + historyTypes[index], + ); + }, + ); + }, + ), + ), + if (isShowOptional) ...[ + Divider( + color: ColorConstants.disableColor, + height: 0, + thickness: 1, + // thickness: 0.65, + ), + SizedBox( + width: MediaQuery.of(context).size.width - 30.toWidth, + child: ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: optionalHistoryTypes.length, + separatorBuilder: (context, index) { + return Divider( + color: listFileType + .contains(optionalHistoryTypes[index]) + ? ColorConstants.orange + : ColorConstants.disableColor, + height: 0, + thickness: 1, + // thickness: 0.65, + ); + }, + itemBuilder: (context, index) { + return FilterOptionItem( + icon: optionalHistoryTypes[index].icon, + title: optionalHistoryTypes[index].text, + isOptional: true, + isCheck: listFileType + .contains(optionalHistoryTypes[index]), + borderRadius: index == optionalHistoryTypes.length - 1 + ? BorderRadius.vertical( + bottom: Radius.circular(13), + ) + : null, + onTap: () { + final fileType = optionalHistoryTypes[index]; + if (listFileType.isNotEmpty && + listFileType.contains(fileType)) { + listFileType.remove(fileType); + } else { + listFileType.add(fileType); + } + widget.onSelectedOptionalFilter?.call(listFileType); + }, + ); + }, + ), + ), + ] + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/history/widgets/filter_item_widget.dart b/lib/screens/history/widgets/filter_item_widget.dart new file mode 100644 index 00000000..a7c5ddf4 --- /dev/null +++ b/lib/screens/history/widgets/filter_item_widget.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class FilterItemWidget extends StatelessWidget { + final Color backgroundColor; + final Color borderColor; + final String prefixIcon; + final String title; + + const FilterItemWidget({ + Key? key, + required this.backgroundColor, + required this.borderColor, + required this.prefixIcon, + required this.title, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 30, + padding: const EdgeInsets.fromLTRB(12, 7, 8, 8), + margin: EdgeInsets.symmetric(vertical: 12.5, horizontal: 16), + decoration: BoxDecoration( + color: backgroundColor, + border: Border.all( + color: borderColor, + ), + borderRadius: BorderRadius.circular(14), + ), + child: Row( + children: [ + SvgPicture.asset(prefixIcon), + Expanded( + child: Center( + child: Text( + title, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/history/widgets/filter_option_item.dart b/lib/screens/history/widgets/filter_option_item.dart new file mode 100644 index 00000000..9c3ab535 --- /dev/null +++ b/lib/screens/history/widgets/filter_option_item.dart @@ -0,0 +1,117 @@ +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class FilterOptionItem extends StatelessWidget { + final String? icon; + final String? title; + final bool isOptional, isCheck; + final BorderRadiusGeometry? borderRadius; + final Function()? onTap; + final Function()? allOptionOnTap; + final bool isAllOption; + final bool? isShowOptional; + + const FilterOptionItem({ + Key? key, + this.icon, + this.isOptional = false, + this.borderRadius, + this.title, + this.isCheck = false, + this.onTap, + this.isAllOption = false, + this.allOptionOnTap, + this.isShowOptional, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Color color = isCheck + ? isOptional + ? Colors.black + : Colors.white + : isOptional + ? ColorConstants.unselectedFilterOptionColor + : Colors.black; + + Color backgroundColor = isOptional + ? ColorConstants.unselectedFilterOptionBackgroundColor + : Colors.white; + + Color checkedBackgroundColor = isOptional + ? ColorConstants.optionalFilterBackgroundColor + : ColorConstants.orange; + + return InkWell( + onTap: () { + isAllOption ? allOptionOnTap?.call() : onTap?.call(); + }, + child: Container( + height: 44, + padding: EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: isCheck ? checkedBackgroundColor : backgroundColor, + borderRadius: borderRadius ?? BorderRadius.zero, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + (icon ?? '').isNotEmpty + ? SvgPicture.asset( + icon!, + color: color, + height: 16, + width: 12, + fit: BoxFit.cover, + ) + : SizedBox(width: 12), + SizedBox(width: 16), + Text( + title ?? '', + style: TextStyle( + color: color, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isAllOption) + SvgPicture.asset( + isShowOptional ?? false + ? AppVectors.icArrowUpOutline + : AppVectors.icArrowDownOutline, + width: 20, + height: 20, + color: color, + fit: BoxFit.cover, + ), + if (isAllOption) SizedBox(width: 16), + InkWell( + onTap: () { + isAllOption ? onTap?.call() : null; + }, + child: SvgPicture.asset( + isCheck ? AppVectors.icChecked : AppVectors.icUnchecked, + width: 24, + height: 24, + color: color, + fit: BoxFit.cover, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/history/widgets/history_card_widget.dart b/lib/screens/history/widgets/history_card_widget.dart new file mode 100644 index 00000000..4b88e4a2 --- /dev/null +++ b/lib/screens/history/widgets/history_card_widget.dart @@ -0,0 +1,326 @@ +import 'dart:io'; + +import 'package:at_common_flutter/at_common_flutter.dart'; +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_flutter/utils/init_contacts_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/contact_attachment_card.dart'; +import 'package:atsign_atmosphere_pro/services/backend_service.dart'; +import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; + +class HistoryCardWidget extends StatefulWidget { + final FileHistory? fileHistory; + final Function()? onDownloaded; + + const HistoryCardWidget({ + Key? key, + this.fileHistory, this.onDownloaded, + }) : super(key: key); + + @override + State createState() => _HistoryCardWidgetState(); +} + +class _HistoryCardWidgetState extends State { + bool isExpanded = false, + isFileSharedToGroup = false, + isDownloadAvailable = false, + isFilesAvailableOffline = true, + isOverwrite = false; + + String nickName = ''; + List existingFileNamesToOverwrite = []; + List contactList = []; + List? filesList = []; + Map _futureBuilder = {}; + + @override + void initState() { + filesList = widget.fileHistory!.fileDetails!.files; + + if (widget.fileHistory?.type == HistoryType.send) { + _loadSent(); + } else { + _loadReceived(); + } + + super.initState(); + } + + void _loadSent() async { + if (widget.fileHistory!.sharedWith != null) { + contactList = + widget.fileHistory!.sharedWith!.map((e) => e.atsign).toList(); + await getDisplayDetails(); + } + + if (widget.fileHistory!.groupName != null) { + isFileSharedToGroup = true; + } + + if (mounted) setState(() {}); + } + + void _loadReceived() async { + // checkForDownloadAvailability(); + // await isFilesAlreadyDownloaded(); + // getFutureBuilders(); + await getDisplayDetails(); + if (mounted) setState(() {}); + } + + Future getDisplayDetails() async { + AtContact? displayDetails; + + if (widget.fileHistory?.type == HistoryType.send) { + displayDetails = await getAtSignDetails(contactList[0] ?? ''); + } else { + displayDetails = await getAtSignDetails( + widget.fileHistory?.fileDetails?.sender ?? '', + ); + } + + if (contactList.length - 1 > 0) { + nickName = "${contactList[0]} and ${contactList.length - 1} others"; + } else { + if (displayDetails.tags != null) { + nickName = displayDetails.tags!['nickname'] ?? + displayDetails.tags!['name'] ?? + ''; + } + } + } + + void checkForDownloadAvailability() { + var expiryDate = + widget.fileHistory!.fileDetails!.date!.add(Duration(days: 6)); + if (expiryDate.difference(DateTime.now()) > Duration(seconds: 0)) { + isDownloadAvailable = true; + } + + // if fileList is not having any file then download icon will not be shown + var isFileUploaded = false; + widget.fileHistory!.fileDetails!.files!.forEach((FileData fileData) { + if (fileData.isUploaded!) { + isFileUploaded = true; + } + }); + + if (!isFileUploaded) { + isDownloadAvailable = false; + } + } + + Future isFilesAlreadyDownloaded() async { + widget.fileHistory!.fileDetails!.files!.forEach((element) async { + String path = BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + (element.name ?? ''); + File test = File(path); + bool fileExists = await test.exists(); + if (fileExists == false) { + if (mounted) { + setState(() { + isFilesAvailableOffline = false; + }); + } + } else { + var fileLatsModified = await test.lastModified(); + if (fileLatsModified.isBefore(widget.fileHistory!.fileDetails!.date!)) { + existingFileNamesToOverwrite.add(element.name); + if (mounted) { + setState(() { + isOverwrite = true; + }); + } + } + } + }); + } + + void getFutureBuilders() { + widget.fileHistory!.fileDetails!.files!.forEach((element) { + String filePath = BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + element.name!; + _futureBuilder[element.name] = + CommonUtilityFunctions().isFilePresent(filePath); + }); + } + + @override + Widget build(BuildContext context) { + // print("nickname: $nickName"); + // print( + // "atSign: ${widget.fileHistory?.type == HistoryType.received ? "${widget.fileHistory?.fileDetails?.sender ?? ''}" : isFileSharedToGroup || contactList.isEmpty ? '' : "${contactList[0] ?? ''}"}"); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only(left: 36, right: 18), + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: isExpanded ? Color(0xFFE9E9E9) : Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: InkWell( + onTap: () { + setState(() { + isExpanded = !isExpanded; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + isFileSharedToGroup + ? "${widget.fileHistory?.groupName ?? ''}" + : nickName, + style: TextStyle( + fontSize: 10.toFont, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + SizedBox(width: 8), + Text( + '${DateFormat("MM/dd/yy").format(widget.fileHistory!.fileDetails!.date!)}', + style: TextStyle( + fontSize: 10.toFont, + color: ColorConstants.oldSliver, + ), + ), + Container( + width: 1, + height: 8, + color: Color(0xFFD7D7D7), + margin: EdgeInsets.symmetric( + horizontal: 3, + ), + ), + Text( + '${DateFormat('kk:mm').format(widget.fileHistory!.fileDetails!.date!)}', + style: TextStyle( + fontSize: 10.toFont, + color: ColorConstants.oldSliver, + ), + ), + SizedBox(width: 6), + Container( + height: 10, + width: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: ColorConstants.lightGreen, + ), + child: Icon( + Icons.check, + size: 8, + color: ColorConstants.textGreen, + ), + ), + SizedBox(width: 4), + Container( + height: 16, + padding: EdgeInsets.symmetric( + horizontal: + widget.fileHistory?.type == HistoryType.received + ? 8 + : 16, + vertical: 2, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(33), + color: ColorConstants.lightGreen, + ), + child: Center( + child: Text( + widget.fileHistory?.type == HistoryType.received + ? "Received" + : "Sent", + style: TextStyle( + color: ColorConstants.textGreen, + fontSize: 8.toFont, + ), + ), + ), + ), + ], + ), + Text( + widget.fileHistory?.type == HistoryType.received + ? "${widget.fileHistory?.fileDetails?.sender ?? ''}" + : isFileSharedToGroup || contactList.isEmpty + ? '' + : "${contactList[0] ?? ''}", + style: TextStyle( + fontSize: 8.toFont, + color: Colors.black, + ), + ), + SizedBox(height: 7), + Row( + children: [ + Expanded( + child: Text( + widget.fileHistory?.type == HistoryType.send + ? widget.fileHistory?.notes ?? '' + : widget.fileHistory?.fileDetails?.notes ?? '', + style: TextStyle( + fontSize: 8.toFont, + color: Color(0xFF747474), + ), + ), + ), + Text( + "${filesList!.length} Files", + style: TextStyle( + fontSize: 8.toFont, + fontWeight: FontWeight.w500, + color: ColorConstants.textBlack, + ), + ), + SizedBox(width: 6), + isExpanded + ? SvgPicture.asset(AppVectors.icArrowUpOutline) + : SvgPicture.asset(AppVectors.icArrowDownOutline), + ], + ) + ], + ), + ), + ), + isExpanded + ? ListView.builder( + shrinkWrap: true, + itemCount: widget.fileHistory?.fileDetails?.files?.length ?? 0, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 4), + itemBuilder: (context, index) { + return ContactAttachmentCard( + fileTransfer: widget.fileHistory!.fileDetails!, + singleFile: widget.fileHistory!.fileDetails!.files![index], + isShowDate: false, + margin: EdgeInsets.fromLTRB(36, 6, 20, 0), + onDownloaded: widget.onDownloaded, + ); + }, + ) + : SizedBox(), + ], + ); + } +} diff --git a/lib/screens/history/widgets/received_file_list_tile.dart b/lib/screens/history/widgets/received_file_list_tile.dart index 97a27ab2..7bd92767 100644 --- a/lib/screens/history/widgets/received_file_list_tile.dart +++ b/lib/screens/history/widgets/received_file_list_tile.dart @@ -52,6 +52,7 @@ class _ReceivedFilesListTileState extends State { isDownloadAvailable = false, isFilesAvailableOfline = true, isOverwrite = false; + DateTime? sendTime; Uint8List? videoThumbnail, image; int fileSize = 0; @@ -964,7 +965,11 @@ class _ReceivedFilesListTileState extends State { } Widget getDownloadStatus(FileTransferProgress? fileTransferProgress) { - Widget spinner = CircularProgressIndicator(); + Widget spinner = CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ); if (fileTransferProgress == null) { return spinner; diff --git a/lib/screens/history/widgets/test.dart b/lib/screens/history/widgets/test.dart new file mode 100644 index 00000000..9350278f --- /dev/null +++ b/lib/screens/history/widgets/test.dart @@ -0,0 +1,67 @@ +// class ksk { +// getAllFileTransferData() async { +// setStatus(GET_ALL_FILE_DATA, Status.Loading); +// List tempReceivedHistoryLogs = []; +// +// List fileTransferAtkeys = +// await AtClientManager.getInstance().atClient.getAtKeys( +// regex: MixedConstants.FILE_TRANSFER_KEY, +// ); +// +// fileTransferAtkeys.retainWhere((element) => +// !element.key!.contains(MixedConstants.FILE_TRANSFER_ACKNOWLEDGEMENT)); +// +// bool isNewKeyAvailable = false; +// fileTransferAtkeys.forEach((AtKey atkey) { +// if (receivedItemsId[atkey.key] == null) { +// isNewKeyAvailable = true; +// } +// receivedItemsId[atkey.key] = true; +// }); +// +// if (!isNewKeyAvailable) { +// return; +// } +// +// for (var atKey in fileTransferAtkeys) { +// var isCurrentAtsign = compareAtSign( +// atKey.sharedBy!, BackendService.getInstance().currentAtSign!); +// +// if (!isCurrentAtsign && !checkRegexFromBlockedAtsign(atKey.sharedBy!)) { +// receivedItemsId[atKey.key] = true; +// +// AtValue atvalue = await AtClientManager.getInstance() +// .atClient +// .get(atKey) +// // ignore: return_of_invalid_type_from_catch_error +// .catchError((e) { +// print("error in getting atValue in getAllFileTransferData : $e"); +// //// Removing exception as called in a loop +// // ExceptionService.instance.showGetExceptionOverlay(e); +// return AtValue(); +// }); +// +// if (atvalue != null && atvalue.value != null) { +// try { +// FileTransferObject fileTransferObject = +// FileTransferObject.fromJson(jsonDecode(atvalue.value))!; +// +// FileTransfer filesModel = +// convertFiletransferObjectToFileTransfer(fileTransferObject); +// +// filesModel.sender = atKey.sharedBy!; +// +// if (filesModel.key != null) { +// tempReceivedHistoryLogs.insert(0, filesModel); +// } +// } catch (e) { +// print('error in getAllFileTransferData file model conversion: $e'); +// } +// } +// } +// } +// +// receivedHistoryLogs = tempReceivedHistoryLogs; +// setStatus(GET_ALL_FILE_DATA, Status.Done); +// } +// } \ No newline at end of file diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index a8e8c94c..9ec56245 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -267,7 +267,7 @@ class _HomeState extends State { await _backendService .checkToOnboard(); }), - // SizedBox(height: 15.toHeight), + SizedBox(height: 24.toHeight), CustomButton( onPressed: () { CommonUtilityFunctions() @@ -318,7 +318,7 @@ class _HomeState extends State { children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( - ColorConstants.redText)), + Color.fromARGB(255, 49, 44, 43))), SizedBox( height: 20, ), diff --git a/lib/screens/my_files/files_detail_screen.dart b/lib/screens/my_files/files_detail_screen.dart new file mode 100644 index 00000000..220d2558 --- /dev/null +++ b/lib/screens/my_files/files_detail_screen.dart @@ -0,0 +1,503 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/file_category_type.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/search_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/sliver_grid_delegate.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/widgets/downloads_folders.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/widgets/recents.dart'; +import 'package:atsign_atmosphere_pro/services/backend_service.dart'; +import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; +import 'package:atsign_atmosphere_pro/utils/app_utils.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/file_utils.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/my_files_provider.dart'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class FilesDetailScreen extends StatefulWidget { + final FileType? type; + final bool? autoFocus; + + const FilesDetailScreen({ + Key? key, + required this.type, + this.autoFocus, + }) : super(key: key); + + @override + State createState() => _FilesDetailScreenState(); +} + +class _FilesDetailScreenState extends State { + bool isGridType = true; + late TextEditingController searchController; + late MyFilesProvider provider; + Uint8List? videoThumbnail; + + @override + void initState() { + searchController = TextEditingController(); + provider = context.read(); + super.initState(); + provider.changeTypeSelected(widget.type); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorConstants.background, + appBar: AppBar( + backgroundColor: ColorConstants.background, + title: Text( + widget.type != null ? "${widget.type!.text}" : "All Files", + style: TextStyle( + fontSize: 25, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + centerTitle: false, + actions: [ + Container( + margin: EdgeInsets.symmetric( + vertical: 6, + horizontal: 21, + ), + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: 6, + ), + decoration: BoxDecoration( + color: ColorConstants.dividerGrey, + borderRadius: BorderRadius.circular(30), + ), + child: Row( + children: [ + InkWell( + onTap: () { + setState(() { + if (!isGridType) { + isGridType = !isGridType; + } + }); + }, + child: Container( + height: 34, + width: 34, + decoration: BoxDecoration( + color: isGridType ? Colors.white : Colors.transparent, + borderRadius: BorderRadius.circular(17), + ), + padding: EdgeInsets.all(8), + child: Image.asset( + isGridType + ? ImageConstants.icGridTypeActivate + : ImageConstants.icGridType, + ), + ), + ), + InkWell( + onTap: () { + setState(() { + if (isGridType) { + isGridType = !isGridType; + } + }); + }, + child: Container( + height: 34, + width: 34, + decoration: BoxDecoration( + color: isGridType ? Colors.transparent : Colors.white, + borderRadius: BorderRadius.circular(17), + ), + padding: EdgeInsets.all(8), + child: Image.asset( + isGridType + ? ImageConstants.icListType + : ImageConstants.icListTypeActivate, + ), + ), + ), + ], + ), + ) + ], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 32, vertical: 28), + child: SearchWidget( + controller: searchController, + autoFocus: widget.autoFocus, + borderColor: Colors.white, + backgroundColor: Colors.white, + hintText: "Search", + onChange: (value) { + provider.searchFileByKeyword( + key: value, + type: widget.type, + ); + }, + hintStyle: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.zero, + ), + ), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.only(bottom: 75), + child: Consumer( + builder: (context, provider, _) { + final files = provider.displayFiles; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + isGridType + ? _buildGridView(files) + : _buildListView(files), + Padding( + padding: EdgeInsets.only(top: 75), + child: Text( + "${files.length} items", + style: TextStyle( + fontSize: 13, + color: ColorConstants.textGrey, + ), + ), + ) + ], + ); + }, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildGridView(List files) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 36), + child: GridView.builder( + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: files.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight( + crossAxisCount: 4, + crossAxisSpacing: 24, + mainAxisSpacing: 22, + height: 104, + ), + itemBuilder: (context, index) { + return Column( + children: [ + Container( + height: 80, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + ), + child: CommonUtilityFunctions().interactableThumbnail( + files[index].fileName?.split(".").last ?? "", + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!, + files[index], + () async { + await FileUtils.deleteFile( + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!, + fileTransferId: files[index].fileTransferId, + onComplete: () { + files.removeAt(index); + }, + ); + if (mounted) { + Navigator.pop(context); + } + setState(() {}); + }, + ), + ), + Spacer(), + Flexible( + child: Text( + files[index].fileName ?? "", + style: TextStyle( + color: Colors.black, + fontSize: 8.toFont, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + ) + ], + ); + }, + ), + ); + } + + Widget _buildListView(List files) { + return ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 32), + physics: NeverScrollableScrollPhysics(), + itemCount: files.length, + separatorBuilder: (context, index) => SizedBox(height: 10), + itemBuilder: (context, index) { + final date = DateTime.parse(files[index].date ?? "").toLocal(); + final shortDate = DateFormat('MM/dd/yy').format(date); + final time = DateFormat('kk:mm').format(date); + + // late FileTransfer fileTransfer; + // bool isDownloaded = false; + + // for (var filetransfer in provider.myFiles) { + // if (filetransfer.key == files[index].fileTransferId) { + // fileTransfer = filetransfer; + // break; + // } + // } + + return Slidable( + actionPane: const SlidableDrawerActionPane(), + actionExtentRatio: 0.11, + secondaryActions: [ + // Consumer( + // builder: (_c, provider, _) { + // var fileTransferProgress = + // provider.receivedFileProgress[fileTransfer.key]; + + // return CommonUtilityFunctions() + // .checkForDownloadAvailability(fileTransfer) + // ? fileTransferProgress != null + // ? CommonUtilityFunctions().getDownloadStatus( + // fileTransferProgress, + // ) + // : isDownloaded + // ? SvgPicture.asset(AppVectors.icCloudDownloaded) + // : InkWell( + // onTap: () async { + // var res = await downloadFiles( + // fileTransfer, + // fileName: files[index].fileName, + // ); + + // setState(() { + // isDownloaded = res; + // }); + // }, + // child: SvgPicture.asset( + // AppVectors.icDownloadFile, + // ), + // ) + // : SizedBox(); + // }, + // ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + AppVectors.icDownloadFile, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + await openFilePath( + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!); + }, + child: SvgPicture.asset( + AppVectors.icSendFile, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + await FileUtils.deleteFile( + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!, + fileTransferId: files[index].fileTransferId, + onComplete: () { + files.removeAt(index); + }, + ); + setState(() {}); + }, + child: SvgPicture.asset( + AppVectors.icDeleteFile, + ), + ), + ), + ], + child: InkWell( + onTap: () async { + await FileUtils.openFile( + path: BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!, + extension: files[index].fileName?.split(".").last ?? "", + onDelete: () async { + await FileUtils.deleteFile( + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!); + if (mounted) { + Navigator.pop(context); + } + setState(() {}); + }, + fileDetail: files[index], + ); + }, + child: Container( + key: UniqueKey(), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: [ + Container( + height: 49, + width: 38, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + ), + child: Center( + child: CommonUtilityFunctions().interactableThumbnail( + files[index].fileName?.split(".").last ?? "", + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!, + files[index], () async { + await FileUtils.deleteFile( + BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + files[index].fileName!, + fileTransferId: files[index].fileTransferId, + onComplete: () { + files.removeAt(index); + }, + ); + + if (mounted) { + Navigator.pop(context); + } + setState(() {}); + }), + ), + ), + SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + "${files[index].fileName}", + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + ), + SizedBox(width: 12), + Text( + "$shortDate", + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ), + Container( + width: 1, + height: 8, + color: Color(0xFFD7D7D7), + margin: EdgeInsets.symmetric( + horizontal: 3, + ), + ), + Text( + "$time", + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ), + ], + ), + SizedBox(height: 7), + Text( + "${(files[index].contactName ?? '').split("@")[1]}", + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + SizedBox(height: 1), + Row( + children: [ + Expanded( + child: Text( + "${files[index].contactName}", + style: TextStyle( + color: Colors.black, + fontSize: 10, + ), + ), + ), + Text( + AppUtils.getFileSizeString( + bytes: files[index].size ?? 0, + decimals: 2, + ), + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ) + ], + ), + ], + ), + ) + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/screens/my_files/image_view_widget.dart b/lib/screens/my_files/image_view_widget.dart new file mode 100644 index 00000000..eb49986c --- /dev/null +++ b/lib/screens/my_files/image_view_widget.dart @@ -0,0 +1,203 @@ +import 'dart:io'; + +import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; +import 'package:atsign_atmosphere_pro/utils/app_utils.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; + +class ImageViewWidget extends StatelessWidget { + final FilesDetail image; + + const ImageViewWidget({ + Key? key, + required this.image, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final date = DateTime.parse(image.date ?? "").toLocal(); + final shortDate = DateFormat('dd/MM/yy').format(date); + final time = DateFormat('HH:mm').format(date); + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 31, + top: 16, + bottom: 16, + ), + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () { + Navigator.of(context).pop(); + }, + child: Icon( + Icons.close, + color: Colors.white, + size: 33, + ), + ), + ), + Expanded( + child: Container( + // height: double.infinity, + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 33), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + image: DecorationImage( + image: FileImage( + File(image.filePath ?? ''), + ), + fit: BoxFit.cover, + ), + ), + ), + ), + SizedBox(height: 24), + Align( + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 48, + height: 48, + child: SvgPicture.asset( + AppVectors.icDownloadFile, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + child: SizedBox( + width: 48, + height: 48, + child: SvgPicture.asset( + AppVectors.icSendFile, + ), + ), + ), + SizedBox( + width: 48, + height: 48, + child: SvgPicture.asset( + AppVectors.icDeleteFile, + ), + ), + ], + ), + ), + SizedBox(height: 32), + Container( + height: 175, + width: double.infinity, + padding: EdgeInsets.fromLTRB(24, 16, 18, 16), + margin: EdgeInsets.symmetric(horizontal: 33), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.topRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "$shortDate", + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ), + Container( + width: 1, + height: 8, + color: Color(0xFFD7D7D7), + margin: EdgeInsets.symmetric( + horizontal: 3, + ), + ), + Text( + "$time", + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ), + ], + ), + ), + Text( + "${image.fileName}", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + SizedBox(height: 5), + Text( + AppUtils.getFileSizeString( + bytes: image.size ?? 0, + decimals: 2, + ), + style: TextStyle( + fontSize: 10, + color: ColorConstants.oldSliver, + ), + ), + SizedBox(height: 10), + Text( + "${(image.contactName ?? '').split("@")[1]}", + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 12, + ), + ), + SizedBox(height: 1), + Text( + "${image.contactName}", + style: TextStyle( + color: Colors.black, + fontSize: 10, + ), + ), + SizedBox(height: 13), + Text( + "Message:", + style: TextStyle( + color: ColorConstants.textLightGray, + fontWeight: FontWeight.w500, + fontSize: 10, + ), + ), + SizedBox(height: 5), + Text( + "${image.date}", + style: TextStyle( + color: ColorConstants.textLightGray, + fontWeight: FontWeight.w500, + fontSize: 10, + ), + ), + ], + ), + ), + SizedBox(height: 16), + ], + ), + ); + } +} diff --git a/lib/screens/my_files/my_files.dart b/lib/screens/my_files/my_files.dart index e683b666..7455c1fb 100644 --- a/lib/screens/my_files/my_files.dart +++ b/lib/screens/my_files/my_files.dart @@ -139,7 +139,12 @@ class _MyFilesState extends State with TickerProviderStateMixin { ), body: SingleChildScrollView( child: (isLoading) - ? Center(child: CircularProgressIndicator()) + ? Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + )) : Consumer( builder: (BuildContext _context, _provider, _) { if (_provider.tabs.length != tabs.length) { diff --git a/lib/screens/my_files/my_files_screen.dart b/lib/screens/my_files/my_files_screen.dart new file mode 100644 index 00000000..5076fe3c --- /dev/null +++ b/lib/screens/my_files/my_files_screen.dart @@ -0,0 +1,561 @@ +import 'dart:io'; + +import 'package:atsign_atmosphere_pro/data_models/enums/file_category_type.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/app_bar_custom.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/option_header_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/provider_handler.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/search_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/history/widgets/edit_bottomsheet.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/files_detail_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/widgets/downloads_folders.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/widgets/recents.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/widgets/videos.dart'; +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/utils/app_utils.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/my_files_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +import '../../services/backend_service.dart'; + +class MyFilesScreen extends StatefulWidget { + @override + _MyFilesScreenState createState() => _MyFilesScreenState(); +} + +class _MyFilesScreenState extends State { + bool isOpen = false; + List tabs = []; + List tabNames = []; + + bool isLoading = false; + Type runtimeType = Videos; + late MyFilesProvider provider; + late TextEditingController searchController; + + @override + void initState() { + provider = context.read(); + searchController = TextEditingController(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorConstants.background, + appBar: AppBarCustom( + height: 130, + title: "Files", + ), + body: _buildBody(), + ); + } + + Widget _buildBody() { + return SingleChildScrollView( + physics: ClampingScrollPhysics(), + padding: EdgeInsets.only(left: 34, bottom: 32), + child: ProviderHandler( + load: (provider) async { + await provider.getAllFiles(); + await provider.getMyFilesRecords(); + }, + functionName: 'all_files', + showError: false, + successBuilder: (provider) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only( + top: 18.toHeight, + right: 32.toWidth, + ), + child: SearchWidget( + controller: searchController, + readOnly: true, + borderColor: Colors.white, + backgroundColor: Colors.white, + hintText: "Search", + onTap: () { + navigateToFilesDetail(autoFocus: true); + }, + hintStyle: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.zero, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Text( + "Recent", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ), + provider.recentFile.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(bottom: 20), + child: SizedBox( + height: 112, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: provider.recentFile.length, + padding: EdgeInsets.only(right: 32), + physics: ClampingScrollPhysics(), + separatorBuilder: (context, index) => SizedBox( + width: 16, + ), + itemBuilder: (context, index) { + return SizedBox( + width: 66, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Container( + height: 85, + width: 66, + decoration: BoxDecoration( + color: ColorConstants.lightSliver, + borderRadius: + BorderRadius.circular(5), + ), + child: thumbnail( + provider.recentFile[index].fileName + ?.split(".") + .last ?? + "", + BackendService.getInstance() + .downloadDirectory! + .path + + Platform.pathSeparator + + provider + .recentFile[index].fileName!, + ), + ), + Align( + alignment: Alignment.topRight, + child: SvgPicture.asset( + AppVectors.icBannerOverlay, + ), + ), + ], + ), + SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8), + child: Text( + "${provider.recentFile[index].filePath!.split(Platform.pathSeparator).last}", + style: TextStyle( + color: Colors.black, + fontSize: 10, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + ), + ), + ) + : SizedBox(), + Text( + "Category", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + SizedBox( + width: double.infinity, + child: Wrap( + children: _generateChildren(), + ), + ), + SizedBox(height: 32), + Container( + height: 56, + width: double.infinity, + margin: EdgeInsets.only(left: 2, right: 36), + padding: EdgeInsets.symmetric( + horizontal: 28, + vertical: 18, + ), + decoration: BoxDecoration( + color: ColorConstants.raisinBlack, + borderRadius: BorderRadius.circular(10), + ), + child: InkWell( + onTap: () { + navigateToFilesDetail(); + }, + child: Row( + children: [ + Text( + "All Files", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + SizedBox(width: 8), + Text( + "${context.watch().allFiles.length}", + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: ColorConstants.gray2, + ), + ), + Spacer(), + SvgPicture.asset( + AppVectors.icArrowRight, + ), + ], + ), + ), + ), + ], + ); + }, + ), + ); + } + + Widget _generateItem(FileType fileType) { + return InkWell( + onTap: () { + navigateToFilesDetail(type: fileType); + }, + child: Container( + width: (MediaQuery.of(context).size.width - 110) / 3, + height: 100, + margin: EdgeInsets.only(right: 20, top: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + gradient: LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: fileType.backgroundColor, + ), + ), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + fileType.image, + ), + Text( + fileType.text, + style: TextStyle( + color: Colors.white, + fontSize: 8.toFont, + ), + ), + ], + ), + ), + ), + ); + } + + List _generateChildren() { + List items = []; + + for (int i = 0; i < FileType.values.length; i++) { + items.add( + _generateItem(FileType.values[i]), + ); + } + + return items; + } + + void navigateToFilesDetail({ + FileType? type, + bool? autoFocus, + }) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => FilesDetailScreen( + type: type, + autoFocus: autoFocus, + ), + ), + ); + } + + Widget buildBody() { + return Column( + children: [ + OptionHeaderWidget( + controller: searchController, + onSearch: (content) { + provider.setFileSearchText(content); + }, + onReloadCallback: () async { + await provider.getMyFilesRecords(); + await provider.getAllFiles(); + }, + searchOffCallBack: () { + searchController.clear(); + provider.setFileSearchText(''); + }, + filterWidget: Consumer( + builder: (context, provider, _) { + return DropdownButtonHideUnderline( + child: Padding( + padding: EdgeInsets.zero, + child: DropdownButton( + value: provider.typeSelected, + icon: SvgPicture.asset( + AppVectors.icArrowDown, + ), + isExpanded: true, + underline: null, + alignment: AlignmentDirectional.bottomEnd, + hint: Text( + "All", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorConstants.grey, + ), + ), + items: FileType.values.map( + (key) { + return DropdownMenuItem( + value: key, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 12, + ), + child: Row( + children: [ + Expanded( + child: Text( + key.text, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: ColorConstants.grey, + ), + ), + ), + provider.typeSelected == key + ? SvgPicture.asset( + AppVectors.icCheck, + ) + : SvgPicture.asset( + AppVectors.icUnCheck, + ), + ], + ), + ), + Container( + color: ColorConstants.sidebarTileSelected, + height: 2, + width: double.infinity, + ) + ], + ), + ); + }, + ).toList(), + selectedItemBuilder: (BuildContext context) { + return FileType.values.map( + (key) { + return DropdownMenuItem( + value: key, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + key.text, + style: TextStyle( + fontSize: 12.toFont, + fontWeight: FontWeight.w600, + color: ColorConstants.grey, + ), + ), + ], + ), + ); + }, + ).toList(); + }, + onChanged: (type) { + provider.changeTypeSelected(type!); + }, + borderRadius: BorderRadius.circular(10), + ), + ), + ); + }, + ), + ), + Expanded( + child: ProviderHandler( + load: (provider) async { + await provider.getMyFilesRecords(); + await provider.getAllFiles(); + }, + functionName: 'all_files', + showError: false, + successBuilder: (provider) { + final listFile = provider.displayFiles.where( + (element) { + return (element.fileName?.toLowerCase() ?? '').contains( + provider.fileSearchText.toLowerCase(), + ); + }, + ).toList(); + + return (listFile.isEmpty) + ? Center( + child: Text( + TextStrings().noFilesRecieved, + style: TextStyle( + fontSize: 15.toFont, + fontWeight: FontWeight.normal, + ), + ), + ) + : Scrollbar( + child: ListView.builder( + itemCount: listFile.length, + physics: AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only( + top: 24.toHeight, + left: 28, + right: 28, + bottom: 100, + ), + itemBuilder: (context, index) { + return InkWell( + onTap: () async { + await openFilePath( + listFile[index].filePath!, + ); + }, + onLongPress: () { + deleteFile( + listFile[index].filePath!, + fileTransferId: listFile[index].fileTransferId, + ); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: ColorConstants.sidebarTextUnselected, + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + margin: EdgeInsets.only(bottom: 12), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + "${listFile[index].fileName}", + maxLines: 2, + style: TextStyle( + color: ColorConstants.grayText, + fontSize: 12.toFont, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox(height: 2), + Text( + AppUtils.getFileSizeString( + bytes: listFile[index].size ?? 0, + decimals: 2, + ), + style: TextStyle( + color: ColorConstants + .sidebarTextUnselected, + fontSize: 9.toFont, + fontWeight: FontWeight.w500, + ), + ) + ], + ), + ), + Icon(Icons.remove_red_eye_outlined), + ], + ), + ), + ); + }, + ), + ); + }, + ), + ), + ], + ); + } + + deleteFile(String filePath, {String? fileTransferId}) async { + await showModalBottomSheet( + context: NavService.navKey.currentContext!, + backgroundColor: Colors.white, + builder: (context) => EditBottomSheet( + onConfirmation: () async { + var file = File(filePath); + if (await file.exists()) { + file.deleteSync(); + } + if (fileTransferId != null) { + await Provider.of( + NavService.navKey.currentContext!, + listen: false) + .removeParticularFile(fileTransferId, + filePath.split(Platform.pathSeparator).last); + } + await provider.getAllFiles(); + }, + deleteMessage: TextStrings.deleteFileConfirmationMsgMyFiles, + ), + ); + } +} diff --git a/lib/screens/my_files/widgets/recents.dart b/lib/screens/my_files/widgets/recents.dart index 56e7b90b..d502cad8 100644 --- a/lib/screens/my_files/widgets/recents.dart +++ b/lib/screens/my_files/widgets/recents.dart @@ -8,6 +8,7 @@ import 'package:atsign_atmosphere_pro/services/backend_service.dart'; import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; import 'package:atsign_atmosphere_pro/utils/file_types.dart'; +import 'package:atsign_atmosphere_pro/utils/file_utils.dart'; import 'package:atsign_atmosphere_pro/utils/images.dart'; import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; import 'package:atsign_atmosphere_pro/view_models/my_files_provider.dart'; @@ -59,32 +60,10 @@ class _RecentsState extends State { } } -deleteFile(String filePath, {String? fileTransferId}) async { - await showModalBottomSheet( - context: NavService.navKey.currentContext!, - backgroundColor: Colors.white, - builder: (context) => EditBottomSheet( - onConfirmation: () async { - var file = File(filePath); - if (await file.exists()) { - file.deleteSync(); - } - if (fileTransferId != null) { - await Provider.of(NavService.navKey.currentContext!, - listen: false) - .removeParticularFile( - fileTransferId, filePath.split(Platform.pathSeparator).last); - } - }, - deleteMessage: TextStrings.deleteFileConfirmationMsgMyFiles, - ), - ); -} - Widget fileCard(String? title, String? filePath, {String? fileTransferId}) { return InkWell( onLongPress: () { - deleteFile(filePath!, fileTransferId: fileTransferId); + FileUtils.deleteFile(filePath!, fileTransferId: fileTransferId); }, child: Column( children: [ @@ -198,7 +177,7 @@ Widget thumbnail(String extension, String path) { // await openDownloadsFolder(context); }, child: Container( - padding: EdgeInsets.only(left: 10), + // padding: EdgeInsets.only(left: 10), height: 50.toHeight, width: 50.toWidth, child: Image.asset( diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart new file mode 100644 index 00000000..16f1a3ed --- /dev/null +++ b/lib/screens/settings/settings_screen.dart @@ -0,0 +1,196 @@ +import 'package:at_backupkey_flutter/widgets/backup_key_widget.dart'; +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:atsign_atmosphere_pro/routes/route_names.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/app_bar_custom.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/side_bar.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/switch_at_sign.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/blocked_contact_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/settings/widgets/settings_buttons.dart'; +import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; +import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/constants.dart'; +import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; +import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({Key? key}) : super(key: key); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + final List optionTitle = [ + TextStrings().blockedAtSign, + TextStrings().backUpKeys, + TextStrings().switchatSign, + TextStrings().deleteAtsigns, + TextStrings().faqs, + TextStrings().contactUs, + TextStrings().termsAppBar, + ]; + + final List optionIcons = [ + AppVectors.icSettingBlock, + AppVectors.icSettingBackup, + AppVectors.icSettingSwitch, + AppVectors.icSettingDelete, + AppVectors.icSettingFAQ, + AppVectors.icSettingContactUs, + AppVectors.icSettingPrivacy, + ]; + + void switchAtsign() async { + var atSignList = await KeychainUtil.getAtsignList(); + await showModalBottomSheet( + context: NavService.navKey.currentContext!, + backgroundColor: Colors.transparent, + builder: (context) => AtSignBottomSheet( + atSignList: atSignList, + ), + ); + } + + @override + void initState() { + super.initState(); + + _initPackageInfo(); + } + + PackageInfo _packageInfo = PackageInfo( + appName: 'Unknown', + packageName: 'Unknown', + version: 'Unknown', + buildNumber: 'Unknown', + ); + + Future _initPackageInfo() async { + final PackageInfo info = await PackageInfo.fromPlatform(); + if (mounted) { + setState(() { + _packageInfo = info; + }); + } + } + + @override + Widget build(BuildContext context) { + SizeConfig().init(context); + return Scaffold( + backgroundColor: ColorConstants.background, + // extendBodyBehindAppBar: true, + appBar: AppBarCustom( + height: 330, + title: "Settings", + ), + // extendBody: true, + // drawerScrimColor: Colors.transparent, + // endDrawer: SideBarWidget( + // isExpanded: true, + // ), + body: SafeArea( + child: ListView( + padding: const EdgeInsets.fromLTRB(31, 0, 31, 24), + children: [ + SizedBox(height: 28), + SettingsButton( + buttonText: optionTitle[0], + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) { + return BlockedContactScreen(); + }, + ), + ); + }, + image: optionIcons[0], + ), + SizedBox(height: 32), + SettingsButton( + buttonText: optionTitle[1], + onPressed: () async { + BackupKeyWidget( + atsign: AtClientManager.getInstance() + .atClient + .getCurrentAtSign()!, + ).showBackupDialog(context); + }, + image: optionIcons[1], + ), + SizedBox(height: 16), + SettingsButton( + buttonText: optionTitle[2], + onPressed: switchAtsign, + image: optionIcons[2], + ), + SizedBox(height: 16), + SettingsButton( + buttonText: optionTitle[3], + onPressed: () async { + CommonUtilityFunctions().showResetAtsignDialog(); + }, + image: optionIcons[3], + ), + SizedBox(height: 33), + SettingsButton( + buttonText: optionTitle[4], + onPressed: () { + Navigator.pushNamed( + context, + Routes.FAQ_SCREEN, + ); + }, + image: optionIcons[4], + ), + SizedBox(height: 16), + SettingsButton( + buttonText: optionTitle[5], + onPressed: () async { + await launchUrl( + Uri( + scheme: 'mailto', + path: 'atmospherepro@atsign.com', + ), + ); + }, + image: optionIcons[5], + ), + SizedBox(height: 16), + SettingsButton( + buttonText: optionTitle[6], + onPressed: () { + Navigator.pushNamed( + context, + Routes.WEBSITE_SCREEN, + arguments: { + 'title': optionTitle[6], + 'url': MixedConstants.PRIVACY_POLICY + }, + ); + }, + image: optionIcons[6], + ), + SizedBox( + height: 12, + ), + Text( + 'App Version ${_packageInfo.version} (${_packageInfo.buildNumber})', + style: CustomTextStyles.black12.copyWith( + color: ColorConstants.oldSliver, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/settings/widgets/settings_buttons.dart b/lib/screens/settings/widgets/settings_buttons.dart new file mode 100644 index 00000000..39239968 --- /dev/null +++ b/lib/screens/settings/widgets/settings_buttons.dart @@ -0,0 +1,49 @@ +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class SettingsButton extends StatelessWidget { + const SettingsButton({ + Key? key, + required this.onPressed, + required this.buttonText, + required this.image, + }) : super(key: key); + + final Function()? onPressed; + final String? buttonText; + final String image; + + @override + Widget build(BuildContext context) { + return MaterialButton( + elevation: 0, + onPressed: onPressed, + color: ColorConstants.jetColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), + child: Row( + children: [ + SvgPicture.asset( + image, + color: ColorConstants.textBoxBg, + height: 27, + width: 27, + ), + SizedBox(width: 24), + Text( + buttonText.toString(), + style: CustomTextStyles.whiteMedium18.copyWith( + color: ColorConstants.textBoxBg, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/trusted_contacts/widgets/remove_trusted_contact_dialog.dart b/lib/screens/trusted_contacts/widgets/remove_trusted_contact_dialog.dart index 392299e6..688920c5 100644 --- a/lib/screens/trusted_contacts/widgets/remove_trusted_contact_dialog.dart +++ b/lib/screens/trusted_contacts/widgets/remove_trusted_contact_dialog.dart @@ -134,7 +134,11 @@ class _RemoveTrustedContactState extends State { children: [ (Provider.of(context) .trustedContactOperation) - ? CircularProgressIndicator() + ? CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ) : CustomButton( isOrange: true, buttonText: TextStrings().yes, diff --git a/lib/screens/welcome_screen/welcome_screen.dart b/lib/screens/welcome_screen/welcome_screen.dart index 2a2cf670..e486271f 100644 --- a/lib/screens/welcome_screen/welcome_screen.dart +++ b/lib/screens/welcome_screen/welcome_screen.dart @@ -1,15 +1,20 @@ import 'package:at_contacts_flutter/utils/init_contacts_service.dart'; import 'package:at_contacts_group_flutter/services/group_service.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/error_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/linear_progress_bar.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/contact_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/history/transfer_history_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/my_files_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/settings/settings_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/welcome_screen/widgets/bottom_navigation_widget.dart'; import 'package:atsign_atmosphere_pro/screens/welcome_screen/widgets/welcome_sceen_home.dart'; -import 'package:atsign_atmosphere_pro/screens/welcome_screen/widgets/welcome_screen_received_files.dart'; -import 'package:atsign_atmosphere_pro/services/overlay_service.dart'; import 'package:atsign_atmosphere_pro/utils/constants.dart'; -import 'package:atsign_atmosphere_pro/screens/common_widgets/app_bar.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/side_bar.dart'; import 'package:atsign_atmosphere_pro/services/backend_service.dart'; import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:atsign_atmosphere_pro/view_models/file_progress_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/file_transfer_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/internet_connectivity_checker.dart'; @@ -17,10 +22,15 @@ import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../utils/text_strings.dart'; -import '../common_widgets/side_bar.dart'; -import '../../view_models/file_transfer_provider.dart'; class WelcomeScreen extends StatefulWidget { + final int? indexBottomBarSelected; + + const WelcomeScreen({ + Key? key, + this.indexBottomBarSelected, + }) : super(key: key); + @override _WelcomeScreenState createState() => _WelcomeScreenState(); } @@ -30,8 +40,9 @@ class _WelcomeScreenState extends State { BackendService backendService = BackendService.getInstance(); HistoryProvider? historyProvider; bool isExpanded = true; - int _selectedBottomNavigationIndex = 0; - late FileTransferProvider _fileTransferProvider; + + late WelcomeScreenProvider welcomeScreenProvider; + // 0-Sending, 1-Success, 2-Error List transferStatus = [ SizedBox(), @@ -47,18 +58,21 @@ class _WelcomeScreenState extends State { ) ]; String? currentAtSign; + @override void initState() { - _fileTransferProvider = - Provider.of(context, listen: false); + welcomeScreenProvider = context.read(); setAtSign(); - listenForFlushBarStatus(); WidgetsBinding.instance.addPostFrameCallback((_) async { WelcomeScreenProvider().isExpanded = false; await initPackages(); }); + if (widget.indexBottomBarSelected != null) { + welcomeScreenProvider + .changeBottomNavigationIndex(widget.indexBottomBarSelected!); + } super.initState(); } @@ -67,17 +81,6 @@ class _WelcomeScreenState extends State { super.dispose(); } - listenForFlushBarStatus() { - FileTransferProvider().flushBarStatusStream.listen((flushbarStatus) async { - OverlayService.instance.showOverlay( - flushbarStatus, - errorMessage: flushbarStatus == FLUSHBAR_STATUS.FAILED - ? _fileTransferProvider.error[_fileTransferProvider.SEND_FILES] - : null, - ); - }); - } - setAtSign() async { currentAtSign = await backendService.getAtSign(); setState(() {}); @@ -89,79 +92,255 @@ class _WelcomeScreenState extends State { await GroupService().fetchGroupsAndContacts(); } - void _onBottomNavigationSelect(int index) { - setState(() { - _selectedBottomNavigationIndex = index; - }); - } - static List _bottomSheetWidgetOptions = [ WelcomeScreenHome(), - WelcomeScreenReceivedFiles() + ContactScreen(), + MyFilesScreen(), + TransferHistoryScreen(), + SettingsScreen() ]; @override Widget build(BuildContext context) { - return Container( - color: ColorConstants.scaffoldColor, - child: SafeArea( - child: Scaffold( - bottomNavigationBar: BottomNavigationBar( - elevation: 0, - selectedLabelStyle: TextStyle( - fontSize: 12.toFont, - fontWeight: FontWeight.normal, - ), - unselectedLabelStyle: TextStyle( - fontSize: 12.toFont, - fontWeight: FontWeight.normal, + return Scaffold( + bottomNavigationBar: customBottomNavigationBar(), + key: _scaffoldKey, + backgroundColor: ColorConstants.background, + // extendBody: true, + // drawerScrimColor: Colors.transparent, + // endDrawer: SideBarWidget( + // isExpanded: true, + // ), + body: SafeArea( + bottom: false, + child: Column( + children: [ + Consumer( + builder: (_c, welcomeProvider, _) { + return !welcomeProvider.isShowOverlay + ? SafeArea( + bottom: false, + child: Container( + height: 24, + width: double.infinity, + child: StreamBuilder( + stream: FileTransferProvider().flushBarStatusStream, + builder: (context, snapshot) { + final flushbarStatus = + snapshot.data ?? FLUSHBAR_STATUS.SENDING; + + if (flushbarStatus == FLUSHBAR_STATUS.DONE) { + Future.delayed( + const Duration(seconds: 3), + () { + welcomeScreenProvider.changeOverlayStatus(true); + }, + ); + return Material( + child: Container( + width: double.infinity, + height: double.infinity, + color: ColorConstants.successGreen, + child: Center( + child: Text( + 'Success!🎉 ', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ); + } else if (flushbarStatus == FLUSHBAR_STATUS.FAILED) { + Future.delayed( + const Duration(seconds: 3), + () { + welcomeScreenProvider.changeOverlayStatus(true); + }, + ); + return Material( + child: Container( + width: double.infinity, + height: double.infinity, + color: ColorConstants.redAlert, + child: Center( + child: Text( + 'Something went wrong! ⚠️', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ); + } else { + return Consumer( + builder: (_c, provider, _) { + var percent = (provider.sentFileTransferProgress + ?.percent ?? + 30) / + 100; + return ProgressBarAnimation( + value: percent, + gradient: const LinearGradient( + colors: [ + Color(0xFFF05E3F), + Color(0xFFEAA743), + ], + ), + // backgroundColor: Colors.red, + ); + }, + ); + } + }, + ), + ), + ) + : SizedBox(); + }, ), - items: [ - BottomNavigationBarItem( - icon: Icon(Icons.home, size: 20.toFont), - label: 'Home', - ), - BottomNavigationBarItem( - icon: Icon(Icons.import_export, size: 20.toFont), - label: 'Received', + Expanded( + child: Consumer( + builder: (_c, provider, widget) { + if (provider.isInternetAvailable) { + return _bottomSheetWidgetOptions[context + .watch() + .selectedBottomNavigationIndex]; + } else { + return ErrorScreen( + TextStrings.noInternet, + ); + } + }, ), - ], - currentIndex: _selectedBottomNavigationIndex, - selectedItemColor: Colors.amber[800], - onTap: _onBottomNavigationSelect, - ), - key: _scaffoldKey, - backgroundColor: ColorConstants.scaffoldColor, - appBar: _selectedBottomNavigationIndex == 0 - ? (SizeConfig().isTablet(context) - ? null - : CustomAppBar( - showLeadingicon: true, - )) - : CustomAppBar( - showMenu: true, - showBackButton: false, - showTrailingButton: true, - showTitle: true, - showClosedBtnText: false, - title: 'Received Files'), - extendBody: true, - drawerScrimColor: Colors.transparent, - endDrawer: SideBarWidget( - isExpanded: true, - ), - body: Consumer( - builder: (_c, provider, widget) { - if (provider.isInternetAvailable) { - return _bottomSheetWidgetOptions[_selectedBottomNavigationIndex]; - } else { - return ErrorScreen( - TextStrings.noInternet, - ); - } - }), + ), + ], ), ), ); } + + Widget customBottomNavigationBar() { + return Consumer( + builder: (context, provider, _) { + return Selector( + selector: (context, provider) => + provider.selectedBottomNavigationIndex, + builder: (context, selectedBottomNavigationIndex, _) { + return Container( + height: 74, + margin: EdgeInsets.fromLTRB( + 16.toWidth, + 0, + 16.toWidth, + 16 + MediaQuery.of(context).padding.bottom, + ), + padding: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(76), + ), + child: Padding( + padding: const EdgeInsets.only(left: 15, right: 15.0), + child: Row( + children: [ + Expanded( + child: BottomNavigationWidget( + iconActivate: ImageConstants.icUserActivate, + iconInactivate: ImageConstants.icUserInactivate, + title: "Contacts", + index: 1, + indexSelected: selectedBottomNavigationIndex, + onTap: (index) { + welcomeScreenProvider + .changeBottomNavigationIndex(index); + }, + ), + ), + Expanded( + child: BottomNavigationWidget( + iconActivate: ImageConstants.icFileActivate, + iconInactivate: ImageConstants.icFileInactivate, + title: "Files", + index: 2, + indexSelected: selectedBottomNavigationIndex, + onTap: (index) { + welcomeScreenProvider + .changeBottomNavigationIndex(index); + }, + ), + ), + Expanded( + child: BottomNavigationWidget( + iconActivate: ImageConstants.icSendActivate, + iconInactivate: ImageConstants.icSendInactivate, + index: 0, + indexSelected: selectedBottomNavigationIndex, + onTap: (index) { + welcomeScreenProvider + .changeBottomNavigationIndex(index); + }, + ), + ), + Expanded( + child: BottomNavigationWidget( + iconActivate: ImageConstants.icHistoryActivate, + iconInactivate: ImageConstants.icHistoryInactivate, + title: "History", + index: 3, + indexSelected: selectedBottomNavigationIndex, + onTap: (index) { + welcomeScreenProvider + .changeBottomNavigationIndex(index); + }, + ), + ), + Expanded( + child: BottomNavigationWidget( + iconActivate: ImageConstants.icSettingActivate, + iconInactivate: ImageConstants.icSettingInactivate, + title: "Settings", + index: 4, + indexSelected: selectedBottomNavigationIndex, + onTap: (index) { + welcomeScreenProvider + .changeBottomNavigationIndex(index); + }, + ), + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} + +class PainterOne extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + double w = size.width; + double h = size.height; + + var paint1 = Paint() + ..color = Color(0xffEAA743) + ..style = PaintingStyle.fill; + + RRect halfRect = RRect.fromRectAndCorners( + Rect.fromCenter(center: Offset(w / 2, h / 2), width: w, height: h), + bottomLeft: Radius.circular(50), + bottomRight: Radius.circular(50)); + canvas.drawRRect(halfRect, paint1); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; } diff --git a/lib/screens/welcome_screen/widgets/bottom_navigation_widget.dart b/lib/screens/welcome_screen/widgets/bottom_navigation_widget.dart new file mode 100644 index 00000000..58ee8146 --- /dev/null +++ b/lib/screens/welcome_screen/widgets/bottom_navigation_widget.dart @@ -0,0 +1,50 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:flutter/material.dart'; + +class BottomNavigationWidget extends StatelessWidget { + final Function(int index)? onTap; + final int index; + final String iconActivate, iconInactivate; + final String title; + final int indexSelected; + + const BottomNavigationWidget({ + Key? key, + this.onTap, + required this.index, + this.title = '', + required this.indexSelected, + required this.iconActivate, + required this.iconInactivate, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + onTap?.call(index); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + indexSelected == index ? iconActivate : iconInactivate, + height: title.isNotEmpty ? 25 : null, + ), + if (title.isNotEmpty) ...[ + SizedBox(height: 3), + Text( + title, + style: TextStyle( + fontSize: 10.toFont, + color: + indexSelected == index ? Colors.black : Color(0xFFAEAEAE), + fontWeight: FontWeight.w500, + ), + ), + ], + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/welcome_screen/widgets/choice_contacts_widget.dart b/lib/screens/welcome_screen/widgets/choice_contacts_widget.dart new file mode 100644 index 00000000..5d7f6780 --- /dev/null +++ b/lib/screens/welcome_screen/widgets/choice_contacts_widget.dart @@ -0,0 +1,222 @@ +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:at_contacts_group_flutter/at_contacts_group_flutter.dart'; +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/contact_type.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/search_widget.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/add_contact_screen.dart'; +import 'package:atsign_atmosphere_pro/screens/contact_new_version/widget/list_contact_widget.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +class ChoiceContactsWidget extends StatefulWidget { + final List? selectedContacts; + + const ChoiceContactsWidget({ + Key? key, + this.selectedContacts, + }) : super(key: key); + + @override + State createState() => _ChoiceContactsWidgetState(); +} + +class _ChoiceContactsWidgetState extends State { + late TrustedContactProvider trustedProvider; + late List listContact; + late GroupService _groupService; + late TextEditingController searchController; + + @override + void initState() { + searchController = TextEditingController(); + _groupService = GroupService(); + trustedProvider = context.read(); + listContact = widget.selectedContacts ?? []; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Align( + alignment: Alignment.bottomCenter, + child: Container( + height: MediaQuery.of(context).size.height - 60, + width: double.infinity, + decoration: BoxDecoration( + color: Color(0xFFF4F4F4), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + offset: const Offset(0, 4), + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeaderWidget(), + Padding( + padding: const EdgeInsets.only(left: 27, top: 10), + child: Text( + "Send To:", + style: TextStyle( + fontSize: 20.toFont, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ), + SearchWidget( + controller: searchController, + borderColor: Colors.white, + backgroundColor: Colors.white, + hintText: "Search", + hintStyle: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + margin: EdgeInsets.fromLTRB( + 36.toWidth, + 11.toHeight, + 36.toWidth, + 10.toHeight, + ), + onChange: (value) { + setState(() {}); + }, + ), + Expanded( + child: ListContactWidget( + trustedContacts: trustedProvider.trustedContacts, + isSelectMultiContacts: true, + contactsType: ListContactType.all, + selectedContacts: listContact, + searchKeywords: searchController.text, + onSelectContacts: (contacts) { + setState(() { + listContact = contacts; + }); + }, + ), + ), + SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 44, + vertical: 24, + ), + child: InkWell( + onTap: () { + Navigator.of(context).pop(listContact); + }, + child: Container( + width: double.infinity, + height: 51.toHeight, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.black, + ), + child: Center( + child: Text( + "Select (${listContact.length})", + style: TextStyle( + color: Colors.white, + fontSize: 16.toFont, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + ) + ], + ), + ), + ), + ); + } + + Widget _buildHeaderWidget() { + return Padding( + padding: const EdgeInsets.fromLTRB(18, 14, 18, 0), + child: Row( + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 10, + horizontal: 6, + ), + child: SvgPicture.asset( + AppVectors.icBack, + ), + ), + ), + const Spacer(), + Align( + alignment: Alignment.topRight, + child: InkWell( + onTap: () async { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return AddContactScreen(); + }, + ); + if (result == true) { + _groupService.fetchGroupsAndContacts(); + } + }, + child: Container( + height: 34, + margin: EdgeInsets.only(top: 10, right: 8), + padding: EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: ColorConstants.orange, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Text( + "Add New", + style: TextStyle( + color: Colors.white, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 9), + SvgPicture.asset( + AppVectors.icPlus11px, + color: Colors.white, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/welcome_screen/widgets/contact_card.dart b/lib/screens/welcome_screen/widgets/contact_card.dart new file mode 100644 index 00000000..f5377571 --- /dev/null +++ b/lib/screens/welcome_screen/widgets/contact_card.dart @@ -0,0 +1,152 @@ +import 'dart:typed_data'; + +import 'package:at_common_flutter/services/size_config.dart'; +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_flutter/widgets/custom_circle_avatar.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class ContactCard extends StatefulWidget { + final AtContact contact; + final double avatarSize, borderRadius; + final Function()? onTap; + final bool isSelected, isTrusted; + final Function? deleteFunc; + + const ContactCard({ + Key? key, + required this.contact, + this.avatarSize = 40, + this.borderRadius = 18, + this.onTap, + this.isSelected = false, + this.isTrusted = false, + this.deleteFunc, + }) : super(key: key); + + @override + State createState() => _ContactCardState(); +} + +class _ContactCardState extends State { + String contactName = 'UG'; + Uint8List? image; + + @override + void initState() { + getNameAndImage(); + super.initState(); + } + + void getNameAndImage() { + try { + contactName = widget.contact.atSign ?? 'UG'; + + if (contactName[0] == '@') { + contactName = contactName.substring(1); + } + + if (widget.contact.tags != null && + widget.contact.tags?['image'] != null) { + List intList = widget.contact.tags!['image'].cast(); + image = Uint8List.fromList(intList); + } + } catch (e) { + contactName = 'UG'; + print('Error in getting image $e'); + } + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + widget.onTap?.call(); + }, + child: Container( + padding: EdgeInsets.fromLTRB( + 20.toWidth, + 12.toHeight, + 14.toWidth, + 12.toHeight, + ), + margin: EdgeInsets.only(bottom: 10.toHeight), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: ColorConstants.textBoxBg, + ), + color: Colors.white, + ), + child: Row( + children: [ + Container( + height: widget.avatarSize, + width: widget.avatarSize, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + widget.borderRadius, + ), + ), + child: image != null + ? CustomCircleAvatar( + byteImage: image, + nonAsset: true, + ) + : ContactInitial( + borderRadius: widget.borderRadius, + size: widget.avatarSize, + initials: contactName, + ), + ), + const SizedBox(width: 18), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.contact.atSign ?? '', + style: TextStyle( + fontSize: 14.toFont, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + Text( + widget.contact.tags?['name'] ?? + widget.contact.atSign!.substring(1), + style: TextStyle( + fontSize: 11.toFont, + fontWeight: FontWeight.w400, + color: Colors.black, + ), + ), + ], + ), + ), + widget.isTrusted + ? SvgPicture.asset( + AppVectors.icTrustActivated, + ) + : const SizedBox(), + InkWell( + onTap: () { + widget.deleteFunc?.call(); + }, + child: Padding( + padding: const EdgeInsets.only(top: 4), + child: SvgPicture.asset( + AppVectors.icClose, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/welcome_screen/widgets/overlapping_contacts.dart b/lib/screens/welcome_screen/widgets/overlapping_contacts.dart index e638866f..4376de2d 100644 --- a/lib/screens/welcome_screen/widgets/overlapping_contacts.dart +++ b/lib/screens/welcome_screen/widgets/overlapping_contacts.dart @@ -4,24 +4,26 @@ import 'package:at_contact/at_contact.dart'; /// This is a custom widget to display the selected contacts /// in a row with overlapping profile pictures - import 'package:at_contacts_group_flutter/models/group_contacts_model.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/contact_initial.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_circle_avatar.dart'; import 'package:atsign_atmosphere_pro/screens/group_contacts_screen/widgets/group_contact_list_tile.dart'; +import 'package:atsign_atmosphere_pro/screens/welcome_screen/widgets/contact_card.dart'; import 'package:atsign_atmosphere_pro/services/common_utility_functions.dart'; -import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; +import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; import 'package:flutter/material.dart'; -import 'package:at_common_flutter/services/size_config.dart'; import 'package:provider/provider.dart'; class OverlappingContacts extends StatefulWidget { - final List? selectedList; - final ValueChanged? onChnage; + final List selectedList; + final ValueChanged? onchange; - const OverlappingContacts({Key? key, this.selectedList, this.onChnage}) - : super(key: key); + const OverlappingContacts({ + Key? key, + required this.selectedList, + this.onchange, + }) : super(key: key); @override _OverlappingContactsState createState() => _OverlappingContactsState(); @@ -30,17 +32,19 @@ class OverlappingContacts extends StatefulWidget { class _OverlappingContactsState extends State { bool isExpanded = false; Map _atsignImages = {}; + late TrustedContactProvider trustedProvider; @override void initState() { - for (var index = 0; index < widget.selectedList!.length; index++) { + trustedProvider = context.read(); + for (var index = 0; index < widget.selectedList.length; index++) { Uint8List? image; - if (widget.selectedList![index]?.contact?.tags != null && - widget.selectedList![index]?.contact?.tags!['image'] != null) { + if (widget.selectedList[index]?.contact?.tags != null && + widget.selectedList[index]?.contact?.tags!['image'] != null) { image = CommonUtilityFunctions() - .getContactImage(widget.selectedList![index]!.contact!); + .getContactImage(widget.selectedList[index]!.contact!); } - _atsignImages[widget.selectedList![index]?.contact?.atSign] = image; + _atsignImages[widget.selectedList[index]?.contact?.atSign] = image; } super.initState(); @@ -48,179 +52,64 @@ class _OverlappingContactsState extends State { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - setState(() { - isExpanded = !isExpanded; - }); - }, - child: Container( - height: (isExpanded) ? 300.toHeight : 60.toHeight, - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - color: Color(0xffF7F7FF), - borderRadius: BorderRadius.circular(10), - ), - child: Stack( - children: [ - Stack( - children: List.generate( - (widget.selectedList!.length > 3) - ? 3 - : widget.selectedList!.length, - (index) { - Uint8List? image = _atsignImages[ - widget.selectedList![index]?.contact?.atSign]; - - return Positioned( - left: 5 + double.parse((index * 10).toString()), - top: 5.toHeight, - child: Container( - height: 28.toHeight, - width: 28.toHeight, - decoration: BoxDecoration(shape: BoxShape.circle), - child: (image != null) - ? CustomCircleAvatar( - byteImage: image, - nonAsset: true, - ) - : ContactInitial( - initials: widget - .selectedList![index]?.contact?.atSign ?? - widget.selectedList![index]?.group?.groupName, - ), - ), - ); - }, - ), - ), - Positioned( - top: 5.toHeight, - left: 40 + - double.parse((widget.selectedList!.length * 25).toString()), - child: Row( - // mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - (widget.selectedList!.isEmpty) - ? Container() - : Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - width: 160.toWidth, - child: Row( - children: [ - Container( - width: 60.toWidth, - child: Text( - '${widget.selectedList![0]?.contact?.atSign ?? widget.selectedList![0]?.group?.groupName}', - style: - CustomTextStyles.secondaryRegular14, - overflow: TextOverflow.ellipsis, - ), - ), - Container( - // width: 100.toWidth, - child: Text( - widget.selectedList!.length - 1 == 0 - ? '' - : widget.selectedList!.length - 1 == 1 - ? ' and ${widget.selectedList!.length - 1} other' - : ' and ${widget.selectedList!.length - 1} others', - style: - CustomTextStyles.secondaryRegular14, - overflow: TextOverflow.ellipsis, - ), - ) - ], - ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox( - width: 10.toWidth, - ), - ], - ) - ], - ), - ), - Positioned( - top: 10.toHeight, - right: 0, - child: Container( - width: 20.toWidth, - child: Icon( - (isExpanded) - ? Icons.keyboard_arrow_up - : Icons.keyboard_arrow_down, - size: 15.toFont, - ), - ), - ), - (isExpanded) - ? Consumer( - builder: (context, provider, _) { - return Positioned( - top: 50.toHeight, - child: Container( - height: 200.toHeight, - width: SizeConfig().screenWidth - 60.toWidth, - child: ListView.builder( - itemCount: widget.selectedList!.length, - itemBuilder: (context, index) { - Uint8List? image = _atsignImages[ - widget.selectedList![index]?.contact?.atSign]; + return Consumer( + builder: (context, provider, _) { + return ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: widget.selectedList.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + Uint8List? image = + _atsignImages[widget.selectedList[index]?.contact?.atSign]; + final contactSelected = provider.selectedContacts[index]; - return ContactListTile( - isSelected: provider.selectedContacts - .contains(provider.selectedContacts[index]), - onAdd: () {}, - onRemove: () { - provider.removeContacts( - provider.selectedContacts[index]); - widget.onChnage!(true); - }, - name: provider - .selectedContacts[index].contact?.atSign - ?.substring(1) ?? - provider.selectedContacts[index].group - ?.groupName - ?.substring(0), - atSign: provider.selectedContacts[index].contact - ?.atSign ?? - '${provider.selectedContacts[index].group?.members?.length.toString()} Members', - image: (image != null) - ? CustomCircleAvatar( - byteImage: image, - nonAsset: true, - ) - : ContactInitial( - initials: provider - .selectedContacts[index] - .contact - ?.atSign ?? - provider.selectedContacts[index] - .group?.groupName, - ), - ); - }, - ), - ), - ); + return widget.selectedList[index]?.contact != null + ? ContactCard( + key: Key(widget.selectedList[index]!.contact!.atSign ?? ''), + contact: widget.selectedList[index]!.contact!, + isTrusted: _checkTrustedContact( + widget.selectedList[index]!.contact!), + deleteFunc: () { + provider.removeContacts(contactSelected); }, ) - : Positioned( - top: 20.toHeight, - child: Container(), - ) - ], - ), - ), + : ContactListTile( + isSelected: + provider.selectedContacts.contains(contactSelected), + onAdd: () {}, + onRemove: () { + provider.removeContacts(contactSelected); + widget.onchange!(true); + }, + name: contactSelected.contact?.atSign?.substring(1) ?? + contactSelected.group?.groupName?.substring(0), + atSign: contactSelected.contact?.atSign ?? + '${contactSelected.group?.members?.length.toString()} Members', + image: (image != null) + ? CustomCircleAvatar( + byteImage: image, + nonAsset: true, + ) + : ContactInitial( + initials: contactSelected.contact?.atSign ?? + contactSelected.group?.groupName, + ), + ); + }, + ); + }, ); } + + bool _checkTrustedContact(AtContact contact) { + bool isTrusted = false; + for (var element in trustedProvider.trustedContacts) { + if (contact.atSign == element.atSign) { + isTrusted = true; + } + } + + return isTrusted; + } } diff --git a/lib/screens/welcome_screen/widgets/select_contact_widget.dart b/lib/screens/welcome_screen/widgets/select_contact_widget.dart deleted file mode 100644 index b8bc33cf..00000000 --- a/lib/screens/welcome_screen/widgets/select_contact_widget.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'package:at_contacts_group_flutter/at_contacts_group_flutter.dart'; -import 'package:at_contacts_group_flutter/screens/group_contact_view/group_contact_view.dart'; -import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; -import 'package:at_common_flutter/services/size_config.dart'; -import 'package:atsign_atmosphere_pro/utils/colors.dart'; -import 'package:atsign_atmosphere_pro/utils/images.dart'; -import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; -import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class SelectContactWidget extends StatefulWidget { - final Function(bool) onUpdate; - SelectContactWidget(this.onUpdate); - @override - _SelectContactWidgetState createState() => _SelectContactWidgetState(); -} - -class _SelectContactWidgetState extends State { - String? headerText; - - @override - void initState() { - headerText = TextStrings().welcomeContactPlaceholder; - - super.initState(); - } - - @override - Widget build(BuildContext context) { - SizeConfig().init(context); - return Theme( - data: ThemeData( - dividerColor: Colors.transparent, - textTheme: TextTheme( - subtitle1: TextStyle( - color: ColorConstants.inputFieldColor, - ), - ), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(15.toFont), - child: Container( - color: ColorConstants.inputFieldColor, - child: _ExpansionTileWidget( - headerText, - (index) { - widget.onUpdate(true); - setState(() {}); - }, - )), - ), - ); - } -} - -class _ExpansionTileWidget extends StatelessWidget { - final String? headerText; - final Function(int) onSelected; - - _ExpansionTileWidget(this.headerText, this.onSelected); - @override - Widget build(BuildContext context) { - return ListTile( - // tilePadding: SizeConfig().isTablet(context) - // ? EdgeInsets.symmetric(vertical: 10.toFont, horizontal: 10.toFont) - // : EdgeInsets.only(left: 10.toFont, right: 10.toFont), - // backgroundColor: ColorConstants.inputFieldColor, - title: Text( - headerText!, - style: TextStyle( - color: ColorConstants.fadedText, - fontSize: 14.toFont, - fontWeight: FontWeight.normal, - ), - semanticsLabel: 'Select atSign from contacts button', - ), - trailing: Container( - padding: EdgeInsets.symmetric(vertical: 15), - child: Image.asset( - ImageConstants.contactsIcon, - color: Colors.black, - semanticLabel: '', - ), - ), - onTap: () { - selectContact(context); - }, - ); - } - - selectContact(BuildContext context) async { - List? contactSelectedHistory = []; - Provider.of(NavService.navKey.currentContext!, - listen: false) - .selectedContacts - .forEach((GroupContactsModel? element) { - contactSelectedHistory.add(element ?? GroupContactsModel()); - }); - - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => GroupContactView( - asSelectionScreen: true, - showGroups: true, - showContacts: true, - selectedList: (s) { - Provider.of( - NavService.navKey.currentContext!, - listen: false) - .updateSelectedContacts(s); - onSelected(s.length); - }, - // singleSelection: true, - contactSelectedHistory: contactSelectedHistory, - ), - ), - ); - } -} diff --git a/lib/screens/welcome_screen/widgets/welcome_sceen_home.dart b/lib/screens/welcome_screen/widgets/welcome_sceen_home.dart index 1f3dcb84..dcadc311 100644 --- a/lib/screens/welcome_screen/widgets/welcome_sceen_home.dart +++ b/lib/screens/welcome_screen/widgets/welcome_sceen_home.dart @@ -2,32 +2,38 @@ import 'dart:async'; import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:at_contact/at_contact.dart'; -import 'package:atsign_atmosphere_pro/screens/common_widgets/common_button.dart'; -import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_heading.dart'; -import 'package:atsign_atmosphere_pro/screens/common_widgets/side_bar.dart'; +import 'package:at_contacts_group_flutter/at_contacts_group_flutter.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/error_dialog.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/file_card.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/provider_callback.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/switch_at_sign.dart'; import 'package:atsign_atmosphere_pro/screens/welcome_screen/widgets/overlapping_contacts.dart'; -import 'package:atsign_atmosphere_pro/screens/welcome_screen/widgets/select_contact_widget.dart'; -import 'package:atsign_atmosphere_pro/screens/welcome_screen/widgets/select_file_widget.dart'; import 'package:atsign_atmosphere_pro/services/backend_service.dart'; import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/services/overlay_service.dart'; +import 'package:atsign_atmosphere_pro/services/snackbar_service.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; import 'package:atsign_atmosphere_pro/view_models/file_transfer_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; +import 'package:open_file/open_file.dart'; import 'package:provider/provider.dart'; +import '../../../utils/images.dart'; +import '../../common_widgets/app_bar_custom.dart'; +import 'choice_contacts_widget.dart'; + class WelcomeScreenHome extends StatefulWidget { @override _WelcomeScreenHomeState createState() => _WelcomeScreenHomeState(); } class _WelcomeScreenHomeState extends State { - bool? isContactSelected; + bool isContactSelected = false; bool? isFileSelected; late WelcomeScreenProvider _welcomeScreenProvider; HistoryProvider? historyProvider; @@ -37,12 +43,14 @@ class _WelcomeScreenHomeState extends State { isSentFileEntrySaved = true; ScrollController scrollController = ScrollController(); late FileTransferProvider filePickerModel; - String? notes; - FocusNode _notesFocusNode = FocusNode(); - TextEditingController _notesController = TextEditingController(); + List listContacts = []; + late TextEditingController noteController; @override void initState() { + _welcomeScreenProvider = context.read(); + filePickerModel = context.read(); + noteController = TextEditingController(); isContactSelected = false; isFileSelected = false; super.initState(); @@ -50,330 +58,319 @@ class _WelcomeScreenHomeState extends State { @override Widget build(BuildContext context) { - filePickerModel = Provider.of(context); - _welcomeScreenProvider = Provider.of( - context, - ); - - return Container( + return Scaffold( + appBar: AppBarCustom( + height: 130, + title: "${BackendService.getInstance().currentAtSign ?? ''} ", + description: '', + ), + body: Container( + decoration: BoxDecoration( + color: ColorConstants.background, + ), width: double.infinity, height: SizeConfig().screenHeight, - child: Container( - width: double.infinity, - height: SizeConfig().screenHeight, - child: Stack( - children: [ - Row( - children: [ - Expanded( - flex: 3, - child: SingleChildScrollView( - controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + padding: EdgeInsets.only(bottom: 100.toHeight), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 30.toWidth, + vertical: 20.toHeight, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.symmetric( + horizontal: SizeConfig().isTablet(context) ? 33.toWidth : 0, + ), + child: Text( + TextStrings().selectFiles, + style: TextStyle( + fontSize: 15.toFont, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 10), + Consumer(builder: (context, provider, _) { + if (provider.selectedFiles.isNotEmpty) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Wrap( + alignment: WrapAlignment.start, + runAlignment: WrapAlignment.start, + children: List.generate( + provider.selectedFiles.length, + (index) { + return FileCard( + fileDetail: provider.selectedFiles[index], + deleteFunc: () { + provider.deleteFiles(index); + provider.calculateSize(); + }, + onTap: () { + openFile( + provider.selectedFiles[index], + ); + }, + ); + }, + ), + ), + _buildAddFilesOption() + ], + ); + } else { + return InkWell( + onTap: selectFiles, child: Container( + height: 142.toHeight, width: double.infinity, - padding: EdgeInsets.symmetric( - horizontal: 20.toWidth, vertical: 20.toHeight), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - TextStrings().welcome, - semanticsLabel: TextStrings().welcome, - style: GoogleFonts.playfairDisplay( - textStyle: TextStyle( - fontSize: 26.toFont, - fontWeight: FontWeight.w800, - height: 1.3, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + padding: EdgeInsets.all(2), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + ImageConstants.uploadIcon, ), - ), - ), - InkWell( - onTap: switchAtsign, - child: Text( - BackendService.getInstance().currentAtSign!, - style: GoogleFonts.playfairDisplay( - textStyle: TextStyle( - fontSize: 26.toFont, - fontWeight: FontWeight.w800, - height: 1.3, - color: ColorConstants.orangeColor, + SizedBox(height: 10.toHeight), + Text( + 'Upload your file(s)', + style: TextStyle( + color: ColorConstants.gray, + fontSize: 15.toFont, + fontWeight: FontWeight.w500, ), ), - ), - ), - SizedBox( - height: 10.toHeight, - ), - Text( - TextStrings().welcomeRecipient, - style: TextStyle( - color: ColorConstants.fadedText, - fontSize: 13.toFont, - fontWeight: FontWeight.normal, - ), - ), - SizedBox( - height: 67.toHeight, - ), - Text( - TextStrings().welcomeSendFilesTo, - style: TextStyle( - color: ColorConstants.fadedText, - fontSize: 12.toFont, - fontWeight: FontWeight.normal, - ), - ), - SizedBox( - height: 20.toHeight, - ), - SelectContactWidget( - (b) { - setState(() { - isContactSelected = b; - }); - }, - ), - SizedBox( - height: 10.toHeight, - ), - Consumer( - builder: (context, provider, _) { - if (filePickerModel.scrollToBottom) { - scrollToBottom(); - } - return SizedBox(); - }), - Consumer( - builder: (context, provider, _) { - if (provider.scrollToBottom) { - scrollToBottom(); - } - if ((provider.selectedContacts.isEmpty)) { - return Container(); - } else { - return OverlappingContacts( - selectedList: provider.selectedContacts, - onChnage: (isUpdate) { - setState(() {}); - }, - ); - } - }, - ), - SizedBox( - height: 20.toHeight, - ), - SelectFileWidget( - (b) { - setState(() { - isFileSelected = b; - }); - }, - (_str) { - setState(() { - notes = _str; - }); - }, - initialValue: notes, + ], ), - SizedBox( - height: (_welcomeScreenProvider - .selectedContacts != - null && - _welcomeScreenProvider - .selectedContacts.isNotEmpty && - filePickerModel.selectedFiles.isNotEmpty) - ? 20.toHeight - : 60.toHeight, - ), - (_welcomeScreenProvider.selectedContacts != null && - _welcomeScreenProvider - .selectedContacts.isNotEmpty && - filePickerModel.selectedFiles.isNotEmpty) - ? Container( - decoration: BoxDecoration( - color: ColorConstants.inputFieldColor, - borderRadius: BorderRadius.all( - Radius.circular(10), - ), - ), - child: Row( - children: [ - SizedBox( - width: 10.toWidth, - ), - Expanded( - child: TextFormField( - focusNode: _notesFocusNode, - controller: _notesController, - // initialValue: notes, - decoration: InputDecoration( - hintText: TextStrings() - .welcomeAddTranscripts, - hintStyle: TextStyle( - color: ColorConstants.fadedText, - fontSize: 14.toFont, - fontWeight: FontWeight.normal, - ), - border: InputBorder.none, - fillColor: ColorConstants - .inputFieldColor, - focusColor: ColorConstants - .inputFieldColor, - hoverColor: ColorConstants - .inputFieldColor, - ), - style: TextStyle( - color: ColorConstants.fadedText, - fontSize: 14.toFont, - fontWeight: FontWeight.normal, - ), - onChanged: (String txt) { - setState(() { - notes = txt; - }); - }, - ), - ), - notes != null - ? InkWell( - onTap: () { - setState(() { - notes = null; - }); - _notesController.clear(); - }, - child: Icon(Icons.clear, - color: Colors.black), - ) - : InkWell( - onTap: () { - FocusScope.of(context) - .requestFocus( - _notesFocusNode); - }, - child: Icon(Icons.edit, - color: Colors.black), - ), - SizedBox( - width: 15, - ), - ], - ), - ) - : SizedBox(), - SizedBox( - height: 30.toHeight, - ), - if (_welcomeScreenProvider.selectedContacts != - null && - _welcomeScreenProvider - .selectedContacts.isNotEmpty && - filePickerModel.selectedFiles.isNotEmpty) ...[ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - CommonButton('Clear', () { - setState(() { - isFileShareFailed = false; - _welcomeScreenProvider.selectedContacts - .clear(); - _welcomeScreenProvider - .resetSelectedContactsStatus(); - filePickerModel.selectedFiles.clear(); - filePickerModel - .resetSelectedFilesStatus(); - notes = null; - _notesController.clear(); - }); - }), - Expanded(child: SizedBox()), - Visibility( - visible: ((!_welcomeScreenProvider - .hasSelectedContactsChanged && - !filePickerModel - .hasSelectedFilesChanged) && - isFileShareFailed), - child: CommonButton( - TextStrings().buttonResend, - reAttemptSendingFiles, - color: Colors.amber[800], - )), - (_welcomeScreenProvider - .hasSelectedContactsChanged || - filePickerModel - .hasSelectedFilesChanged && - !isFileShareFailed) - ? CommonButton( - TextStrings().buttonSend, - sendFileWithFileBin, - ) - : SizedBox(), - ], - ), - SizedBox( - height: 60.toHeight, - ), - ], - ], + ), + ), + ), + ); + } + }), + SizedBox(height: 27.toHeight), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.only( + left: SizeConfig().isTablet(context) ? 30.toWidth : 0, + ), + child: Text( + TextStrings().selectContacts, + style: TextStyle( + fontSize: 15.toFont, + fontWeight: FontWeight.w600, + ), ), ), ), + ], + ), + SizedBox(height: 10.toHeight), + Consumer( + builder: (context, provider, _) { + if (provider.scrollToBottom) { + scrollToBottom(); + } + + return provider.selectedContacts.isNotEmpty + ? OverlappingContacts( + selectedList: provider.selectedContacts, + ) + : SizedBox(); + }, + ), + _buildChoiceContact(), + SizedBox(height: 27.toHeight), + Container( + height: 94.toHeight, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), ), - SizeConfig().isTablet(context) - ? Container( - height: SizeConfig().screenHeight, - width: 100, - child: SideBarWidget( - isExpanded: false, + padding: EdgeInsets.symmetric(horizontal: 18, vertical: 4), + child: TextField( + controller: noteController, + maxLines: 5, + style: TextStyle( + fontSize: 14.toFont, + color: Colors.black, + ), + decoration: InputDecoration( + hintText: 'Send Message (Optional)', + hintStyle: TextStyle( + fontSize: 15.toFont, + fontWeight: FontWeight.w500, + color: ColorConstants.textBlack, + ), + border: InputBorder.none, + labelStyle: TextStyle(fontSize: 15.toFont), + fillColor: Colors.white, + ), + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.done, + ), + ), + SizedBox(height: 40.toHeight), + InkWell( + onTap: sendFileWithFileBin, + child: Container( + height: 67.toHeight, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(247), + gradient: LinearGradient( + colors: [Color(0xffF05E3F), Color(0xffe9a642)], + stops: [0.1, 0.8], + ), + ), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Transfer Now', + style: TextStyle( + fontSize: 20.toFont, + color: Colors.white, + fontWeight: FontWeight.w600, + ), ), - ) - : SizedBox(), - ], + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildChoiceContact() { + return InkWell( + onTap: () { + _choiceContact(clearSelectedContact: false); + }, + child: Container( + height: 56.toHeight, + width: double.infinity, + decoration: BoxDecoration( + color: Color(0xFFF6DED5), + borderRadius: BorderRadius.circular(10), + ), + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: 20.toWidth, right: 20.toWidth), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Add atSigns', + style: TextStyle( + color: ColorConstants.orange, + fontSize: 15.toFont, + fontWeight: FontWeight.w500, + ), ), - SizeConfig().isTablet(context) - ? Container( - height: 100, - width: SizeConfig().screenWidth - 100, - child: Customheading(), - ) - : SizedBox(), - SizeConfig().isTablet(context) - ? Positioned( - right: 80, - top: 100, - child: Container( - height: 50, - width: 50, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(25), - color: Colors.black, - ), - child: Builder( - builder: (context) { - return InkWell( - onTap: () { - setState(() { - isExpanded = !isExpanded; - WelcomeScreenProvider().isExpanded = true; - }); + Icon( + Icons.add_circle_outline, + size: 27, + color: ColorConstants.orange, + ) + ], + ), + ), + ), + ); + } - Scaffold.of(context).openEndDrawer(); - }, - child: Icon( - Icons.arrow_back_ios, - color: Colors.white, - ), - ); - }, - ), - ), - ) - : SizedBox(), + Widget _buildAddFilesOption() { + return InkWell( + onTap: selectFiles, + child: Container( + height: 61.toHeight, + width: double.infinity, + decoration: BoxDecoration( + color: ColorConstants.yellow.withOpacity(0.19), + borderRadius: BorderRadius.circular(10), + ), + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.toWidth), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Add Files', + style: TextStyle( + color: ColorConstants.yellow, + fontSize: 15.toFont, + fontWeight: FontWeight.w500, + ), + ), + Icon( + Icons.add_circle_outline, + size: 27, + color: ColorConstants.yellow, + ) ], ), - )); + ), + ), + ); + } + + void _choiceContact({bool clearSelectedContact = false}) async { + if (clearSelectedContact) { + listContacts.clear(); + } + + final result = await showModalBottomSheet?>( + context: context, + isScrollControlled: true, + useRootNavigator: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return ChoiceContactsWidget( + selectedContacts: listContacts, + ); + }, + ); + + if (result != null) { + listContacts = result; + _welcomeScreenProvider.updateSelectedContacts(result); + } + } + + selectFiles() async { + await providerCallback(context, + task: (provider) => provider.pickFiles(provider.FILES), + taskName: (provider) => provider.PICK_FILES, + onSuccess: (provider) {}, + onError: (err) => ErrorDialog().show(err.toString(), context: context)); } scrollToBottom() { @@ -417,6 +414,24 @@ class _WelcomeScreenHomeState extends State { } sendFileWithFileBin() async { + if (filePickerModel.selectedFiles.isEmpty) { + SnackbarService().showSnackbar( + context, + 'No files selected', + bgColor: ColorConstants.redAlert, + ); + return; + } + + if (_welcomeScreenProvider.selectedContacts.isEmpty) { + SnackbarService().showSnackbar( + context, + 'No atSign selected', + bgColor: ColorConstants.redAlert, + ); + return; + } + if (mounted) { setState(() { // assuming file share record will be saved in sent history. @@ -426,16 +441,23 @@ class _WelcomeScreenHomeState extends State { } _welcomeScreenProvider.resetSelectedContactsStatus(); filePickerModel.resetSelectedFilesStatus(); + OverlayService.instance.showOverlay(); + var res = await filePickerModel.sendFileWithFileBin( filePickerModel.selectedFiles, _welcomeScreenProvider.selectedContacts, groupName: _welcomeScreenProvider.groupName, - notes: notes, + notes: noteController.text, ); if (mounted && res is bool) { + filePickerModel.resetData(); + _welcomeScreenProvider.resetData(); + setState(() { isFileShareFailed = !res; + listContacts.clear(); + noteController.clear(); }); } else if (res == null) { if (mounted) { @@ -457,4 +479,15 @@ class _WelcomeScreenHomeState extends State { ), ); } + + openFile(PlatformFile file) async { + final result = await OpenFile.open(file.path); + + if (result.type != ResultType.done) { + SnackbarService().showSnackbar( + context, + result.message, + ); + } + } } diff --git a/lib/services/backend_service.dart b/lib/services/backend_service.dart index c82f37f5..f936fd69 100644 --- a/lib/services/backend_service.dart +++ b/lib/services/backend_service.dart @@ -21,7 +21,9 @@ import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/utils/constants.dart'; import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; +import 'package:atsign_atmosphere_pro/view_models/add_contact_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; +import 'package:atsign_atmosphere_pro/view_models/create_group_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/file_download_checker.dart'; import 'package:atsign_atmosphere_pro/view_models/file_transfer_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; @@ -56,7 +58,8 @@ class BackendService { late AtClientPreference atClientPreference; bool autoAcceptFiles = false; final String AUTH_SUCCESS = "Authentication successful"; - Timer? periodicHistoryRefresh; + + // Timer? periodicHistoryRefresh; String? get currentAtsign => currentAtSign; Directory? downloadDirectory; AnimationController? controller; @@ -158,12 +161,12 @@ class BackendService { }); } - setPeriodicFileHistoryRefresh() { - periodicHistoryRefresh?.cancel(); - periodicHistoryRefresh = Timer.periodic(Duration(minutes: 1), (timer) { - refreshHistoryScreen(); - }); - } + // setPeriodicFileHistoryRefresh() { + // periodicHistoryRefresh?.cancel(); + // periodicHistoryRefresh = Timer.periodic(Duration(minutes: 1), (timer) { + // refreshHistoryScreen(); + // }); + // } Future _notificationCallBack(AtNotification response) async { print('response => $response'); @@ -199,9 +202,6 @@ class BackendService { var atKey = notificationKey.split(':')[1]; var decryptedMessage = response.value!; - //TODO: only for testing - // await sendNotificationAck(notificationKey, fromAtSign); - if (decryptedMessage != null && decryptedMessage != '') { await Provider.of(NavService.navKey.currentContext!, listen: false) @@ -334,7 +334,7 @@ class BackendService { listen: false) .checkForUndownloadedFiles(); - setPeriodicFileHistoryRefresh(); + // setPeriodicFileHistoryRefresh(); } } @@ -453,9 +453,7 @@ class BackendService { late var atClientPrefernce; AtOnboardingResult result; - if ((atSign ?? '').isNotEmpty) { - _onboardingService.setAtsign = atSign; - } + _onboardingService.setAtsign = atSign; // await getAtClientPreference(); await getAtClientPreference() @@ -524,13 +522,16 @@ class BackendService { // clearing file and contact informations. Provider.of(NavService.navKey.currentState!.context, listen: false) - .selectedContacts = []; + .resetData(); Provider.of(NavService.navKey.currentState!.context, listen: false) - .selectedFiles = []; + .resetData(); Provider.of(NavService.navKey.currentState!.context, listen: false) .resetData(); + Provider.of(NavService.navKey.currentState!.context, + listen: false) + .resetData(); await KeychainUtil.makeAtSignPrimary(onboardedAtsign); startMonitor(); @@ -583,9 +584,13 @@ class BackendService { onNotificationClick(String payload) async { if (Platform.isAndroid || Platform.isIOS) { - await Navigator.push( + await Navigator.pushNamedAndRemoveUntil( NavService.navKey.currentContext!, - MaterialPageRoute(builder: (context) => HistoryScreen(tabIndex: 1)), + Routes.WELCOME_SCREEN, + (route) => false, + arguments: { + "indexBottomBarSelected": 3, + }, ); } else if (Platform.isMacOS) { DesktopSetupRoutes.nested_push(DesktopRoutes.DESKTOP_HISTORY); diff --git a/lib/services/common_utility_functions.dart b/lib/services/common_utility_functions.dart index 67334024..d66ae82c 100644 --- a/lib/services/common_utility_functions.dart +++ b/lib/services/common_utility_functions.dart @@ -3,7 +3,9 @@ import 'dart:typed_data'; import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:at_common_flutter/services/size_config.dart'; import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_flutter/services/contact_service.dart'; import 'package:at_contacts_flutter/utils/init_contacts_service.dart'; +import 'package:at_contacts_group_flutter/services/group_service.dart'; import 'package:atsign_atmosphere_pro/routes/route_names.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/confirmation_dialog.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_button.dart'; @@ -11,17 +13,26 @@ import 'package:atsign_atmosphere_pro/services/backend_service.dart'; import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/utils/file_types.dart'; +import 'package:atsign_atmosphere_pro/utils/file_utils.dart'; import 'package:atsign_atmosphere_pro/utils/images.dart'; import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:intl/intl.dart'; import 'package:showcaseview/showcaseview.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; +import '../data_models/file_modal.dart'; +import '../data_models/file_transfer.dart'; +import '../screens/common_widgets/labelled_circular_progress.dart'; +import '../screens/my_files/widgets/downloads_folders.dart'; +import '../utils/vectors.dart'; + class CommonUtilityFunctions { static final CommonUtilityFunctions _singleton = CommonUtilityFunctions._internal(); + CommonUtilityFunctions._internal(); factory CommonUtilityFunctions() { @@ -99,6 +110,7 @@ class CommonUtilityFunctions { child: SingleChildScrollView( child: Container( padding: EdgeInsets.all(20), + width: 600, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -301,7 +313,7 @@ class CommonUtilityFunctions { await Navigator.pushNamedAndRemoveUntil(NavService.navKey.currentContext!, Routes.HOME, (Route route) => false); } else if (atSignList == null || atSignList.isEmpty) { - BackendService.getInstance().periodicHistoryRefresh?.cancel(); + // BackendService.getInstance().periodicHistoryRefresh?.cancel(); await Navigator.pushNamedAndRemoveUntil(NavService.navKey.currentContext!, Routes.HOME, (Route route) => false); } @@ -397,8 +409,8 @@ class CommonUtilityFunctions { var videoThumbnail = await VideoThumbnail.thumbnailData( video: path, imageFormat: ImageFormat.JPEG, - maxWidth: - 50, // specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio + maxWidth: 50, + // specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio quality: 100, ); return videoThumbnail; @@ -608,6 +620,379 @@ class CommonUtilityFunctions { }); } + String formatDateTime(DateTime datetime) { + return "${datetime.day}/${datetime.month}/${datetime.year} | ${datetime.hour}:${datetime.minute}"; + } + + Widget getDownloadStatus(FileTransferProgress? fileTransferProgress) { + Widget spinner = CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ColorConstants.orange, + ), + ); + + if (fileTransferProgress == null) { + return spinner; + } + + if (fileTransferProgress.fileState == FileState.download && + fileTransferProgress.percent != null) { + spinner = LabelledCircularProgressIndicator( + value: (fileTransferProgress.percent! / 100)); + } + + return spinner; + } + + bool checkForDownloadAvailability(FileTransfer file) { + bool isDownloadAvailable = false; + + var expiryDate = file.date!.add(Duration(days: 6)); + if (expiryDate.difference(DateTime.now()) > Duration(seconds: 0)) { + isDownloadAvailable = true; + } + + // if fileList is not having any file then download icon will not be shown + var isFileUploaded = false; + file.files!.forEach((FileData fileData) { + if (fileData.isUploaded!) { + isFileUploaded = true; + } + }); + + if (!isFileUploaded) { + isDownloadAvailable = false; + } + + return isDownloadAvailable; + } + + Future getNickname(String atSign) async { + var res = await ContactService().getContactDetails(atSign, null); + return res['nickname'] ?? ""; + } + + Widget interactableThumbnail(String extension, String path, + FilesDetail fileDetail, Function onDelete) { + GroupService().allContacts; + String nickname = ""; + final date = DateTime.parse(fileDetail.date ?? "").toLocal(); + final shortDate = DateFormat('dd/MM/yy').format(date); + final time = DateFormat('HH:mm').format(date); + + for (var contact in GroupService().allContacts) { + if (contact?.contact?.atSign == fileDetail.contactName) { + nickname = contact?.contact?.tags?["nickname"] ?? ""; + break; + } + } + return FileTypes.IMAGE_TYPES.contains(extension) + ? ClipRRect( + borderRadius: BorderRadius.circular(10.toHeight), + child: GestureDetector( + onTap: () async { + await showDialog( + context: NavService.navKey.currentContext!, + builder: (_) => Material( + type: MaterialType.transparency, + child: Column( + children: [ + SizedBox( + height: 10, + ), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(left: 32), + child: InkWell( + onTap: () { + Navigator.pop( + NavService.navKey.currentContext!); + }, + child: Icon( + Icons.clear, + color: Colors.white, + size: 24, + ), + ), + ), + ), + SizedBox( + height: 10, + ), + Expanded( + child: Container( + // height: double.infinity, + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 33), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + image: DecorationImage( + image: FileImage( + File(path), + ), + fit: BoxFit.cover, + ), + ), + ), + ), + SizedBox( + height: 40, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + AppVectors.icDownloadFile, + height: 50, + width: 50, + ), + ), + SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + Navigator.pop( + NavService.navKey.currentContext!); + Navigator.pop( + NavService.navKey.currentContext!); + await FileUtils.moveToSendFile(path); + }, + child: SvgPicture.asset( + AppVectors.icSendFile, + height: 50, + width: 50, + ), + ), + ), + SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + await onDelete(); + }, + child: SvgPicture.asset( + AppVectors.icDeleteFile, + height: 50, + width: 50, + ), + ), + ), + ], + ), + SizedBox( + height: 40, + ), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + padding: EdgeInsets.all(20), + margin: EdgeInsets.symmetric(horizontal: 25), + width: double.infinity, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + fileDetail.fileName ?? "", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 12), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "$shortDate", + style: TextStyle( + fontSize: 12, + color: ColorConstants.oldSliver, + ), + ), + Container( + width: 1, + height: 8, + color: Color(0xFFD7D7D7), + margin: EdgeInsets.symmetric( + horizontal: 3, + ), + ), + Text( + "$time", + style: TextStyle( + fontSize: 12, + color: ColorConstants.oldSliver, + ), + ), + ], + ), + ], + ), + SizedBox(height: 5), + Text( + double.parse(fileDetail.size.toString()) <= + 1024 + ? '${fileDetail.size} ' + TextStrings().kb + : '${(fileDetail.size! / (1024 * 1024)).toStringAsFixed(2)} ' + + TextStrings().mb, + style: TextStyle( + color: ColorConstants.grey, + fontSize: 12, + ), + textAlign: TextAlign.left, + ), + SizedBox(height: 10), + nickname.isNotEmpty + ? Text( + nickname, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold), + ) + : SizedBox(), + SizedBox(height: 5), + Text( + fileDetail.contactName ?? "", + style: TextStyle( + fontSize: 14, + ), + ), + SizedBox(height: 10), + // fileDetail.message.isNotNull + // ? + Text( + "Message", + style: TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + // : SizedBox(), + SizedBox(height: 5), + Text( + fileDetail.message ?? "", + style: TextStyle( + fontSize: 14, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + }, + child: Container( + height: 50.toHeight, + width: 50.toWidth, + child: Image.file( + File(path), + fit: BoxFit.cover, + errorBuilder: (BuildContext _context, _, __) { + return Container( + child: Icon( + Icons.image, + size: 30.toFont, + ), + ); + }, + ), + ), + ), + ) + : FileTypes.VIDEO_TYPES.contains(extension) + ? FutureBuilder( + future: videoThumbnailBuilder(path), + builder: (context, snapshot) => ClipRRect( + borderRadius: BorderRadius.circular(10.toHeight), + child: GestureDetector( + onTap: () async { + // await openDownloadsFolder(context); + await openFilePath(path); + }, + child: Container( + padding: EdgeInsets.only(left: 10), + height: 50.toHeight, + width: 50.toWidth, + child: (snapshot.data == null) + ? Image.asset(ImageConstants.videoLogo, + fit: BoxFit.cover, + errorBuilder: (BuildContext _context, _, __) { + return Container( + child: Icon( + Icons.image, + size: 30.toFont, + ), + ); + }) + : Image.memory( + snapshot.data as Uint8List, + fit: BoxFit.cover, + errorBuilder: (BuildContext _context, _, __) { + return Container( + child: Icon( + Icons.image, + size: 30.toFont, + ), + ); + }, + ), + ), + ), + ), + ) + : Builder( + builder: (context) => ClipRRect( + borderRadius: BorderRadius.circular(10.toHeight), + child: GestureDetector( + onTap: () async { + await openFilePath(path); + // await openDownloadsFolder(context); + }, + child: Container( + // padding: EdgeInsets.only(left: 10), + height: 50.toHeight, + width: 50.toWidth, + child: Image.asset( + FileTypes.PDF_TYPES.contains(extension) + ? ImageConstants.pdfLogo + : FileTypes.AUDIO_TYPES.contains(extension) + ? ImageConstants.musicLogo + : FileTypes.WORD_TYPES.contains(extension) + ? ImageConstants.wordLogo + : FileTypes.EXEL_TYPES.contains(extension) + ? ImageConstants.exelLogo + : FileTypes.TEXT_TYPES + .contains(extension) + ? ImageConstants.txtLogo + : ImageConstants.unknownLogo, + fit: BoxFit.cover, + ), + ), + ), + ), + ); + } + void showNoFileDialog({double deviceTextFactor = 1}) { showDialog( context: NavService.navKey.currentContext!, diff --git a/lib/services/file_transfer_service.dart b/lib/services/file_transfer_service.dart index 4aa346dd..44dd5379 100644 --- a/lib/services/file_transfer_service.dart +++ b/lib/services/file_transfer_service.dart @@ -445,7 +445,9 @@ class FileTransferService { } void encryptFileInIsolate(Map params) async { - final encryptedFile = await EncryptionService().encryptFileInChunks( + final encryptedFile = + await EncryptionService(BackendService.getInstance().currentAtSign ?? '') + .encryptFileInChunks( params['file'], params['encryptionKey'], params['fileEncryptionChunkSize'], @@ -455,7 +457,9 @@ void encryptFileInIsolate(Map params) async { } void decryptFileInIsolate(Map params) async { - final decryptedFile = await EncryptionService().decryptFileInChunks( + final decryptedFile = + await EncryptionService(BackendService.getInstance().currentAtSign ?? '') + .decryptFileInChunks( params['file'], params['encryptionKey'], params['fileEncryptionChunkSize'], diff --git a/lib/services/overlay_service.dart b/lib/services/overlay_service.dart index 4bf0ccc0..d529d3e9 100644 --- a/lib/services/overlay_service.dart +++ b/lib/services/overlay_service.dart @@ -1,163 +1,174 @@ +import 'dart:ui'; + import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; +import 'package:atsign_atmosphere_pro/routes/route_names.dart'; import 'package:atsign_atmosphere_pro/screens/history/widgets/file_recipients.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/linear_progress_bar.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; -import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; +import 'package:atsign_atmosphere_pro/utils/images.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; import 'package:atsign_atmosphere_pro/view_models/file_transfer_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/file_progress_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/history_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import 'navigation_service.dart'; import '../data_models/file_transfer_status.dart'; class OverlayService { OverlayService._(); + static final OverlayService _instance = OverlayService._(); + static OverlayService get instance => _instance; OverlayEntry? snackBarOverlayEntry; - showOverlay(FLUSHBAR_STATUS flushbarStatus, {String? errorMessage}) async { + void showOverlay() { hideOverlay(); - - snackBarOverlayEntry = - _buildSnackBarOverlayEntry(flushbarStatus, errorMessage: errorMessage); + snackBarOverlayEntry = _buildSnackBarOverlayEntry(); NavService.navKey.currentState?.overlay?.insert(snackBarOverlayEntry!); - - if (flushbarStatus == FLUSHBAR_STATUS.DONE) { - await Future.delayed(Duration(seconds: 3)); - hideOverlay(); - } else if (flushbarStatus == FLUSHBAR_STATUS.FAILED) { - await Future.delayed(Duration(seconds: 5)); - hideOverlay(); - } + return null; } - hideOverlay() { + void hideOverlay() { snackBarOverlayEntry?.remove(); snackBarOverlayEntry = null; } - OverlayEntry _buildSnackBarOverlayEntry( - FLUSHBAR_STATUS flushbarStatus, { - String? errorMessage, - }) { - Color bgColor = _getColor(flushbarStatus); - String text = errorMessage ?? _getText(flushbarStatus); + OverlayEntry _buildSnackBarOverlayEntry() { + Color bgColor = Colors.white; + + return OverlayEntry( + builder: (context) { + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), + child: StreamBuilder( + stream: FileTransferProvider().flushBarStatusStream, + builder: (context, snapshot) { + final flushbarStatus = snapshot.data ?? FLUSHBAR_STATUS.SENDING; + return Consumer( + builder: (_context, provider, _) { + String text = _getText( + flushbarStatus, + fileTransferProgress: provider.sentFileTransferProgress, + ); - return OverlayEntry(builder: (context) { - final size = MediaQuery.of(context).size; - return Consumer( - builder: (_context, provider, _) { - text = errorMessage ?? - _getText(flushbarStatus, - fileTransferProgress: provider.sentFileTransferProgress); - return Positioned( - width: size.width, - height: 100, - bottom: 0, - child: Material( - child: Container( - alignment: Alignment.center, - color: bgColor, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - flushbarStatus == FLUSHBAR_STATUS.SENDING - ? provider.sentFileTransferProgress != null - ? getProgressBar(provider.sentFileTransferProgress!) - : LinearProgressIndicator() - : SizedBox(), - Padding( - padding: - EdgeInsets.symmetric(vertical: 3, horizontal: 15), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - text, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: flushbarStatus == - FLUSHBAR_STATUS.SENDING - ? Colors.black - : Colors.white, - fontSize: 16.toFont, - fontWeight: FontWeight.normal, + String icon = getImage(flushbarStatus); + return Scaffold( + backgroundColor: bgColor.withOpacity(0.7), + body: SafeArea( + child: Material( + color: bgColor.withOpacity(0.7), + child: Column( + children: [ + Align( + alignment: Alignment.topRight, + child: Padding( + padding: EdgeInsets.only(top: 24, right: 14), + child: InkWell( + onTap: () { + hideOverlay(); + if (flushbarStatus != + FLUSHBAR_STATUS.DONE && + flushbarStatus != + FLUSHBAR_STATUS.FAILED) { + WelcomeScreenProvider() + .changeOverlayStatus(false); + } + }, + child: SvgPicture.asset( + AppVectors.icClose, + height: 52, + width: 52, ), ), - //// Not showing estimated file upload time. - // SizedBox(height: 5), - // Text( - // getFileUploadMessage( - // provider.sentFileTransferProgress, - // ), - // style: TextStyle(fontSize: 12.toFont), - // ), - ], + ), ), - ), - flushbarStatus == FLUSHBAR_STATUS.FAILED - ? TextButton( - onPressed: () { - openFileReceiptBottomSheet(context); - }, - child: Container( + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset(icon), + SizedBox(height: 40), + Padding( padding: EdgeInsets.symmetric( - vertical: 7, horizontal: 7), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - color: Colors.white, + vertical: 3, + horizontal: 15, ), child: Text( - TextStrings.buttonShowMore, + text, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, style: TextStyle( - color: ColorConstants.fontPrimary, - fontSize: 15.toFont, - fontWeight: FontWeight.normal, + color: Colors.black, + fontSize: 25.toFont, + fontWeight: FontWeight.w400, ), ), ), - ) - : TextButton( - onPressed: () { - hideOverlay(); - }, - child: Container( - padding: EdgeInsets.symmetric( - vertical: 7, horizontal: 7), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - color: Colors.white, - ), - child: Text( - TextStrings.buttonDismiss, - style: TextStyle( - color: ColorConstants.fontPrimary, - fontSize: 15.toFont, - fontWeight: FontWeight.normal, - ), - ), - ), - ) - ], + SizedBox(height: 30), + flushbarStatus == FLUSHBAR_STATUS.SENDING + ? getProgressBar() + : _buildHistoryButton(), + ], + ), + ), + ], + ), ), ), - ], - ), - ), - ), + ); + }, + ); + }, + ), + ); + }, + ); + } + + Widget _buildHistoryButton() { + return Padding( + padding: EdgeInsets.only(top: 80), + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () { + hideOverlay(); + Navigator.pushNamedAndRemoveUntil( + NavService.navKey.currentContext!, + Routes.WELCOME_SCREEN, + (route) => false, + arguments: { + "indexBottomBarSelected": 3, + }, ); }, - ); - }); + child: Container( + width: 160.toWidth, + height: 36.toHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: ColorConstants.grey), + ), + child: Center( + child: Text( + 'See History', + style: TextStyle( + color: ColorConstants.grey, + fontSize: 17, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ); } String _getText(FLUSHBAR_STATUS flushbarStatus, @@ -167,11 +178,11 @@ class OverlayService { String sendingMessage = transferMessages[0]; if (fileTransferProgress != null) { if (fileTransferProgress.fileState == FileState.encrypt) { - sendingMessage = 'Encrypting ${fileTransferProgress.fileName}'; + sendingMessage = 'Encrypting your files'; } else if (fileTransferProgress.fileState == FileState.upload) { - sendingMessage = 'Uploading ${fileTransferProgress.fileName}'; + sendingMessage = 'Sending your files'; } else if (fileTransferProgress.fileState == FileState.processing) { - sendingMessage = 'Uploading ${fileTransferProgress.fileName}'; + sendingMessage = 'Sending your files'; } } return sendingMessage; @@ -184,6 +195,19 @@ class OverlayService { } } + String getImage(FLUSHBAR_STATUS flushbarStatus) { + switch (flushbarStatus) { + case FLUSHBAR_STATUS.SENDING: + return ImageConstants.sendFileIcon; + case FLUSHBAR_STATUS.DONE: + return ImageConstants.iconSuccess; + case FLUSHBAR_STATUS.FAILED: + return ImageConstants.iconWarning; + default: + return ImageConstants.sendFileIcon; + } + } + String getFileUploadMessage(FileTransferProgress? fileTransferProgress) { String uploadMessage = ''; @@ -210,17 +234,27 @@ class OverlayService { return uploadMessage; } - Widget getProgressBar(FileTransferProgress fileTransferProgress) { - /// Not showing upload percent - // if (fileTransferProgress.fileState == FileState.upload && - // fileTransferProgress.percent != null) { - // var percent = fileTransferProgress.percent! / 100; - // return LinearProgressIndicator(); - // } - return LinearProgressIndicator(); + Widget getProgressBar() { + return SizedBox( + width: 300.toWidth, + height: 40, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: ProgressBarAnimation( + height: 45, + gradient: const LinearGradient( + colors: [ + Color(0xFFF05E3F), + Color(0xFFEAA743), + ], + ), + backgroundColor: Color(0xFFE2E2E2), + ), + ), + ); } - Color _getColor(FLUSHBAR_STATUS flushbarStatus) { +/* Color _getColor(FLUSHBAR_STATUS flushbarStatus) { switch (flushbarStatus) { case FLUSHBAR_STATUS.SENDING: return Colors.amber; @@ -231,12 +265,12 @@ class OverlayService { default: return Colors.amber; } - } + }*/ List transferMessages = [ - 'Sending file(s) ...', - 'File(s) sent', - 'Oops! something went wrong' + 'Sending your files', + 'Success!', + 'Something went wrong,\nplease try again!', ]; openFileReceiptBottomSheet(context, diff --git a/lib/utils/app_utils.dart b/lib/utils/app_utils.dart new file mode 100644 index 00000000..f73df040 --- /dev/null +++ b/lib/utils/app_utils.dart @@ -0,0 +1,21 @@ +import 'dart:math'; + +class AppUtils { + static String getFileSizeString({required double bytes, int decimals = 0}) { + const suffixes = ["b", "Kb", "Mb", "Gb", "Tb"]; + var i = (log(bytes) / log(1024)).floor(); + return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + + ' ' + + suffixes[i]; + } + + static isFilesAvailableToDownload(String dateString) { + DateTime date = DateTime.parse(dateString); + var expiryDate = date.add(Duration(days: 6)); + if (expiryDate.difference(DateTime.now()) > Duration(seconds: 0)) { + return true; + } + + return false; + } +} diff --git a/lib/utils/colors.dart b/lib/utils/colors.dart index 45ee0b55..326997fe 100644 --- a/lib/utils/colors.dart +++ b/lib/utils/colors.dart @@ -19,10 +19,13 @@ class ColorConstants { static const Color fadedGrey = Color(0xffF1F2F3); static const Color listBackground = Color(0xffF7F7FF); static const Color orangeColor = Color(0xffF05E3F); + static const Color yellow = Color(0xFFEAA743); static const Color MILD_GREY = Color(0xFFE4E4E4); static const Color redAlert = Color(0xffF86060); static const Color red = Color(0xFFe34040); static const Color successGreen = Color(0xFF0ACB21); + static const Color lightGreen = Color(0xFFE1EDCC); + static const Color textGreen = Color(0xFF67A700); static const Color selago = Color(0xFFFFFAFA); static const Color mildGrey = Color(0xFFE4E4E4); static const Color selected_list = Color(0xFFFEF7F7); @@ -32,6 +35,45 @@ class ColorConstants { static const Color light_border_color = Color(0xFFEEF1F4); static const Color textBoxBg = Color(0xFFF2F2F2); static const Color lightBlueBg = Color(0xFFF8FBFF); + static const Color textBlack = Color(0xFF414141); + static const Color textGray = Color(0xFFA7A7A7); + static const Color textGrey = Color(0xFF868686); + static const Color textLightGrey = Color(0xFF868686); + static const Color oldSliver = Color(0xFF838383); + static const Color textLightGray = Color(0xFFBCBCBC); + + //NEW UI COLORS + static const Color fadedGreyN = Color(0xFFF1F1F1); + static const Color dividerGrey = Color(0xFFD9D9D9); + static const Color grey = Color(0xFF939393); + static const Color orange = Color(0xFFF07C50); + static const Color portlandOrange = Color(0xFFFB6232); + static const Color unbleachedSilk = Color(0xFFFFDACC); + static const Color optionalFilterBackgroundColor = Color(0xFFFFE9E1); + static const Color lightGrey = Color(0xFFF1F1F1); + static const Color boxGrey = Color(0xFFEFEFEF); + static const Color darkGray = Color(0xFFC0C0C0); + static const Color buttonGrey = Color(0xFFC7C7C7); + static const Color gray = Color(0xFF9B9B9B); + static const Color gray2 = Color(0xFFB9B9B9); + + static const Color sidebarTextUnselected = Color(0xFFA4A4A5); + static const Color sidebarTextSelected = Color(0xFF000000); + static const Color sidebarTextHeading = Color(0xFFE7E7E7); + static const Color sidebarTileSelected = Color(0xFFF5F5F5); + static const Color grayText = Color(0xFF424242); + static const Color background = Color(0xFFF8F8F8); + static const Color culturedColor = Color(0xFFF4F4F4); + static const Color backgroundTab = Color(0xFFF6F0E6); + static const Color darkSliver = Color(0xFF717171); + static const Color lightGray = Color(0xFFD3D3D3); + static const Color jetColor = Color(0xFF363636); + static const Color lightSliver = Color(0xFFD8D8D8); + static const Color raisinBlack = Color(0xFF252525); + static const Color disableColor = Color(0xFFC3C3C3); + static const Color disableBackgroundColor = Color(0xFFE9E9E9); + static const Color unselectedFilterOptionBackgroundColor = Color(0xFFFAFAFA); + static const Color unselectedFilterOptionColor = Color(0xFF8D8D8D); } class ContactInitialsColors { diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 9b2519fd..130be21b 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -58,7 +58,8 @@ class MixedConstants { static String? ApplicationDocumentsDirectory = ''; /// Sibebar width - static double SIDEBAR_WIDTH = 70; + static double SIDEBAR_WIDTH_COLLAPSED = 180; + static double SIDEBAR_WIDTH_EXPANDED = 280; /// Appbar height static const double APPBAR_HEIGHT = 80; diff --git a/lib/utils/file_types.dart b/lib/utils/file_types.dart index 01818a18..79307977 100644 --- a/lib/utils/file_types.dart +++ b/lib/utils/file_types.dart @@ -25,4 +25,22 @@ class FileTypes { static List WORD_TYPES = ['doc', 'docx', 'DOC', 'DOCX']; static List EXEL_TYPES = ['xls', 'xlsx', 'XLS', 'XLSX']; static List TEXT_TYPES = ['txt', 'TXT']; + static List ZIP_TYPES = [ + 'zip', + 'zipx', + '7z', + 'rar', + 'tar.gz', + 'z', + 'jar' + ]; + + static List ALL_TYPES = IMAGE_TYPES + + VIDEO_TYPES + + AUDIO_TYPES + + PDF_TYPES + + WORD_TYPES + + EXEL_TYPES + + TEXT_TYPES + + ZIP_TYPES; } diff --git a/lib/utils/file_utils.dart b/lib/utils/file_utils.dart new file mode 100644 index 00000000..ef4a4f54 --- /dev/null +++ b/lib/utils/file_utils.dart @@ -0,0 +1,301 @@ +import 'dart:io'; + +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; +import 'package:atsign_atmosphere_pro/screens/history/widgets/edit_bottomsheet.dart'; +import 'package:atsign_atmosphere_pro/screens/my_files/widgets/downloads_folders.dart'; +import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/file_types.dart'; +import 'package:atsign_atmosphere_pro/utils/text_strings.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/file_transfer_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/my_files_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/welcome_screen_view_model.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:path/path.dart' show basename; + +class FileUtils { + static Future deleteFile( + String filePath, { + String? fileTransferId, + Function()? onComplete, + }) async { + await showModalBottomSheet( + context: NavService.navKey.currentContext!, + backgroundColor: Colors.white, + builder: (context) => EditBottomSheet( + onConfirmation: () async { + var file = File(filePath); + if (await file.exists()) { + file.deleteSync(); + } + if (fileTransferId != null) { + await Provider.of( + NavService.navKey.currentContext!, + listen: false) + .removeParticularFile(fileTransferId, + filePath.split(Platform.pathSeparator).last); + + await Provider.of( + NavService.navKey.currentContext!, + listen: false) + .getAllFiles(); + } + onComplete; + }, + deleteMessage: TextStrings.deleteFileConfirmationMsgMyFiles, + ), + ); + } + + static Future moveToSendFile(String filePath) async { + final file = File(filePath); + final length = await file.length(); + FileTransferProvider.appClosedSharedFiles.add( + PlatformFile( + name: basename(file.path), + path: file.path, + size: length.round(), + bytes: await file.readAsBytes()), + ); + Provider.of(NavService.navKey.currentContext!, + listen: false) + .setFiles(); + Provider.of(NavService.navKey.currentContext!, + listen: false) + .changeBottomNavigationIndex(0); + } + + static Future openFile({ + required String path, + required String extension, + required Function() onDelete, + required FilesDetail fileDetail, + }) async { + GroupService().allContacts; + String nickname = ""; + final date = DateTime.parse(fileDetail.date ?? "").toLocal(); + final shortDate = DateFormat('dd/MM/yy').format(date); + final time = DateFormat('HH:mm').format(date); + + for (var contact in GroupService().allContacts) { + if (contact?.contact?.atSign == fileDetail.contactName) { + nickname = contact?.contact?.tags?["nickname"] ?? ""; + break; + } + } + if (FileTypes.IMAGE_TYPES.contains(extension)) { + await showDialog( + context: NavService.navKey.currentContext!, + builder: (_) => Material( + type: MaterialType.transparency, + child: Column( + children: [ + SizedBox( + height: 10, + ), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(left: 32), + child: InkWell( + onTap: () { + Navigator.pop(NavService.navKey.currentContext!); + }, + child: Icon( + Icons.clear, + color: Colors.white, + size: 24, + ), + ), + ), + ), + SizedBox( + height: 10, + ), + Expanded( + child: Container( + // height: double.infinity, + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 33), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + image: DecorationImage( + image: FileImage( + File(path), + ), + fit: BoxFit.cover, + ), + ), + ), + ), + SizedBox( + height: 40, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + AppVectors.icDownloadFile, + height: 50, + width: 50, + ), + ), + SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + Navigator.pop(NavService.navKey.currentContext!); + Navigator.pop(NavService.navKey.currentContext!); + await FileUtils.moveToSendFile(path); + }, + child: SvgPicture.asset( + AppVectors.icSendFile, + height: 50, + width: 50, + ), + ), + ), + SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: GestureDetector( + onTap: () async { + await onDelete(); + }, + child: SvgPicture.asset( + AppVectors.icDeleteFile, + height: 50, + width: 50, + ), + ), + ), + ], + ), + SizedBox( + height: 40, + ), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + padding: EdgeInsets.all(20), + margin: EdgeInsets.symmetric(horizontal: 25), + width: double.infinity, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + fileDetail.fileName ?? "", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 12), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "$shortDate", + style: TextStyle( + fontSize: 12, + color: ColorConstants.oldSliver, + ), + ), + Container( + width: 1, + height: 8, + color: Color(0xFFD7D7D7), + margin: EdgeInsets.symmetric( + horizontal: 3, + ), + ), + Text( + "$time", + style: TextStyle( + fontSize: 12, + color: ColorConstants.oldSliver, + ), + ), + ], + ), + ], + ), + SizedBox(height: 5), + Text( + double.parse(fileDetail.size.toString()) <= 1024 + ? '${fileDetail.size} ' + TextStrings().kb + : '${(fileDetail.size! / (1024 * 1024)).toStringAsFixed(2)} ' + + TextStrings().mb, + style: TextStyle( + color: ColorConstants.grey, + fontSize: 12, + ), + textAlign: TextAlign.left, + ), + SizedBox(height: 10), + nickname.isNotEmpty + ? Text( + nickname, + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.bold), + ) + : SizedBox(), + SizedBox(height: 5), + Text( + fileDetail.contactName ?? "", + style: TextStyle( + fontSize: 14, + ), + ), + SizedBox(height: 10), + // fileDetail.message.isNotNull + // ? + Text( + "Message", + style: TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + // : SizedBox(), + SizedBox(height: 5), + Text( + fileDetail.message ?? "", + style: TextStyle( + fontSize: 14, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } else { + await openFilePath(path); + } + } +} diff --git a/lib/utils/images.dart b/lib/utils/images.dart index 644c93c2..fe7eeedb 100644 --- a/lib/utils/images.dart +++ b/lib/utils/images.dart @@ -1,7 +1,7 @@ class ImageConstants { static String _basePath = 'assets/images'; - static String welcomeBackground = '$_basePath/welcome_bg.png'; + static String welcomeBackground = '$_basePath/new_welcome_bg.png'; static String logoIcon = '$_basePath/logo.png'; // static String myFiles = '$_basePath/myfiles.png'; static String group = '$_basePath/group.png'; @@ -14,7 +14,7 @@ class ImageConstants { static String transferHistoryIcon = '$_basePath/transferHistory.png'; static String trustedSendersIcon = '$_basePath/trustedSendersIcon.png'; static String logoutIcon = '$_basePath/logout.png'; - static String sendIcon = '$_basePath/send.png'; + static String sendIcon = '$_basePath/send_icon.png'; static String imagePlaceholder = '$_basePath/imagePlaceholder.png'; static String pdfLogo = '$_basePath/pdf.jpeg'; static String musicLogo = '$_basePath/music.png'; @@ -23,6 +23,7 @@ class ImageConstants { static String txtLogo = '$_basePath/txtLogo.png'; static String unknownLogo = '$_basePath/unknown.png'; static String videoLogo = '$_basePath/video-camera.png'; + static String openFileIcon = '$_basePath/open_file_icon.png'; static String emptyTrustedSenders = '$_basePath/emptyTrustedSenders.png'; static String emptyGroup = '$_basePath/empty_group.png'; static String profileImage = '$_basePath/profile.png'; @@ -36,12 +37,24 @@ class ImageConstants { static String documentFile = '$_basePath/documentFile.png'; static String exclamation = '$_basePath/exclamation.png'; static String close = '$_basePath/close.png'; + static String closeIcon = '$_basePath/close_icon.png'; static String trustedSender = '$_basePath/trustedSendersIcon.png'; static String myFiles = '$_basePath/my_files.png'; static String groups = '$_basePath/groups.png'; static String contactUs = '$_basePath/contactUs.png'; + static String settings = '$_basePath/settings.png'; // TEST FILE DELETE WHEN IN PRODUCTION static String test = '$_basePath/test.jpg'; + static String iconSuccess = '$_basePath/icon_success.png'; + static String iconWarning = '$_basePath/icon_warning.png'; + + // New UI Settings Screen + static String switchAtSign = '$_basePath/switch_atsign.png'; + static String backupKeys = '$_basePath/backup_keys.png'; + static String faqs = '$_basePath/faq.png'; + static String contactUsLogo = '$_basePath/contact_us.png'; + static String termsAndConditions = '$_basePath/terms.png'; + static String deleteAtsigns = '$_basePath/delete_atsigns.png'; // Sample profile pictures static String barbara = '$_basePath/barbara.jpg'; @@ -50,12 +63,38 @@ class ImageConstants { static String filePreview = '$_basePath/filePreview.jpg'; // Desktop + static String sendFileIcon = '$_basePath/send_file_icon.png'; static String welcomeDesktop = '$_basePath/welcome_screen.png'; static String groupPhotoDesktop = '$_basePath/group_photo.png'; static String homeBgDesktop = '$_basePath/home_bg.png'; static String homeIcon = '$_basePath/home_icon.png'; + static String backgroundDesktop = '$_basePath/desktop_bg.png'; static String homeCaraousel1 = '$_basePath/home_caraousel_2.png'; static String homeCaraousel2 = '$_basePath/home_caraousel_1.png'; static String homeCaraousel3 = '$_basePath/home_caraousel_3.png'; + + static String sidebarSelectedTile = '$_basePath/selected_sidebar.png'; + static String sidebarSettings = '$_basePath/settings.png'; + + static String emptyBox = '$_basePath/img_empty_box.png'; + static String uploadIcon = '$_basePath/img_upload.png'; + + static String icFileActivate = '$_basePath/ic_attach_activate.png'; + static String icFileInactivate = '$_basePath/ic_attach_inactivate.png'; + static String icHistoryActivate = '$_basePath/ic_history_activate.png'; + static String icHistoryInactivate = '$_basePath/ic_history_inactivate.png'; + static String icSendActivate = '$_basePath/ic_send_activate.png'; + static String icSendInactivate = '$_basePath/ic_send_inactivate.png'; + static String icSettingActivate = '$_basePath/ic_setting_activate.png'; + static String icSettingInactivate = '$_basePath/ic_setting_inactivate.png'; + static String icUserActivate = '$_basePath/ic_user_activate.png'; + static String icUserInactivate = '$_basePath/ic_user_inactivate.png'; + static String icImage = '$_basePath/ic_image.png'; + + static String icGridTypeActivate = '$_basePath/ic_grid_type_activate.png'; + static String icGridType = '$_basePath/ic_grid_type.png'; + static String icListTypeActivate = '$_basePath/ic_list_type_activate.png'; + static String icListType = '$_basePath/ic_list_type.png'; + static String icCloudDownloading = '$_basePath/ic_cloud_downloading.png'; } diff --git a/lib/utils/text_strings.dart b/lib/utils/text_strings.dart index d9e0d655..16fc3c9a 100644 --- a/lib/utils/text_strings.dart +++ b/lib/utils/text_strings.dart @@ -38,8 +38,13 @@ class TextStrings { String oopsSomethingWentWrong = 'Oops! something went wrong'; String hello = 'hello'; String reset = 'Reset'; + String selectFiles = 'SELECT FILES'; + String selectContacts = 'SELECT CONTACTS'; //sidebar menu texts + String sidebarGeneral = 'GENERAL'; + String sidebarHelpCenter = 'HELPCENTER'; + String sidebarSendFiles = 'Send Files'; String sidebarHome = 'Home'; String sidebarContact = 'Contacts'; String sidebarTransferHistory = 'Transfer History'; @@ -57,6 +62,9 @@ class TextStrings { String sidebarSwitchOut = 'Switch atSign'; String sidebarDeleteAtsign = 'Delete atSign(s)'; String sidebarContactUs = 'Contact us'; + String sidebarSettings = 'Settings'; + + String general = 'General'; String atSign = 'atSign'; String switchingAtSign = 'Switching atsign...'; @@ -163,7 +171,7 @@ class TextStrings { String removeGroupPhoto = 'Remove Group Photo'; // terms and conditions texts - String termsAppBar = 'Terms and Conditions'; + String termsAppBar = 'Privacy Policy'; String termsAndConditions = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\n Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \n\n Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; @@ -324,6 +332,13 @@ class TextStrings { String mayBeLater = 'Maybe later'; String to = 'to'; + //New UI Setting Screen + String switchatSign = 'Switch atSign'; + String backUpKeys = 'Backup Your Keys'; + String contactUs = 'Contact Us'; + String deleteAtsigns = 'Delete Your Key'; + String blockedAtSign = 'Blocked atSign'; + static final String resetButton = 'Reset'; static const String resetDescription = 'This will remove the selected atSign and its details from this app only.'; @@ -354,4 +369,9 @@ class TextStrings { static const noInternetMsg = 'No internet available'; static const permissionRequireMessage = 'This action cannot be completed because the app does not have permission to access the required items. Please change the app permission settings to continue.'; + String noContactsFound = 'No results'; + String contactEmpty = 'Empty contact'; + String serviceError = 'Something went wrong, please try again.'; + String groupAlreadyExists = 'Group with this name already exists.'; + String groupEmptyName = 'Add a group name'; } diff --git a/lib/utils/text_styles.dart b/lib/utils/text_styles.dart index 4e2bd4a7..5215cffc 100644 --- a/lib/utils/text_styles.dart +++ b/lib/utils/text_styles.dart @@ -1,8 +1,6 @@ -import 'dart:ui'; - +import 'package:at_common_flutter/services/size_config.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:flutter/material.dart'; -import 'package:at_common_flutter/services/size_config.dart'; class CustomTextStyles { //REGULAR FONTS @@ -36,6 +34,12 @@ class CustomTextStyles { letterSpacing: 0.1, fontWeight: FontWeight.normal); + static TextStyle black12 = TextStyle( + color: Colors.black, + fontSize: 12.toFont, + letterSpacing: 0.1, + fontWeight: FontWeight.normal); + static TextStyle secondaryRegular14 = TextStyle( color: ColorConstants.fontSecondary, fontSize: 14.toFont, @@ -97,6 +101,12 @@ class CustomTextStyles { fontWeight: FontWeight.normal); //BOLD FONTS + static TextStyle blackBold25 = TextStyle( + color: Colors.black, + fontSize: 25.toFont, + letterSpacing: 0.1, + fontWeight: FontWeight.w700, + ); static TextStyle whiteBold16 = TextStyle( color: Colors.white, fontSize: 16.toFont, @@ -145,6 +155,14 @@ class CustomTextStyles { fontWeight: FontWeight.w700, ); + //SEMIBOLD FONTS + static TextStyle greySemiBold18 = TextStyle( + color: Color(0xFF939393), + fontSize: 18.toFont, + letterSpacing: 0.1, + fontWeight: FontWeight.w600, + ); + //MEDIUM FONTS static TextStyle primaryMedium14 = TextStyle( @@ -175,6 +193,13 @@ class CustomTextStyles { fontWeight: FontWeight.w500, ); + static TextStyle whiteMedium18 = TextStyle( + color: Colors.white, + fontSize: 18.toFont, + letterSpacing: 0.1, + fontWeight: FontWeight.w500, + ); + /// Desktop static TextStyle greyText16 = TextStyle( @@ -245,18 +270,36 @@ class CustomTextStyles { letterSpacing: 0.1, fontWeight: FontWeight.bold); + static TextStyle desktopPrimaryBold12 = TextStyle( + color: Colors.black, + fontSize: 10, + fontWeight: FontWeight.w600, + ); + static TextStyle desktopPrimaryBold18 = TextStyle( color: ColorConstants.fontPrimary, fontSize: 18, letterSpacing: 0.1, fontWeight: FontWeight.bold); + static TextStyle desktopSecondaryBold12 = TextStyle( + color: ColorConstants.sidebarTextHeading, + fontSize: 10, + letterSpacing: 0.1, + fontWeight: FontWeight.w700); + static TextStyle desktopBlackPlayfairDisplay26 = TextStyle( fontFamily: 'PlayfairDisplay', fontSize: 44, color: Colors.black, ); + static TextStyle desktopPrimaryRegular12 = TextStyle( + color: Colors.black, + fontSize: 12, + letterSpacing: 0.1, + fontWeight: FontWeight.normal); + static TextStyle desktopPrimaryRegular14 = TextStyle( color: Colors.black, fontSize: 14, @@ -281,4 +324,10 @@ class CustomTextStyles { letterSpacing: 0.1, fontWeight: FontWeight.w600, ); + + static TextStyle desktopButton15 = TextStyle( + color: Colors.white, + fontSize: 15, + letterSpacing: 0.1, + fontWeight: FontWeight.w700); } diff --git a/lib/utils/vectors.dart b/lib/utils/vectors.dart new file mode 100644 index 00000000..72b28a5d --- /dev/null +++ b/lib/utils/vectors.dart @@ -0,0 +1,74 @@ +class AppVectors { + static String _basePath = 'assets/svg'; + static String appIcon = '$_basePath/app_icon.svg'; + static String icReload = '$_basePath/ic_reload.svg'; + static String icSearch = '$_basePath/ic_search.svg'; + static String icArrowDown = '$_basePath/ic_arrow_down.svg'; + static String icCheck = '$_basePath/ic_check.svg'; + static String icUnCheck = '$_basePath/ic_unchecked.svg'; + static String icCancel = '$_basePath/ic_cancel.svg'; + static String icReceive = '$_basePath/ic_receive.svg'; + static String icSend = '$_basePath/ic_send.svg'; + static String icSort = '$_basePath/ic_sort.svg'; + static String icReceiveBorder = '$_basePath/ic_receive_border.svg'; + static String icSendBorder = '$_basePath/ic_send_border.svg'; + static String icNote = '$_basePath/ic_note.svg'; + static String icActivate = '$_basePath/ic_activate.svg'; + static String icAdd = '$_basePath/ic_add.svg'; + static String icBlock = '$_basePath/ic_block.svg'; + static String icContactGroup = '$_basePath/ic_contact_group.svg'; + static String icTrust = '$_basePath/ic_trust.svg'; + static String icBigTrust = '$_basePath/ic_big_trust.svg'; + static String icWhiteTrust = '$_basePath/ic_white_trust.svg'; + static String icBigTrustActivated = '$_basePath/ic_big_trust_activated.svg'; + static String icTrash = '$_basePath/ic_trash.svg'; + static String icArrow = '$_basePath/ic_arrow.svg'; + static String icImage = '$_basePath/ic_image.svg'; + static String icClose = '$_basePath/ic_close.svg'; + static String icTrustActivated = '$_basePath/ic_trust_activated.svg'; + static String icFile = '$_basePath/ic_file.svg'; + static String icBack = '$_basePath/ic_back.svg'; + static String icPlus11px = '$_basePath/ic_plus_11px.svg'; + static String icImageGray = '$_basePath/ic_image_gray.svg'; + + static String icSettingBlock = '$_basePath/ic_setting_block.svg'; + static String icSettingBackup = '$_basePath/ic_setting_backup.svg'; + static String icSettingSwitch = '$_basePath/ic_setting_switch.svg'; + static String icSettingDelete = '$_basePath/ic_setting_delete.svg'; + static String icSettingFAQ = '$_basePath/ic_setting_faq.svg'; + static String icSettingContactUs = '$_basePath/ic_setting_contact_us.svg'; + static String icSettingPrivacy = '$_basePath/ic_setting_privacy.svg'; + static String icFilter = '$_basePath/ic_filter.svg'; + static String icFilterOpened = '$_basePath/ic_filter_opened.svg'; + static String icBannerOverlay = '$_basePath/ic_banner_overlay.svg'; + + static String icCategoryVolume = '$_basePath/ic_category_volume.svg'; + static String icCategoryFolder = '$_basePath/ic_category_folder.svg'; + static String icCategoryImage = '$_basePath/ic_category_image.svg'; + static String icCategoryOther = '$_basePath/ic_category_other.svg'; + static String icCategoryPlay = '$_basePath/ic_category_play.svg'; + static String icCategoryFiles = '$_basePath/ic_category_files.svg'; + static String icArrowRight = '$_basePath/ic_arrow_right.svg'; + static String icGridType = '$_basePath/ic_grid_type.svg'; + static String icListType = '$_basePath/ic_list_type.svg'; + static String icDeleteFile = '$_basePath/ic_delete_file.svg'; + static String icSendFile = '$_basePath/ic_send_file.svg'; + static String icDownloadFile = '$_basePath/ic_download_file.svg'; + static String icArrowUpOutline = '$_basePath/ic_arrow_up_outline.svg'; + static String icArrowDownOutline = '$_basePath/ic_arrow_down_outline.svg'; + static String icCloudDownloaded = '$_basePath/ic_cloud_downloaded.svg'; + static String icCloudDownloading = '$_basePath/ic_cloud_downloading.svg'; + + static String icArrowDesc = '$_basePath/ic_arrow_desc.svg'; + static String icArrowAsc = '$_basePath/ic_arrow_asc.svg'; + static String icReceived = '$_basePath/ic_received.svg'; + static String icSent = '$_basePath/ic_sent.svg'; + static String icPhotos = '$_basePath/ic_photos.svg'; + static String icFiles = '$_basePath/ic_files.svg'; + static String icAudio = '$_basePath/ic_audio.svg'; + static String icVideos = '$_basePath/ic_videos.svg'; + static String icZips = '$_basePath/ic_zips.svg'; + static String icOther = '$_basePath/ic_other.svg'; + static String icChecked = '$_basePath/ic_checked.svg'; + static String icUnchecked = '$_basePath/ic_uncheck.svg'; +} diff --git a/lib/view_models/add_contact_provider.dart b/lib/view_models/add_contact_provider.dart new file mode 100644 index 00000000..4bc808d3 --- /dev/null +++ b/lib/view_models/add_contact_provider.dart @@ -0,0 +1,67 @@ +import 'package:at_contacts_flutter/services/contact_service.dart'; +import 'package:at_server_status/at_status_impl.dart'; +import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; + +class AddContactProvider extends BaseModel { + String addContactStatus = 'add_contact_status'; + ContactService contactService = ContactService(); + bool isVerify = false; + String atSignError = ''; + + void initData() { + contactService.resetData(); + isVerify = false; + atSignError = ''; + } + + void changeVerifyStatus(bool verify) { + if (verify) atSignError = ''; + isVerify = verify; + notifyListeners(); + } + + Future checkValid(String text) async { + if (text.isNotEmpty) { + setStatus(addContactStatus, Status.Loading); + try { + final atStatus = await AtStatusImpl().get(text); + if (atStatus.serverLocation == null) { + changeVerifyStatus(false); + } else { + changeVerifyStatus(true); + } + setStatus(addContactStatus, Status.Done); + } catch (e) { + setStatus(addContactStatus, Status.Error); + } + } else { + changeVerifyStatus(false); + } + } + + Future addContact({ + required String atSign, + required String nickname, + }) async { + setStatus(addContactStatus, Status.Loading); + try { + await Future.delayed(Duration(seconds: 2)); + var response = await contactService.addAtSign( + atSign: atSign, + nickName: nickname, + ); + + if (response && (contactService.checkAtSign ?? false)) { + setStatus(addContactStatus, Status.Done); + return true; + } else { + atSignError = contactService.getAtSignError; + changeVerifyStatus(false); + setStatus(addContactStatus, Status.Done); + } + } catch (e) { + setStatus(addContactStatus, Status.Error); + } + return null; + } +} diff --git a/lib/view_models/create_group_provider.dart b/lib/view_models/create_group_provider.dart new file mode 100644 index 00000000..7d6babfe --- /dev/null +++ b/lib/view_models/create_group_provider.dart @@ -0,0 +1,86 @@ +import 'dart:io'; + +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_group_flutter/models/group_contacts_model.dart'; +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; +import 'package:flutter/services.dart'; +import 'package:image_picker/image_picker.dart'; + +class CreateGroupProvider extends BaseModel { + bool isLoading = false; + Uint8List? selectedImageByteData; + List listContact = []; + GroupService groupService = GroupService(); + String groupName = ''; + String searchKeyword = ''; + + Future selectCoverImage() async { + var image = await ImagePicker().pickImage( + source: ImageSource.gallery, + ); + if (image != null) { + selectedImageByteData = await File(image.path).readAsBytes(); + notifyListeners(); + } + } + + void resetData() { + listContact = []; + notifyListeners(); + } + + void removeSelectedImage() { + selectedImageByteData = null; + } + + void setGroupName(String name) { + groupName = name; + notifyListeners(); + } + + void setSearchKeyword(String keyword) { + searchKeyword = keyword; + notifyListeners(); + } + + void addGroupContacts(List list) { + listContact = []; + for (var element in list) { + listContact.add(element.contact!); + } + notifyListeners(); + } + + Future createGroup({ + required Function(dynamic) whenComplete, + required Function() whenNameIsEmpty, + }) async { + if (groupName.isNotEmpty) { + isLoading = true; + notifyListeners(); + + var group = AtGroup( + groupName.trim(), + description: 'group desc', + displayName: groupName.trim(), + members: Set.from(listContact), + createdBy: groupService.currentAtsign, + updatedBy: groupService.currentAtsign, + ); + + if (selectedImageByteData != null) { + group.groupPicture = selectedImageByteData; + } + + var result = await groupService.createGroup(group); + + isLoading = false; + notifyListeners(); + + whenComplete(result); + } else { + whenNameIsEmpty; + } + } +} diff --git a/lib/view_models/file_transfer_provider.dart b/lib/view_models/file_transfer_provider.dart index 02d5035d..05188d1a 100644 --- a/lib/view_models/file_transfer_provider.dart +++ b/lib/view_models/file_transfer_provider.dart @@ -8,7 +8,6 @@ import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; import 'package:atsign_atmosphere_pro/data_models/file_transfer_status.dart'; import 'package:atsign_atmosphere_pro/routes/route_names.dart'; import 'package:atsign_atmosphere_pro/screens/common_widgets/permission_dialog.dart'; -import 'package:atsign_atmosphere_pro/services/backend_service.dart'; import 'package:atsign_atmosphere_pro/services/exception_service.dart'; import 'package:atsign_atmosphere_pro/services/file_transfer_service.dart'; import 'package:atsign_atmosphere_pro/services/navigation_service.dart'; @@ -23,7 +22,6 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:path/path.dart' show basename; -import 'package:at_client/src/stream/file_transfer_object.dart'; class FileTransferProvider extends BaseModel { FileTransferProvider._(); @@ -136,6 +134,11 @@ class FileTransferProvider extends BaseModel { } } + void deleteFiles(int fileIndex) { + selectedFiles.removeAt(fileIndex); + notifyListeners(); + } + showNoPermissionDialog() async { await showDialog( context: NavService.navKey.currentContext!, @@ -257,6 +260,8 @@ class FileTransferProvider extends BaseModel { groupName: groupName, ); + _historyProvider.changeIsUpcomingEvent(); + // checking if everyone received the notification or not. for (var atsignStatus in uploadResult.entries) { if (atsignStatus.value.sharedStatus != null && @@ -362,36 +367,43 @@ class FileTransferProvider extends BaseModel { var uploadStatus = await FileTransferService.getInstance() .reuploadFiles([file], _sentHistory.fileTransferObject!); - if (uploadStatus is List && uploadStatus.isNotEmpty) { - if (uploadStatus[0].isUploaded!) { - var index = _sentHistory.fileDetails!.files! - .indexWhere((element) => element.name == _filesList[_index].name); - - if (index > -1) { - _sentHistory.fileDetails!.files![index].isUploaded = true; - } - - var i = _sentHistory.fileTransferObject!.fileStatus.indexWhere( - (element) => element.fileName == _filesList[_index].name); - if (i > -1) { - _sentHistory.fileTransferObject!.fileStatus[i].isUploaded = true; - } - - // sending file upload notification to every atsign - await Future.forEach(_sentHistory.sharedWith!, - (ShareStatus sharedWith) async { - await reSendFileNotification(_sentHistory, sharedWith.atsign!); - }); + if (uploadStatus.isNotEmpty && uploadStatus[0].isUploaded!) { + var index = _sentHistory.fileDetails!.files! + .indexWhere((element) => element.name == _filesList[_index].name); - Provider.of(NavService.navKey.currentContext!, - listen: false) - .updateFileSendingStatus( - isUploading: false, - isUploaded: true, - id: _sentHistory.fileDetails!.key, - filename: _filesList[_index].name, - ); + if (index > -1) { + _sentHistory.fileDetails!.files![index].isUploaded = true; + } + + var i = _sentHistory.fileTransferObject!.fileStatus.indexWhere( + (element) => element.fileName == _filesList[_index].name); + if (i > -1) { + _sentHistory.fileTransferObject!.fileStatus[i].isUploaded = true; } + + // sending file upload notification to every atsign + await Future.forEach(_sentHistory.sharedWith!, + (ShareStatus sharedWith) async { + await reSendFileNotification(_sentHistory, sharedWith.atsign!); + }); + + Provider.of(NavService.navKey.currentContext!, + listen: false) + .updateFileSendingStatus( + isUploading: false, + isUploaded: true, + id: _sentHistory.fileDetails!.key, + filename: _filesList[_index].name, + ); + } else { + Provider.of(NavService.navKey.currentContext!, + listen: false) + .updateFileSendingStatus( + isUploading: false, + isUploaded: false, + id: _sentHistory.fileDetails!.key, + filename: _filesList[_index].name, + ); } setStatus(RETRY_NOTIFICATION, Status.Done); } catch (e) { diff --git a/lib/view_models/history_provider.dart b/lib/view_models/history_provider.dart index 0a0002b7..3160450a 100644 --- a/lib/view_models/history_provider.dart +++ b/lib/view_models/history_provider.dart @@ -1,25 +1,13 @@ import 'dart:convert'; import 'dart:io'; import 'package:at_client_mobile/at_client_mobile.dart'; -import 'package:at_commons/at_commons.dart'; import 'package:at_contacts_flutter/services/contact_service.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/file_category_type.dart'; +import 'package:atsign_atmosphere_pro/data_models/file_entity.dart'; import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; import 'package:atsign_atmosphere_pro/data_models/file_transfer_object.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_apk.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_audios.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_documents.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_photos.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_recent.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_unknowns.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_videos.dart'; -import 'package:atsign_atmosphere_pro/screens/my_files/widgets/apk.dart'; -import 'package:atsign_atmosphere_pro/screens/my_files/widgets/audios.dart'; -import 'package:atsign_atmosphere_pro/screens/my_files/widgets/documents.dart'; -import 'package:atsign_atmosphere_pro/screens/my_files/widgets/photos.dart'; -import 'package:atsign_atmosphere_pro/screens/my_files/widgets/recents.dart'; -import 'package:atsign_atmosphere_pro/screens/my_files/widgets/unknowns.dart'; -import 'package:atsign_atmosphere_pro/screens/my_files/widgets/videos.dart'; import 'package:atsign_atmosphere_pro/services/backend_service.dart'; import 'package:atsign_atmosphere_pro/services/exception_service.dart'; import 'package:atsign_atmosphere_pro/services/file_transfer_service.dart'; @@ -28,16 +16,15 @@ import 'package:atsign_atmosphere_pro/services/notification_service.dart'; import 'package:atsign_atmosphere_pro/services/snackbar_service.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; import 'package:atsign_atmosphere_pro/utils/constants.dart'; +import 'package:atsign_atmosphere_pro/utils/file_types.dart'; import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; import 'package:atsign_atmosphere_pro/view_models/file_download_checker.dart'; import 'package:atsign_atmosphere_pro/view_models/file_progress_provider.dart'; +import 'package:atsign_atmosphere_pro/view_models/my_files_provider.dart'; import 'package:flutter/cupertino.dart'; -// import 'package:at_client/src/stream/file_transfer_object.dart'; import 'package:flutter/services.dart'; -import 'package:at_client/src/service/notification_service.dart'; import 'package:provider/provider.dart'; -import 'file_download_checker.dart'; import 'trusted_sender_view_model.dart'; class HistoryProvider extends BaseModel { @@ -50,14 +37,29 @@ class HistoryProvider extends BaseModel { String SET_FILE_HISTORY = 'set_flie_history'; String SET_RECEIVED_HISTORY = 'set_received_history'; String GET_ALL_FILE_DATA = 'get_all_file_data'; + String GET_ALL_FILE_HISTORY = 'get_all_file_history'; + String GET_FILE_STATUS = 'get_file_status'; String DOWNLOAD_FILE = 'download_file'; String DOWNLOAD_ACK = 'download_ack'; - List sentHistory = [], tempSentHistory = []; + + List sentHistory = [], + tempSentHistory = [], + receivedFileHistory = [], + allFilesHistory = [], + displayFilesHistory = []; + + List listType = FileType.values.toList(); + List receivedHistoryLogs = []; + Map> downloadedFileAcknowledgement = {}; Map individualSentFileId = {}, receivedItemsId = {}; String? state; String _historySearchText = ''; + bool isDesc = true; + bool hadNewFile = false; + + List allFiles = []; // on first transfer history fetch, we show loader in history screen. // on second attempt we keep the status as idle. @@ -68,24 +70,11 @@ class HistoryProvider extends BaseModel { sentApk, sentDocument = []; - List receivedPhotos = [], - receivedVideos = [], - receivedAudio = [], - receivedApk = [], - receivedDocument = [], - recentFile = [], - receivedUnknown = []; - List tabNames = ['Recents']; - - List tabs = [Recents()]; - + List recentFile = []; List desktopTabs = [DesktopRecents()]; - String SORT_FILES = 'sort_files'; - String POPULATE_TABS = 'populate_tabs'; Map sendFileHistory = {'history': []}; - String SORT_LIST = 'sort_list'; - BackendService backendService = BackendService.getInstance(); String? app_lifecycle_state; + HistoryType typeSelected = HistoryType.all; resetData() { isSyncedDataFetched = false; @@ -93,15 +82,14 @@ class HistoryProvider extends BaseModel { receivedHistoryLogs = []; sendFileHistory = {'history': []}; downloadedFileAcknowledgement = {}; - receivedPhotos = []; - receivedVideos = []; - receivedAudio = []; - receivedApk = []; - receivedDocument = []; recentFile = []; - receivedUnknown = []; individualSentFileId = {}; receivedItemsId = {}; + tempSentHistory = []; + receivedFileHistory = []; + allFilesHistory = []; + displayFilesHistory = []; + notifyListeners(); } String get getSearchText => _historySearchText; @@ -111,6 +99,18 @@ class HistoryProvider extends BaseModel { notifyListeners(); } + void changeIsUpcomingEvent() { + hadNewFile = !hadNewFile; + notifyListeners(); + } + + void resetOptional() { + if (typeSelected == HistoryType.all && listType.isEmpty) { + listType = FileType.values.toList(); + notifyListeners(); + } + } + updateFileHistoryDetail(FileHistory fileHistory) async { // checking whether sent file is stored in individual atKey or in sentHistory list. if (individualSentFileId[fileHistory.fileDetails!.key] != null) { @@ -169,9 +169,34 @@ class HistoryProvider extends BaseModel { )); if (res) { individualSentFileId[fileHistory.fileDetails!.key] = true; - isEdit - ? updateFileEntryInSentHistory(fileHistory) - : sentHistory.insert(0, fileHistory); + if (isEdit) { + updateFileEntryInSentHistory(fileHistory); + } else { + sentHistory.insert(0, fileHistory); + + // + FileTransfer? fileTransfer = fileHistory.fileDetails; + if ((fileTransfer?.files?.length ?? 0) > 0) { + for (int j = 0; j < fileTransfer!.files!.length; j++) { + for (int k = 0; k < fileHistory.sharedWith!.length; k++) { + allFiles.insert( + 0, + FileEntity( + file: fileTransfer.files![j], + date: fileTransfer.date != null + ? fileTransfer.date.toString() + : '', + atSign: fileHistory.sharedWith![k].atsign, + historyType: HistoryType.send, + note: fileTransfer.notes, + transferId: fileTransfer.key, + isUploaded: fileTransfer.files?[j].isUploaded ?? false, + fileTransferObject: fileHistory.fileTransferObject!), + ); + } + } + } + } } notifyListeners(); return res; @@ -189,7 +214,10 @@ class HistoryProvider extends BaseModel { // with new approach we are saving data in individual keys // 2 -- every sent file data is stored individually in `file_transfer_[ID]` key. /// [getSentHistory] will get data from both keys and store them into [sentHistory] variable. - getSentHistory({bool setLoading = true}) async { + getSentHistory({ + bool setLoading = true, + List? listFileTypeSelect, + }) async { if (setLoading) { setStatus(SENT_HISTORY, Status.Loading); } @@ -217,10 +245,10 @@ class HistoryProvider extends BaseModel { individualSentFileId[atkey.key] = true; }); - if (!isNewKeyAvailable) { - setStatus(SENT_HISTORY, Status.Done); - return; - } + // if (!isNewKeyAvailable) { + // setStatus(SENT_HISTORY, Status.Done); + // return; + // } tempSentHistory = []; @@ -229,25 +257,65 @@ class HistoryProvider extends BaseModel { ..key = MixedConstants.SENT_FILE_HISTORY ..sharedBy = AtClientManager.getInstance().atClient.getCurrentAtSign() ..metadata = Metadata(); + var keyValue = await AtClientManager.getInstance().atClient.get(key).catchError((e) { print('error in getSentHistory : $e'); ExceptionService.instance.showGetExceptionOverlay(e); return AtValue(); }); + if (keyValue != null && keyValue.value != null) { try { Map historyFile = json.decode((keyValue.value) as String) as Map; sendFileHistory['history'] = historyFile['history']; - historyFile['history'].forEach((value) { - FileHistory filesModel = FileHistory.fromJson(value); - // checking for download acknowledged - filesModel.sharedWith = checkIfileDownloaded( - filesModel.sharedWith, - filesModel.fileTransferObject!.transferId, - ); - tempSentHistory.add(filesModel); - }); + + historyFile['history'].forEach( + (value) { + FileHistory filesModel = FileHistory.fromJson(value); + // checking for download acknowledged + filesModel.sharedWith = checkIfileDownloaded( + filesModel.sharedWith, + filesModel.fileTransferObject!.transferId, + ); + + if ((listFileTypeSelect ?? []).isNotEmpty) { + List files = []; + + if ((filesModel.fileDetails?.files ?? []).isNotEmpty) { + Future.forEach( + filesModel.fileDetails!.files!, + (dynamic file) async { + String? fileExtension = file.name.split('.').last; + for (int i = 0; i < listFileTypeSelect!.length; i++) { + if (FileTypes.ALL_TYPES.contains(fileExtension)) { + if (listFileTypeSelect[i] + .suffixName + .contains(fileExtension)) { + files.add(file); + break; + } else { + break; + } + } + if (listFileTypeSelect[i] == FileType.other) { + files.add(file); + break; + } + } + }, + ); + } + + if (files.isNotEmpty) { + filesModel.fileDetails?.files = files; + tempSentHistory.add(filesModel); + } + } else { + tempSentHistory.add(filesModel); + } + }, + ); } catch (e) { print('error in file model conversion in getSentHistory: $e'); } @@ -425,7 +493,7 @@ class HistoryProvider extends BaseModel { // ExceptionService.instance.showGetExceptionOverlay(e); return AtValue(); }); - if (atValue != null && atValue.value != null) { + if (atValue.value != null) { var downloadAcknowledgement = DownloadAcknowledgement.fromJson(jsonDecode(atValue.value)); @@ -528,6 +596,25 @@ class HistoryProvider extends BaseModel { _initBackendService(); if (index > -1) { receivedHistoryLogs[index] = filesModel; + + // converting received files into single entries before displaying in transfer history + if ((filesModel.files?.length ?? 0) > 0) { + for (int j = 0; j < filesModel.files!.length; j++) { + allFiles.insert( + 0, + FileEntity( + file: filesModel.files![j], + date: filesModel.date != null ? filesModel.date.toString() : '', + atSign: filesModel.sender, + historyType: HistoryType.received, + note: filesModel.notes, + transferId: filesModel.key, + isUploaded: filesModel.files?[j].isUploaded ?? false, + fileTransferObject: fileTransferObject, + ), + ); + } + } } else { // showing notification for new recieved file switch (app_lifecycle_state) { @@ -545,7 +632,29 @@ class HistoryProvider extends BaseModel { await LocalNotificationService() .showNotification(sharedBy, 'Download and view the file(s).'); } - await addToReceiveFileHistory(sharedBy, filesModel); + await addToReceiveFileHistory( + sharedBy, + filesModel, + fileTransferObject: fileTransferObject, + ); + receivedFileHistory.insert( + 0, + FileHistory( + filesModel, + [], + HistoryType.received, + fileTransferObject, + ), + ); + allFilesHistory.insert( + 0, + FileHistory( + filesModel, + [], + HistoryType.received, + fileTransferObject, + ), + ); } setStatus(UPDATE_RECEIVED_RECORD, Status.Done); } @@ -561,8 +670,12 @@ class HistoryProvider extends BaseModel { }); } - addToReceiveFileHistory(String sharedBy, FileTransfer filesModel, - {bool isUpdate = false}) async { + addToReceiveFileHistory( + String sharedBy, + FileTransfer filesModel, { + bool isUpdate = false, + required FileTransferObject fileTransferObject, + }) async { setStatus(ADD_RECEIVED_FILE, Status.Loading); filesModel.sender = sharedBy; @@ -575,6 +688,24 @@ class HistoryProvider extends BaseModel { } else { receivedHistoryLogs.insert(0, filesModel); receivedItemsId[filesModel.key] = true; + + if ((filesModel.files?.length ?? 0) > 0) { + for (int j = 0; j < filesModel.files!.length; j++) { + allFiles.insert( + 0, + FileEntity( + file: filesModel.files![j], + date: filesModel.date != null ? filesModel.date.toString() : '', + atSign: filesModel.sender, + historyType: HistoryType.received, + note: filesModel.notes, + transferId: filesModel.key, + isUploaded: filesModel.files?[j].isUploaded ?? false, + fileTransferObject: fileTransferObject, + ), + ); + } + } } setStatus(ADD_RECEIVED_FILE, Status.Done); @@ -593,9 +724,12 @@ class HistoryProvider extends BaseModel { } } - getAllFileTransferData() async { + Future getAllFileTransferData({ + List? listFileTypeSelect, + }) async { setStatus(GET_ALL_FILE_DATA, Status.Loading); List tempReceivedHistoryLogs = []; + List tempReceivedFiles = []; List fileTransferAtkeys = await AtClientManager.getInstance().atClient.getAtKeys( @@ -613,27 +747,26 @@ class HistoryProvider extends BaseModel { receivedItemsId[atkey.key] = true; }); - if (!isNewKeyAvailable) { - return; - } + // if (!isNewKeyAvailable) { + // return; + // } for (var atKey in fileTransferAtkeys) { var isCurrentAtsign = compareAtSign( atKey.sharedBy!, BackendService.getInstance().currentAtSign!); + if (!isCurrentAtsign && !checkRegexFromBlockedAtsign(atKey.sharedBy!)) { receivedItemsId[atKey.key] = true; AtValue atvalue = - await AtClientManager.getInstance().atClient.get(atKey) - // ignore: return_of_invalid_type_from_catch_error - .catchError((e) { - print("error in getting atValue in getAllFileTransferData : $e"); - //// Removing exception as called in a loop - // ExceptionService.instance.showGetExceptionOverlay(e); - return AtValue(); - }); + await AtClientManager.getInstance().atClient.get(atKey).catchError( + (e) { + print("error in getting atValue in getAllFileTransferData : $e"); + return AtValue(); + }, + ); - if (atvalue != null && atvalue.value != null) { + if (atvalue.value != null) { try { FileTransferObject fileTransferObject = FileTransferObject.fromJson(jsonDecode(atvalue.value))!; @@ -641,8 +774,61 @@ class HistoryProvider extends BaseModel { convertFiletransferObjectToFileTransfer(fileTransferObject); filesModel.sender = atKey.sharedBy!; - if (filesModel.key != null) { - tempReceivedHistoryLogs.insert(0, filesModel); + if ((listFileTypeSelect ?? []).isNotEmpty) { + List files = []; + + if ((filesModel.files ?? []).isNotEmpty) { + await Future.forEach( + filesModel.files!, + (dynamic file) async { + String? fileExtension = file.name.split('.').last; + for (int i = 0; i < listFileTypeSelect!.length; i++) { + if (FileTypes.ALL_TYPES.contains(fileExtension)) { + if (listFileTypeSelect[i] + .suffixName + .contains(fileExtension)) { + files.add(file); + break; + } else { + break; + } + } + if (listFileTypeSelect[i] == FileType.other) { + files.add(file); + break; + } + print(file); + } + }, + ); + } + + if (files.isNotEmpty) { + filesModel.files = files; + tempReceivedHistoryLogs.insert(0, filesModel); + final file = FileHistory( + filesModel, + [], + HistoryType.received, + fileTransferObject, + ); + tempReceivedFiles.insert(0, file); + } + } else { + if (filesModel.key != null) { + tempReceivedHistoryLogs.insert(0, filesModel); + } + + final file = FileHistory( + filesModel, + [], + HistoryType.received, + fileTransferObject, + ); + + if (filesModel.key != null) { + tempReceivedFiles.insert(0, file); + } } } catch (e) { print('error in getAllFileTransferData file model conversion: $e'); @@ -652,9 +838,90 @@ class HistoryProvider extends BaseModel { } receivedHistoryLogs = tempReceivedHistoryLogs; + receivedFileHistory = tempReceivedFiles; setStatus(GET_ALL_FILE_DATA, Status.Done); } + Future getAllFileTransferHistory() async { + setStatus(GET_ALL_FILE_HISTORY, Status.Loading); + List tempFileHistoryLogs = []; + try { + await [ + getSentHistory(), + getAllFileTransferData(), + ]; + + tempFileHistoryLogs.addAll(receivedFileHistory); + tempFileHistoryLogs.addAll(sentHistory); + + tempFileHistoryLogs + .sort((a, b) => b.fileDetails!.date!.compareTo(a.fileDetails!.date!)); + + allFilesHistory = tempFileHistoryLogs; + displayFilesHistory = allFilesHistory; + changeFilterType(typeSelected); + + setStatus(GET_ALL_FILE_HISTORY, Status.Done); + } catch (e) { + setStatus(GET_ALL_FILE_HISTORY, Status.Error); + } + } + + void changeDesc(bool desc) { + isDesc = desc; + if (desc) { + displayFilesHistory + .sort((a, b) => b.fileDetails!.date!.compareTo(a.fileDetails!.date!)); + } else { + displayFilesHistory + .sort((a, b) => a.fileDetails!.date!.compareTo(b.fileDetails!.date!)); + } + notifyListeners(); + } + + void changeFilterType(HistoryType type) { + if (typeSelected != type && type == HistoryType.all) { + listType = FileType.values.toList(); + } + typeSelected = type; + displayFilesHistory = filterFileHistory(type); + notifyListeners(); + } + + List filterFileHistory(HistoryType type) { + switch (type) { + case HistoryType.all: + return allFilesHistory; + case HistoryType.received: + return receivedFileHistory; + case HistoryType.send: + return sentHistory; + default: + return []; + } + } + + Future filterByFileType(List fileType) async { + setStatus(GET_ALL_FILE_HISTORY, Status.Loading); + listType = fileType; + List tempFileHistoryLogs = []; + try { + await getAllFileTransferData(listFileTypeSelect: listType); + await getSentHistory(listFileTypeSelect: listType); + + tempFileHistoryLogs.addAll(receivedFileHistory); + tempFileHistoryLogs.addAll(sentHistory); + + tempFileHistoryLogs + .sort((a, b) => b.fileDetails!.date!.compareTo(a.fileDetails!.date!)); + + displayFilesHistory = tempFileHistoryLogs; + setStatus(GET_ALL_FILE_HISTORY, Status.Done); + } catch (e) { + setStatus(GET_ALL_FILE_HISTORY, Status.Error); + } + } + getrecentHistoryFiles() async { // finding last 15 received files data for recent tab setStatus(RECENT_HISTORY, Status.Loading); @@ -729,12 +996,12 @@ class HistoryProvider extends BaseModel { }); return FileTransfer( - url: fileTransferObject.fileUrl, - files: files, - date: fileTransferObject.date, - key: fileTransferObject.transferId, - notes: fileTransferObject.notes, - ); + url: fileTransferObject.fileUrl, + files: files, + date: fileTransferObject.date, + key: fileTransferObject.transferId, + notes: fileTransferObject.notes, + fileEncryptionKey: fileTransferObject.fileEncryptionKey); } updateFileSendingStatus( @@ -758,14 +1025,23 @@ class HistoryProvider extends BaseModel { isUploaded; } } + + /// updating file entity list, allFiles + index = allFiles.indexWhere((el) => el.transferId == id); + if (index != -1) { + allFiles[index].isUploading = isUploading ?? false; + allFiles[index].isUploaded = isUploaded ?? false; + } + notifyListeners(); } FileHistory convertFileTransferObjectToFileHistory( - FileTransferObject fileTransferObject, - List sharedWithAtsigns, - Map fileShareResult, - {String? groupName}) { + FileTransferObject fileTransferObject, + List sharedWithAtsigns, + Map fileShareResult, { + String? groupName, + }) { List files = []; var sthareStatus = []; @@ -777,11 +1053,11 @@ class HistoryProvider extends BaseModel { }); FileTransfer fileTransfer = FileTransfer( - key: fileTransferObject.transferId, - date: fileTransferObject.date, - files: files, - url: fileTransferObject.fileUrl, - ); + key: fileTransferObject.transferId, + date: fileTransferObject.date, + files: files, + url: fileTransferObject.fileUrl, + fileEncryptionKey: fileTransferObject.fileEncryptionKey); sharedWithAtsigns.forEach((atsign) { sthareStatus @@ -885,6 +1161,9 @@ class HistoryProvider extends BaseModel { .checkForUndownloadedFiles(); if (files is List) { + await Provider.of(NavService.navKey.currentContext!, + listen: false) + .saveNewDataInMyFiles(receivedHistoryLogs[index]); setStatus(DOWNLOAD_FILE, Status.Done); return true; } else { @@ -910,38 +1189,41 @@ class HistoryProvider extends BaseModel { if (downloadPath == null) { throw Exception('downloadPath not found'); } - var atKey = AtKey() - ..key = transferId - ..sharedBy = sharedByAtSign; - var result = - await AtClientManager.getInstance().atClient.get(atKey).catchError((e) { - print('error in _downloadSingleFileFromWeb : $e'); - ExceptionService.instance.showGetExceptionOverlay(e); - return AtValue(); - }); - if (result == null) { - return []; - } FileTransferObject? fileTransferObject; + + var fileEntityIndex = allFilesHistory.indexWhere( + (element) => element.fileTransferObject?.transferId == transferId); + if (fileEntityIndex == -1) { + throw Exception('file object not found.'); + } + + fileTransferObject = allFilesHistory[fileEntityIndex].fileTransferObject; + String formattedFileUrl = fileTransferObject!.fileUrl; + try { - var _jsonData = jsonDecode(result.value); - _jsonData['fileUrl'] = _jsonData['fileUrl'].replaceFirst('/archive', ''); - _jsonData['fileUrl'] = _jsonData['fileUrl'].replaceFirst('/zip', ''); - _jsonData['fileUrl'] = _jsonData['fileUrl'] + '/$fileName'; + formattedFileUrl = formattedFileUrl.replaceFirst('/archive', ''); + formattedFileUrl = formattedFileUrl.replaceFirst('/zip', ''); + formattedFileUrl = formattedFileUrl + '/$fileName'; - fileTransferObject = FileTransferObject.fromJson(_jsonData); - print('fileTransferObject.fileUrl ${fileTransferObject!.fileUrl}'); + print('fileTransferObject.fileUrl ${fileTransferObject.fileUrl}'); } on Exception catch (e) { throw Exception('json decode exception in download file ${e.toString()}'); } + updateFileTransferState( + transferId!, FileTransferProgress(FileState.download, 0, fileName, 0)); + var downloadedFiles = []; var tempDirectory = await Directory(downloadPath).createTemp('encrypted-files'); - var fileDownloadReponse = await FileTransferService.getInstance() - .downloadIndividualFile(fileTransferObject.fileUrl, tempDirectory.path, - fileName, transferId!); + var fileDownloadReponse = + await FileTransferService.getInstance().downloadIndividualFile( + formattedFileUrl, + tempDirectory.path, + fileName, + transferId, + ); if (fileDownloadReponse.isError) { throw Exception('download fail'); @@ -998,13 +1280,15 @@ class HistoryProvider extends BaseModel { ..metadata!.ttl = 518400000 ..sharedWith = fileTransfer.sender; try { - var notificationResult = - await AtClientManager.getInstance().notificationService.notify( - NotificationParams.forUpdate( - atKey, - value: jsonEncode(downloadAcknowledgement.toJson()), - ), - ); + var notificationResult = await AtClientManager.getInstance() + .atClient + .notificationService + .notify( + NotificationParams.forUpdate( + atKey, + value: jsonEncode(downloadAcknowledgement.toJson()), + ), + ); if (notificationResult.notificationStatusEnum == NotificationStatusEnum.delivered) { @@ -1077,6 +1361,30 @@ class HistoryProvider extends BaseModel { sentHistory.removeAt(index); } else { sentHistory[index] = fileHistory; + + FileTransfer? fileTransfer = fileHistory.fileDetails; + if ((fileTransfer?.files?.length ?? 0) > 0) { + for (int j = 0; j < fileTransfer!.files!.length; j++) { + var fileEntity = FileEntity( + file: fileTransfer.files![j], + date: + fileTransfer.date != null ? fileTransfer.date.toString() : '', + atSign: fileHistory.fileTransferObject?.sharedWith, + historyType: HistoryType.send, + note: fileTransfer.notes, + transferId: fileTransfer.key, + isUploaded: fileTransfer.files?[j].isUploaded ?? false, + fileTransferObject: fileHistory.fileTransferObject!, + ); + + var index = allFiles.indexWhere((element) => + element.transferId == + fileHistory.fileTransferObject?.transferId); + if (index != -1) { + allFiles[index] = fileEntity; + } + } + } } } } @@ -1146,10 +1454,11 @@ class HistoryProvider extends BaseModel { if (!isCurrentAtsign && !checkRegexFromBlockedAtsign(atKey.sharedBy!)) { receivedItemsId[atKey.key] = true; - AtValue atvalue = - await AtClientManager.getInstance().atClient.get(atKey) - // ignore: return_of_invalid_type_from_catch_error - .catchError((e) { + AtValue atvalue = await AtClientManager.getInstance() + .atClient + .get(atKey) + // ignore: return_of_invalid_type_from_catch_error + .catchError((e) { print("error in getting atValue in getAllFileTransferData : $e"); //// Removing exception as called in a loop // ExceptionService.instance.showGetExceptionOverlay(e); @@ -1179,14 +1488,14 @@ class HistoryProvider extends BaseModel { setStatus(RECEIVED_HISTORY, Status.Done); } - // save file in gallery function is not in use as of now. - // saveFilesInGallery(List files) async { - // for (var file in files) { - // if (FileTypes.IMAGE_TYPES.contains(file.path.split('.').last) || - // FileTypes.VIDEO_TYPES.contains(file.path.split('.').last)) { - // // saving image,video in gallery. - // await ImageGallerySaver.saveFile(file.path); - // } - // } - // } +// save file in gallery function is not in use as of now. +// saveFilesInGallery(List files) async { +// for (var file in files) { +// if (FileTypes.IMAGE_TYPES.contains(file.path.split('.').last) || +// FileTypes.VIDEO_TYPES.contains(file.path.split('.').last)) { +// // saving image,video in gallery. +// await ImageGallerySaver.saveFile(file.path); +// } +// } +// } } diff --git a/lib/view_models/my_files_provider.dart b/lib/view_models/my_files_provider.dart index 55879bdc..178c8ac7 100644 --- a/lib/view_models/my_files_provider.dart +++ b/lib/view_models/my_files_provider.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:at_client_mobile/at_client_mobile.dart'; -import 'package:at_commons/at_commons.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/file_category_type.dart'; import 'package:atsign_atmosphere_pro/data_models/file_modal.dart'; import 'package:atsign_atmosphere_pro/data_models/file_transfer.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/desktop_my_files/widgets/desktop_apk.dart'; @@ -32,19 +32,27 @@ class MyFilesProvider extends BaseModel { receivedVideos = [], receivedAudio = [], receivedApk = [], + receivedZip = [], receivedDocument = [], recentFile = [], + allFiles = [], + displayFiles = [], receivedUnknown = []; + List tabNames = ['Recents']; String SORT_FILES = 'sort_files'; String POPULATE_TABS = 'populate_tabs'; String SORT_LIST = 'sort_list'; String RECENT_HISTORY = 'recent_history'; + String ALL_FILES = 'all_files'; String MY_FILES = 'my_files'; String fileSearchText = ''; List tabs = [Recents()]; + Map> filesByAlpha = {}; + FileType? typeSelected; + init() async { await getMyFilesRecords(); await sortFiles(); @@ -56,6 +64,7 @@ class MyFilesProvider extends BaseModel { receivedVideos = []; receivedAudio = []; receivedApk = []; + receivedZip = []; receivedDocument = []; recentFile = []; receivedUnknown = []; @@ -63,7 +72,31 @@ class MyFilesProvider extends BaseModel { tabNames = ['Recents']; } - getMyFilesRecords() async { + void changeTypeSelected(FileType? type) { + typeSelected = type; + displayFiles = filterFiles(type); + } + + List filterFiles(FileType? type) { + switch (type) { + case FileType.photo: + return receivedPhotos; + case FileType.video: + return receivedVideos; + case FileType.audio: + return receivedAudio; + case FileType.zips: + return receivedZip; + case FileType.file: + return receivedDocument; + case FileType.other: + return receivedUnknown; + default: + return allFiles; + } + } + + Future getMyFilesRecords() async { var atClient = AtClientManager.getInstance().atClient; setStatus(MY_FILES, Status.Loading); @@ -102,6 +135,7 @@ class MyFilesProvider extends BaseModel { setStatus(SORT_FILES, Status.Loading); receivedAudio = []; receivedApk = []; + receivedZip = []; receivedDocument = []; receivedPhotos = []; receivedVideos = []; @@ -132,6 +166,7 @@ class MyFilesProvider extends BaseModel { date: fileData.date?.toLocal().toString(), type: file.name.split('.').last, contactName: fileData.sender, + message: fileData.notes, fileTransferId: fileData.key); // check if file exists @@ -173,11 +208,11 @@ class MyFilesProvider extends BaseModel { if (index == -1) { receivedDocument.add(fileDetail); } - } else if (FileTypes.APK_TYPES.contains(fileExtension)) { - int index = receivedApk.indexWhere( + } else if (FileTypes.ZIP_TYPES.contains(fileExtension)) { + int index = receivedZip.indexWhere( (element) => element.fileName == fileDetail.fileName); if (index == -1) { - receivedApk.add(fileDetail); + receivedZip.add(fileDetail); } } else { int index = receivedUnknown.indexWhere( @@ -196,6 +231,22 @@ class MyFilesProvider extends BaseModel { } } + void searchFileByKeyword({ + required String key, + FileType? type, + }) { + final result = filterFiles(type) + .where( + (element) => (element.fileName ?? '') + .toLowerCase() + .trim() + .contains(key.toLowerCase().trim()), + ) + .toList(); + displayFiles = result; + notifyListeners(); + } + populateTabs() { bool isDesktop = false; tabNames = ['Recents']; @@ -326,6 +377,7 @@ class MyFilesProvider extends BaseModel { date: fileData.date?.toLocal().toString(), type: file.name!.split('.').last, contactName: fileData.sender, + message: fileData.notes, fileTransferId: fileData.key); // File tempFile = File(fileDetail.filePath!); @@ -344,6 +396,80 @@ class MyFilesProvider extends BaseModel { } } + Future getAllFiles() async { + setStatus(ALL_FILES, Status.Loading); + allFiles = []; + try { + await Future.forEach(myFiles, (FileTransfer fileData) async { + await Future.forEach(fileData.files!, (FileData file) async { + String filePath; + + if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) { + filePath = MixedConstants.RECEIVED_FILE_DIRECTORY + + Platform.pathSeparator + + fileData.sender! + + Platform.pathSeparator + + (file.name ?? ''); + } else { + filePath = BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + (file.name ?? ''); + } + + FilesDetail fileDetail = FilesDetail( + fileName: file.name, + filePath: filePath, + size: double.parse(file.size.toString()), + date: fileData.date?.toLocal().toString(), + type: file.name!.split('.').last, + contactName: fileData.sender, + fileTransferId: fileData.key, + message: fileData.notes, + ); + + allFiles.add(fileDetail); + }); + }); + displayFiles = filterFiles(typeSelected); + // await sortFilesByAlpha(allFiles); + setStatus(ALL_FILES, Status.Done); + } catch (e) { + setStatus(ALL_FILES, Status.Error); + } + } + + Future sortFilesByAlpha(List files) async { + for (int i = 0; i < files.length; i++) { + String fileName = (files[i].fileName ?? '').trim(); + bool isExist = false; + final firstName = fileName.split('').first.toUpperCase(); + + if (filesByAlpha.isNotEmpty) { + for (int i = 0; i < filesByAlpha.length; i++) { + if (firstName.contains(filesByAlpha.keys.toString())) { + isExist = true; + break; + } + } + } + + if (filesByAlpha.isEmpty || !isExist) { + filesByAlpha.addAll( + { + firstName: [files[i]], + }, + ); + } else { + filesByAlpha.update(firstName, (value) { + value.add(files[i]); + return value; + }); + } + } + filesByAlpha.keys.toList().sort(); + print(filesByAlpha); + } + setFileSearchText(String str) { fileSearchText = str; notifyListeners(); @@ -397,6 +523,35 @@ class MyFilesProvider extends BaseModel { ); if (res) { + await Future.forEach(fileTransfer.files!, (FileData file) async { + String filePath; + + if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) { + filePath = MixedConstants.RECEIVED_FILE_DIRECTORY + + Platform.pathSeparator + + fileTransfer.sender! + + Platform.pathSeparator + + (file.name ?? ''); + } else { + filePath = BackendService.getInstance().downloadDirectory!.path + + Platform.pathSeparator + + (file.name ?? ''); + } + + FilesDetail fileDetail = FilesDetail( + fileName: file.name, + filePath: filePath, + size: double.parse(file.size.toString()), + date: fileTransfer.date?.toLocal().toString(), + type: file.name!.split('.').last, + contactName: fileTransfer.sender, + message: fileTransfer.notes, + fileTransferId: fileTransfer.key, + ); + + allFiles.insert(0, fileDetail); + }); + myFiles.insert( 0, FileTransfer.fromJson( diff --git a/lib/view_models/welcome_screen_view_model.dart b/lib/view_models/welcome_screen_view_model.dart index e9536e9d..30abe921 100644 --- a/lib/view_models/welcome_screen_view_model.dart +++ b/lib/view_models/welcome_screen_view_model.dart @@ -4,7 +4,9 @@ import 'package:atsign_atmosphere_pro/view_models/base_model.dart'; class WelcomeScreenProvider extends BaseModel { WelcomeScreenProvider._(); + static WelcomeScreenProvider _instance = WelcomeScreenProvider._(); + factory WelcomeScreenProvider() => _instance; List selectedContacts = []; String updateContacts = 'update_contacts'; @@ -15,12 +17,19 @@ class WelcomeScreenProvider extends BaseModel { bool hasSelectedContactsChanged = false, authenticating = false; bool isSelectionItemChanged = false; String? groupName; + int selectedBottomNavigationIndex = 0; + bool isShowOverlay = true; void resetData() { selectedContacts = []; setStatus(updateContacts, Status.Done); } + void changeBottomNavigationIndex(int index){ + selectedBottomNavigationIndex = index; + notifyListeners(); + } + void _addtoContactsList(GroupContactsModel _obj) { if (selectedContacts.indexWhere( (element) => element.contact!.atSign == _obj.contact!.atSign) == @@ -102,6 +111,11 @@ class WelcomeScreenProvider extends BaseModel { } } + void changeOverlayStatus(bool overlayStatus) { + isShowOverlay = overlayStatus; + notifyListeners(); + } + void resetSelectedContactsStatus() { hasSelectedContactsChanged = false; } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index a8c9797b..ff485532 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) desktop_window_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWindowPlugin"); desktop_window_plugin_register_with_registrar(desktop_window_registrar); + g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin"); + emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar); g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index c01290d4..914f3f71 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST at_file_saver biometric_storage desktop_window + emoji_picker_flutter file_selector_linux local_notifier url_launcher_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c8822bad..751b188d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,10 +12,10 @@ import emoji_picker_flutter import file_selector_macos import flutter_local_notifications import local_notifier -import package_info_plus_macos -import path_provider_macos +import package_info_plus +import path_provider_foundation import share_plus_macos -import shared_preferences_macos +import shared_preferences_foundation import url_launcher_macos import video_compress diff --git a/pubspec.lock b/pubspec.lock index dfda3c2c..e6da8295 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,412 +5,482 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + url: "https://pub.dev" source: hosted version: "47.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + url: "https://pub.dev" source: hosted version: "4.7.0" archive: dependency: "direct main" description: name: archive - url: "https://pub.dartlang.org" + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.3.7" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" asn1lib: dependency: transitive description: name: asn1lib - url: "https://pub.dartlang.org" + sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.4.0" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "2.11.0" at_backupkey_flutter: dependency: "direct main" description: - path: at_backupkey_flutter - ref: fix_flutter_qr_reader - resolved-ref: ce984971ea1ba653b5d23334e339311044eba90f - url: "https://github.com/atsign-foundation/at_widgets.git" - source: git - version: "4.0.5" + name: at_backupkey_flutter + sha256: "63a8fb9ba7b091aa8ff1a300509eb6933aeff02621bfbdabd738f42230f7b306" + url: "https://pub.dev" + source: hosted + version: "4.0.8" at_base2e15: dependency: transitive description: name: at_base2e15 - url: "https://pub.dartlang.org" + sha256: "06ee6ffba9b3439f1c41f9bf0c01f579ce0a8b25f42da8c374ba3a14d721937f" + url: "https://pub.dev" source: hosted version: "1.0.0" - at_client: + at_chops: dependency: transitive + description: + name: at_chops + sha256: "4bd63f0bb9b61ad8087455f5ab303101cbc07b6b87892d4a7dba197ca2b3eb6c" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + at_client: + dependency: "direct overridden" description: name: at_client - url: "https://pub.dartlang.org" + sha256: "08dae4ab49aa971bffd3504dbfdb17e42995e4aa048e1511a953fbd06862f9f5" + url: "https://pub.dev" source: hosted - version: "3.0.38" + version: "3.0.59" at_client_mobile: dependency: "direct main" description: name: at_client_mobile - url: "https://pub.dartlang.org" + sha256: f056674b02bdf13e5442725934e834f7757abcd7062d302ec2e65e46432ca134 + url: "https://pub.dev" source: hosted - version: "3.2.5" + version: "3.2.9" at_common_flutter: dependency: "direct main" description: name: at_common_flutter - url: "https://pub.dartlang.org" + sha256: "75f7b1070d9aa5d42f9878859995f2a24654733a118012b83eb552baaba8ce13" + url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.11" at_commons: dependency: "direct main" description: name: at_commons - url: "https://pub.dartlang.org" + sha256: "2028941bd97196dd5d0b3c6075fd338d3086915d79b616891242bcd4c9673085" + url: "https://pub.dev" source: hosted - version: "3.0.25" + version: "3.0.45" at_contact: dependency: "direct main" description: name: at_contact - url: "https://pub.dartlang.org" + sha256: "232a7dd140bd4c22f4d99c7866633338de57e50f0e1eb84e515dc1ed0f7b0fee" + url: "https://pub.dev" source: hosted version: "3.0.7" at_contacts_flutter: dependency: "direct main" description: - name: at_contacts_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.7" + path: "packages/at_contacts_flutter" + ref: trunk + resolved-ref: "2759ce3c5cc3764d5245a93e0956de553275a5d8" + url: "https://github.com/atsign-foundation/at_widgets" + source: git + version: "4.0.10" at_contacts_group_flutter: dependency: "direct main" description: - path: at_contacts_group_flutter + path: "packages/at_contacts_group_flutter" ref: trunk - resolved-ref: "81f9827f7921386ed2fd5623008a13ac744b25a1" - url: "https://github.com/atsign-foundation/at_widgets.git" + resolved-ref: "8bed2c021e1f8d0884ec86fe7ea5283c34530185" + url: "https://github.com/atsign-foundation/at_widgets" source: git - version: "4.0.9" + version: "4.0.11" at_file_saver: dependency: transitive description: name: at_file_saver - url: "https://pub.dartlang.org" + sha256: d1d5322be265ff395b6160f8880b4ebd713b673ea739662eca463def9c588f00 + url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "0.1.2" at_lookup: dependency: transitive description: name: at_lookup - url: "https://pub.dartlang.org" + sha256: d1ecc7b48c0843289efe90ee3f7948805ddcc7077c984a35fabbc89c3ba7d958 + url: "https://pub.dev" source: hosted - version: "3.0.30" + version: "3.0.36" at_onboarding_flutter: dependency: "direct main" description: - path: at_onboarding_flutter - ref: fix_flutter_qr_reader - resolved-ref: ce984971ea1ba653b5d23334e339311044eba90f - url: "https://github.com/atsign-foundation/at_widgets.git" - source: git - version: "5.0.2" + name: at_onboarding_flutter + sha256: "57f5753460487129a306ffb523f4800146bae885968e9f3f1ec52598867998e9" + url: "https://pub.dev" + source: hosted + version: "5.0.5" at_persistence_secondary_server: dependency: transitive description: name: at_persistence_secondary_server - url: "https://pub.dartlang.org" + sha256: a1b0e9819d6d22072caf15e52ea3bf459c8b161404ed92bb199bfd32f5ff63a9 + url: "https://pub.dev" source: hosted - version: "3.0.35" + version: "3.0.52" at_persistence_spec: dependency: transitive description: name: at_persistence_spec - url: "https://pub.dartlang.org" + sha256: "2ee8f0433783633d2375dba2acf27f8778bcbcd40dda8659bf54f80925db224f" + url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.12" at_server_status: dependency: transitive description: name: at_server_status - url: "https://pub.dartlang.org" + sha256: "01190ba0886dfafb02a7ec247faff405527e7efaa5c21f567e4f45e10699e12d" + url: "https://pub.dev" source: hosted version: "1.0.3" at_sync_ui_flutter: dependency: "direct main" description: name: at_sync_ui_flutter - url: "https://pub.dartlang.org" + sha256: "9297d27ad1219f61a13372d554d94b5610027df7bd566f08e351a7f9ea09aa17" + url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" at_utf7: dependency: transitive description: name: at_utf7 - url: "https://pub.dartlang.org" + sha256: c88e964e307bfe0e53e0048cff1ebf5ab60e23ceb4273f1ca664e724a9a5c5c9 + url: "https://pub.dev" source: hosted version: "1.0.0" at_utils: dependency: transitive description: name: at_utils - url: "https://pub.dartlang.org" + sha256: a244ea7f6411b177ba2f011d36d23ec786b0d41b0e62b58bb0e8bf9ad61cf530 + url: "https://pub.dev" source: hosted - version: "3.0.11" + version: "3.0.12" biometric_storage: dependency: "direct main" description: name: biometric_storage - url: "https://pub.dartlang.org" + sha256: f6d7f5f4c28323797658423e4c5982c9dee42e18f59a8a8d4bc5df38eaf2e2f1 + url: "https://pub.dev" source: hosted version: "4.1.3" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" carousel_slider: dependency: "direct main" description: name: carousel_slider - url: "https://pub.dartlang.org" + sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42" + url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.2.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" - cli_dialog: - dependency: transitive - description: - name: cli_dialog - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" cli_util: dependency: transitive description: name: cli_util - url: "https://pub.dartlang.org" + sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" + url: "https://pub.dev" source: hosted version: "0.3.5" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.1" + console: + dependency: transitive + description: + name: console + sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a + url: "https://pub.dev" + source: hosted + version: "4.1.0" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.1" coverage: dependency: transitive description: name: coverage - url: "https://pub.dartlang.org" + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.6.3" cron: dependency: transitive description: name: cron - url: "https://pub.dartlang.org" + sha256: d98aa8cdad0cccdb6b098e6a1fb89339c180d8a229145fa4cd8c6fc538f0e35f + url: "https://pub.dev" source: hosted - version: "0.3.2" + version: "0.5.1" cross_file: dependency: transitive description: name: cross_file - url: "https://pub.dartlang.org" + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" source: hosted - version: "0.3.3+2" + version: "0.3.3+4" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted version: "3.0.2" crypton: dependency: transitive description: name: crypton - url: "https://pub.dartlang.org" + sha256: "886462e83bf642ba10f5382002654d27da8c2e6e1f42d928f12764cfa204f124" + url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.0" csslib: dependency: transitive description: name: csslib - url: "https://pub.dartlang.org" + sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + url: "https://pub.dev" source: hosted version: "0.17.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted - version: "0.1.3" - dart_console: - dependency: transitive - description: - name: dart_console - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" + version: "1.0.5" dbus: dependency: transitive description: name: dbus - url: "https://pub.dartlang.org" + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.8" desktop_window: dependency: "direct main" description: name: desktop_window - url: "https://pub.dartlang.org" + sha256: "6256fb6feb7b5ec1311c76a3503f89202825bfe92c0458ec5fe7a728ffa216d5" + url: "https://pub.dev" source: hosted version: "0.4.0" + ecdsa: + dependency: transitive + description: + name: ecdsa + sha256: dd1efbaf6c18bfde9347dddcfe10dce3dd044e5a1b237457a49b5c24850dfb95 + url: "https://pub.dev" + source: hosted + version: "0.0.4" + elliptic: + dependency: transitive + description: + name: elliptic + sha256: "8c7396126c81c574fe970ac4afe9ba919b1ca754da20b509664be2345ffb2845" + url: "https://pub.dev" + source: hosted + version: "0.3.8" emoji_picker_flutter: dependency: transitive description: name: emoji_picker_flutter - url: "https://pub.dartlang.org" + sha256: ece466d8da2dfcb773ae38c90a5548b8f0c6882a5cc51840429ac5dbdc43b5cb + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.5.4" encrypt: dependency: transitive description: name: encrypt - url: "https://pub.dartlang.org" + sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + url: "https://pub.dev" source: hosted version: "5.0.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.0.2" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted version: "6.1.4" file_picker: dependency: "direct main" description: name: file_picker - url: "https://pub.dartlang.org" + sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff + url: "https://pub.dev" source: hosted - version: "4.6.1" + version: "5.2.10" file_selector: dependency: "direct main" description: name: file_selector - url: "https://pub.dartlang.org" + sha256: "9e34368bfacdf644e2c8a59e2b241cfb722bcbbd09876410e8775ae4905d6a49" + url: "https://pub.dev" source: hosted version: "0.8.4+3" file_selector_linux: dependency: transitive description: name: file_selector_linux - url: "https://pub.dartlang.org" + sha256: c06249f2082e88aca55f4aad9e4c70ff0f2b61d753c1577d51adeab88b3f0178 + url: "https://pub.dev" source: hosted version: "0.0.3" file_selector_macos: dependency: "direct main" description: name: file_selector_macos - url: "https://pub.dartlang.org" + sha256: e87311d719039da30d26ae829aab3ae66f82deb3318cd70ffecb608c99e3da68 + url: "https://pub.dev" source: hosted version: "0.8.2+2" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - url: "https://pub.dartlang.org" + sha256: bf15ba6978161e4bb28beeb7cc8106d9d7febcfaf9f7daeab11ba5f54fa8148e + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.5.0" file_selector_web: dependency: transitive description: name: file_selector_web - url: "https://pub.dartlang.org" + sha256: bf166d08f4c3f79286774cdfa39ed301e076c5a903c435f5199818288f24a66d + url: "https://pub.dev" source: hosted version: "0.8.1+5" file_selector_windows: dependency: transitive description: name: file_selector_windows - url: "https://pub.dartlang.org" + sha256: "8bbcc82fe0d3cdf5ae5c289492ddfd703ec028028d9f194dbceae04cfbde1c48" + url: "https://pub.dev" source: hosted version: "0.8.2+2" filesystem_picker: dependency: "direct main" description: name: filesystem_picker - url: "https://pub.dartlang.org" + sha256: "41bb01c65cbd8abc389509adfbd08fc4c9090d2d4d15c5fa8813d0efd980f523" + url: "https://pub.dev" source: hosted version: "2.0.1" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec" + url: "https://pub.dev" source: hosted version: "1.0.1" flutter: @@ -422,51 +492,66 @@ packages: dependency: transitive description: name: flutter_image_compress - url: "https://pub.dartlang.org" + sha256: "37f1b26399098e5f97b74c1483f534855e7dff68ead6ddaccf747029fb03f29f" + url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" flutter_keychain: dependency: "direct main" description: name: flutter_keychain - url: "https://pub.dartlang.org" + sha256: "777ea8d3e1f55536bc8489a9ced73a912da4065645d9a1f751aae3548825b140" + url: "https://pub.dev" source: hosted version: "2.2.1" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - url: "https://pub.dartlang.org" + sha256: "57d0012730780fe137260dd180e072c18a73fbeeb924cdc029c18aaa0f338d64" + url: "https://pub.dev" source: hosted version: "9.9.1" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - url: "https://pub.dartlang.org" + sha256: b472bfc173791b59ede323661eae20f7fff0b6908fea33dd720a6ef5d576bae8 + url: "https://pub.dev" source: hosted version: "0.5.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - url: "https://pub.dartlang.org" + sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" + sha256: "96af49aa6b57c10a312106ad6f71deed5a754029c24789bbf620ba784f0bd0b0" + url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.14" flutter_slidable: dependency: "direct main" description: name: flutter_slidable - url: "https://pub.dartlang.org" + sha256: c7607eb808cdef19c8468246e95a133308aeaeb3971cdd9edfb9d5e31cedfbe9 + url: "https://pub.dev" source: hosted version: "0.6.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" + url: "https://pub.dev" + source: hosted + version: "1.1.6" flutter_test: dependency: "direct dev" description: flutter @@ -476,7 +561,8 @@ packages: dependency: "direct main" description: name: flutter_toastr - url: "https://pub.dartlang.org" + sha256: "60e4af64bfba2c43ca4bedf09d2e84ba36ac242f0b13ac26681eb547ccd8489e" + url: "https://pub.dev" source: hosted version: "1.0.3" flutter_web_plugins: @@ -488,581 +574,680 @@ packages: dependency: "direct main" description: name: fluttertoast - url: "https://pub.dartlang.org" + sha256: "7cc92eabe01e3f1babe1571c5560b135dfc762a34e41e9056881e2196b178ec1" + url: "https://pub.dev" source: hosted - version: "8.0.9" + version: "8.1.2" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.2.0" get_it: dependency: transitive description: name: get_it - url: "https://pub.dartlang.org" + sha256: "43133b45f32f1d96bbaeb43ea35a50ce854981baa80f47c3e26ee2ad23bef113" + url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.5.0" glob: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" google_fonts: dependency: "direct main" description: name: google_fonts - url: "https://pub.dartlang.org" + sha256: "2776c66b3e97c6cdd58d1bd3281548b074b64f1fd5c8f82391f7456e38849567" + url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "4.0.5" hive: dependency: transitive description: name: hive - url: "https://pub.dartlang.org" + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" source: hosted version: "2.2.3" html: dependency: transitive description: name: html - url: "https://pub.dartlang.org" + sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" + url: "https://pub.dev" source: hosted - version: "0.15.0" + version: "0.15.3" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3" + url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "1.0.0" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" source: hosted version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" image: dependency: transitive description: name: image - url: "https://pub.dartlang.org" + sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.3.0" image_compression: dependency: transitive description: name: image_compression - url: "https://pub.dartlang.org" + sha256: "911ae4a59196ebcc33c70a38bbc1fa0e2d07243589803dd616120e1a6125bfe9" + url: "https://pub.dev" source: hosted version: "1.0.3" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: a8f2f0aed50c03230ab37e93ca2905c50b6c4097245345956eb24a88f45328cd + url: "https://pub.dev" + source: hosted + version: "0.8.6" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "271e0448e82268b3fa1cb2a48e4a911cbc2135587123d7df8e7ca703c5b10da2" + url: "https://pub.dev" + source: hosted + version: "0.8.6+11" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" + url: "https://pub.dev" + source: hosted + version: "2.1.12" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: a1546ff5861fc15812953d4733b520c3d371cec3d2859a001ff04c46c4d81883 + url: "https://pub.dev" + source: hosted + version: "0.8.7+3" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8" + url: "https://pub.dev" + source: hosted + version: "2.6.3" internet_connection_checker: dependency: transitive description: name: internet_connection_checker - url: "https://pub.dartlang.org" + sha256: "1c683e63e89c9ac66a40748b1b20889fd9804980da732bf2b58d6d5456c8e876" + url: "https://pub.dev" source: hosted - version: "0.0.1+4" + version: "1.0.0+1" intl: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.7" local_notifier: dependency: "direct main" description: name: local_notifier - url: "https://pub.dartlang.org" + sha256: cc855aa6362c8840e3d3b35b1c3b058a3a8becdb2b03d5a9aa3f3a1e861f0a03 + url: "https://pub.dev" source: hosted version: "0.1.5" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.9.1" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" mocktail: dependency: transitive description: name: mocktail - url: "https://pub.dartlang.org" + sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + url: "https://pub.dev" source: hosted version: "0.3.0" msix: dependency: "direct dev" description: name: msix - url: "https://pub.dartlang.org" + sha256: e3de4d9f52543ad6e4b0f534991e1303cbd379d24be28dd241ac60bd9439a201 + url: "https://pub.dev" source: hosted - version: "3.6.3" + version: "3.7.0" mutex: dependency: transitive description: name: mutex - url: "https://pub.dartlang.org" + sha256: "03116a4e46282a671b46c12de649d72c0ed18188ffe12a8d0fc63e83f4ad88f4" + url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" new_version: dependency: "direct main" description: name: new_version - url: "https://pub.dartlang.org" + sha256: "2eabd66e53c882dbaab4f3f8824d139a35a0521e2e04b8fd6d9fb358112691e9" + url: "https://pub.dev" source: hosted version: "0.3.1" + ninja_asn1: + dependency: transitive + description: + name: ninja_asn1 + sha256: b0f04877243fda51c475ec2bcaadb55a92759baee9f02888124c60775760ccf7 + url: "https://pub.dev" + source: hosted + version: "2.0.0" node_preamble: dependency: transitive description: name: node_preamble - url: "https://pub.dartlang.org" + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" open_file: dependency: "direct main" description: name: open_file - url: "https://pub.dartlang.org" + sha256: c85fbdc6ea2f44adcd999a74821a1436e620f9dfc167cda1ad0c9fb243616257 + url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.3.1" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" source: hosted version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - package_info_plus_linux: - dependency: transitive - description: - name: package_info_plus_linux - url: "https://pub.dartlang.org" + sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745" + url: "https://pub.dev" source: hosted - version: "1.0.5" - package_info_plus_macos: - dependency: transitive - description: - name: package_info_plus_macos - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "3.1.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" source: hosted - version: "1.0.2" - package_info_plus_web: + version: "2.0.1" + path: dependency: transitive description: - name: package_info_plus_web - url: "https://pub.dartlang.org" + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" source: hosted - version: "1.0.5" - package_info_plus_windows: + version: "1.8.3" + path_drawing: dependency: transitive description: - name: package_info_plus_windows - url: "https://pub.dartlang.org" + name: path_drawing + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 + url: "https://pub.dev" source: hosted - version: "1.0.5" - path: + version: "1.0.1" + path_parsing: dependency: transitive description: - name: path - url: "https://pub.dartlang.org" + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.0.1" path_provider: dependency: transitive description: name: path_provider - url: "https://pub.dartlang.org" + sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.0.14" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + url: "https://pub.dev" source: hosted - version: "2.0.20" - path_provider_ios: + version: "2.0.27" + path_provider_foundation: dependency: transitive description: - name: path_provider_ios - url: "https://pub.dartlang.org" + name: path_provider_foundation + sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183 + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.2.2" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.10" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.6" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 + url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.1.6" pedantic: dependency: "direct main" description: name: pedantic - url: "https://pub.dartlang.org" + sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" + url: "https://pub.dev" source: hosted version: "1.11.1" permission_handler: dependency: "direct main" description: name: permission_handler - url: "https://pub.dartlang.org" + sha256: "5749ebeb7ec0c3865ea17e3eb337174b87747be816dab582c551e1aff6f6bbf3" + url: "https://pub.dev" source: hosted version: "9.2.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - url: "https://pub.dartlang.org" + sha256: a512e0fa8abcb0659d938ec2df93a70eb1df1fdea5fdc6d79a866bfd858a28fc + url: "https://pub.dev" source: hosted version: "9.0.2+1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - url: "https://pub.dartlang.org" + sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 + url: "https://pub.dev" source: hosted - version: "9.0.4" + version: "9.0.8" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - url: "https://pub.dartlang.org" + sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "3.9.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - url: "https://pub.dartlang.org" + sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + url: "https://pub.dev" source: hosted - version: "0.1.0" + version: "0.1.2" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" pin_code_fields: dependency: transitive description: name: pin_code_fields - url: "https://pub.dartlang.org" + sha256: c8652519d14688f3fe2a8288d86910a46aa0b9046d728f292d3bf6067c31b4c7 + url: "https://pub.dev" source: hosted version: "7.4.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" pointycastle: dependency: transitive description: name: pointycastle - url: "https://pub.dartlang.org" + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.3" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" source: hosted version: "1.5.1" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.0.5" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" qr: dependency: transitive description: name: qr - url: "https://pub.dartlang.org" + sha256: "5c4208b4dc0d55c3184d10d83ee0ded6212dc2b5e2ba17c5a0c0aab279128d21" + url: "https://pub.dev" source: hosted version: "2.1.0" qr_code_scanner: dependency: transitive description: name: qr_code_scanner - url: "https://pub.dartlang.org" + sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd + url: "https://pub.dev" source: hosted version: "1.0.1" qr_flutter: dependency: "direct main" description: name: qr_flutter - url: "https://pub.dartlang.org" + sha256: c5c121c54cb6dd837b9b9d57eb7bc7ec6df4aee741032060c8833a678c80b87e + url: "https://pub.dev" source: hosted version: "4.0.0" receive_sharing_intent: dependency: "direct main" description: name: receive_sharing_intent - url: "https://pub.dartlang.org" + sha256: "912bebb551bce75a14098891fd750305b30d53eba0d61cc70cd9973be9866e8d" + url: "https://pub.dev" source: hosted version: "1.4.5" rxdart: dependency: "direct main" description: name: rxdart - url: "https://pub.dartlang.org" + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" source: hosted - version: "0.27.5" + version: "0.27.7" share_plus: dependency: "direct main" description: name: share_plus - url: "https://pub.dartlang.org" + sha256: f582d5741930f3ad1bf0211d358eddc0508cc346e5b4b248bd1e569c995ebb7a + url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.5.3" share_plus_linux: dependency: transitive description: name: share_plus_linux - url: "https://pub.dartlang.org" + sha256: dc32bf9f1151b9864bb86a997c61a487967a08f2e0b4feaa9a10538712224da4 + url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" share_plus_macos: dependency: transitive description: name: share_plus_macos - url: "https://pub.dartlang.org" + sha256: "44daa946f2845045ecd7abb3569b61cd9a55ae9cc4cbec9895b2067b270697ae" + url: "https://pub.dev" source: hosted version: "3.0.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" + url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.2.1" share_plus_web: dependency: transitive description: name: share_plus_web - url: "https://pub.dartlang.org" + sha256: eaef05fa8548b372253e772837dd1fbe4ce3aca30ea330765c945d7d4f7c9935 + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" share_plus_windows: dependency: transitive description: name: share_plus_windows - url: "https://pub.dartlang.org" + sha256: "3a21515ae7d46988d42130cd53294849e280a5de6ace24bae6912a1bffd757d4" + url: "https://pub.dev" source: hosted version: "3.0.1" shared_preferences: dependency: transitive description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b" + url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.0" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" + url: "https://pub.dev" source: hosted - version: "2.0.13" - shared_preferences_ios: + version: "2.1.4" + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios - url: "https://pub.dartlang.org" + name: shared_preferences_foundation + sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" + url: "https://pub.dev" source: hosted - version: "2.1.1" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" + version: "2.2.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.1.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.1" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler - url: "https://pub.dartlang.org" + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" shelf_static: dependency: transitive description: name: shelf_static - url: "https://pub.dartlang.org" + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" showcaseview: dependency: "direct main" description: name: showcaseview - url: "https://pub.dartlang.org" + sha256: "09b534d806572135c38e06901de4b36b2bbd61739ec56c5fa9242d10748e19df" + url: "https://pub.dev" source: hosted version: "1.1.8" sky_engine: @@ -1074,261 +1259,306 @@ packages: dependency: transitive description: name: source_map_stack_trace - url: "https://pub.dartlang.org" + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" source_maps: dependency: transitive description: name: source_maps - url: "https://pub.dartlang.org" + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" source: hosted - version: "0.10.10" + version: "0.10.12" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: transitive description: name: test - url: "https://pub.dartlang.org" + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + url: "https://pub.dev" source: hosted - version: "1.21.1" + version: "1.24.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.5.1" test_core: dependency: transitive description: name: test_core - url: "https://pub.dartlang.org" + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + url: "https://pub.dev" source: hosted - version: "0.4.13" + version: "0.5.1" timezone: dependency: transitive description: name: timezone - url: "https://pub.dartlang.org" + sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e" + url: "https://pub.dev" source: hosted version: "0.8.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted version: "1.3.1" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.1.10" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.dartlang.org" + sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd" + url: "https://pub.dev" source: hosted - version: "6.0.19" + version: "6.0.31" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.dartlang.org" + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" source: hosted - version: "6.0.17" + version: "6.1.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.5" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.5" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.0.16" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.6" uuid: dependency: transitive description: name: uuid - url: "https://pub.dartlang.org" + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" + version: + dependency: transitive + description: + name: version + sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" + url: "https://pub.dev" + source: hosted + version: "3.0.2" video_compress: dependency: "direct main" description: name: video_compress - url: "https://pub.dartlang.org" + sha256: "407693726e674a1e1958801deb2d9daf5a5297707ba6d03375007012dae7389a" + url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" video_thumbnail: dependency: "direct main" description: name: video_thumbnail - url: "https://pub.dartlang.org" + sha256: "005bf7898bf6cddbf5f079e2618f3d3cb83dd2712714f0042b5a018c2b32e7d6" + url: "https://pub.dev" source: hosted version: "0.4.6" vm_service: dependency: transitive description: name: vm_service - url: "https://pub.dartlang.org" + sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "9.4.0" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - url: "https://pub.dartlang.org" + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" source: hosted version: "1.2.0" webview_flutter: dependency: "direct main" description: name: webview_flutter - url: "https://pub.dartlang.org" + sha256: "392c1d83b70fe2495de3ea2c84531268d5b8de2de3f01086a53334d8b6030a88" + url: "https://pub.dev" source: hosted version: "3.0.4" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - url: "https://pub.dartlang.org" + sha256: "8b3b2450e98876c70bfcead876d9390573b34b9418c19e28168b74f6cb252dbd" + url: "https://pub.dev" source: hosted - version: "2.10.1" + version: "2.10.4" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - url: "https://pub.dartlang.org" + sha256: "812165e4e34ca677bdfbfa58c01e33b27fd03ab5fa75b70832d4b7d4ca1fa8cf" + url: "https://pub.dev" source: hosted - version: "1.9.3" + version: "1.9.5" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - url: "https://pub.dartlang.org" + sha256: a5364369c758892aa487cbf59ea41d9edd10f9d9baf06a94e80f1bd1b4c7bbc0 + url: "https://pub.dev" source: hosted - version: "2.9.4" + version: "2.9.5" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "3.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + url: "https://pub.dev" source: hosted - version: "0.2.0+2" + version: "0.2.0+3" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: ac0e3f4bf00ba2708c33fbabbbe766300e509f8c82dbd4ab6525039813f7e2fb + url: "https://pub.dev" source: hosted version: "6.1.0" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" source: hosted version: "3.1.1" zxing2: dependency: transitive description: name: zxing2 - url: "https://pub.dartlang.org" + sha256: "1913c33844c68b62573741134ef5f987f1e15e331c95ac7dc327afbb9896e9ec" + url: "https://pub.dev" source: hosted - version: "0.1.0" + version: "0.1.1" sdks: - dart: ">=2.17.0 <3.0.0" - flutter: ">=3.0.0" + dart: ">=3.0.0-0 <4.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 44342542..225778a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # Mobile version -version: 1.0.8+48 +version: 1.0.10+55 # Desktop version macos # version: 1.0.1+19 @@ -26,20 +26,20 @@ version: 1.0.8+48 # version: 1.0.1+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.12.0 <4.0.0" dependencies: flutter: sdk: flutter archive: ^3.2.2 - cupertino_icons: ^0.1.3 + cupertino_icons: ^1.0.5 file_picker: ^4.5.1 filesystem_picker: ^2.0.0-nullsafety.0 desktop_window: ^0.4.0 flutter_keychain: ^2.1.0 flutter_local_notifications: ^9.4.0 flutter_slidable: ^0.6.0 - google_fonts: ^2.3.1 + google_fonts: ^4.0.4 intl: ^0.17.0 open_file: ^3.2.1 pedantic: ^1.11.1 @@ -54,7 +54,7 @@ dependencies: webview_flutter: ^3.0.4 http: ^0.13.4 package_info_plus: ^1.4.2 - fluttertoast: ^8.0.9 + fluttertoast: 8.1.2 file_selector: ^0.8.3 file_selector_macos: ^0.8.2 carousel_slider: ^4.0.0 @@ -65,7 +65,7 @@ dependencies: at_commons: ^3.0.25 at_contact: ^3.0.7 at_common_flutter: ^2.0.10 - + at_server_status: ^1.0.3 at_client_mobile: ^3.2.5 at_onboarding_flutter: ^5.0.2 at_backupkey_flutter: ^4.0.5 @@ -75,28 +75,28 @@ dependencies: at_sync_ui_flutter: ^1.0.6 showcaseview: ^1.1.5 biometric_storage: ^4.1.3 - + flutter_svg: ^1.1.5 + image_picker: 0.8.6 dev_dependencies: flutter_test: sdk: flutter msix: ^3.3.1 -dependency_overrides: + +dependency_overrides: + package_info_plus: ^3.0.0 + at_client: ^3.0.56 biometric_storage: ^4.1.3 + file_picker: ^5.0.0 at_contacts_group_flutter: git: - url: https://github.com/atsign-foundation/at_widgets.git - path: at_contacts_group_flutter + url: https://github.com/atsign-foundation/at_widgets + path: packages/at_contacts_group_flutter ref: trunk - at_backupkey_flutter: + at_contacts_flutter: git: - url: https://github.com/atsign-foundation/at_widgets.git - path: at_backupkey_flutter - ref: fix_flutter_qr_reader - at_onboarding_flutter: - git: - url: https://github.com/atsign-foundation/at_widgets.git - path: at_onboarding_flutter - ref: fix_flutter_qr_reader + url: https://github.com/atsign-foundation/at_widgets + path: packages/at_contacts_flutter + ref: trunk # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -110,6 +110,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/images/ + - assets/svg/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. @@ -138,6 +139,34 @@ flutter: fonts: - asset: assets/fonts/Playfair_Display/PlayfairDisplay-Bold.ttf + - family: Inter + fonts: + - asset: assets/fonts/Inter/Inter-Light.ttf + weight: 300 + - asset: assets/fonts/Inter/Inter-Regular.ttf + weight: 400 + - asset: assets/fonts/Inter/Inter-Medium.ttf + weight: 500 + - asset: assets/fonts/Inter/Inter-SemiBold.ttf + weight: 600 + - asset: assets/fonts/Inter/Inter-Bold.ttf + weight: 700 + + - family: Poppins + fonts: + - asset: assets/fonts/Poppins/Poppins-Light.ttf + weight: 300 + - asset: assets/fonts/Poppins/Poppins-Medium.ttf + weight: 500 + - asset: assets/fonts/Poppins/Poppins-Regular.ttf + weight: 400 + - asset: assets/fonts/Poppins/Poppins-Bold.ttf + weight: 700 + - asset: assets/fonts/Poppins/Poppins-Thin.ttf + weight: 100 + - asset: assets/fonts/Poppins/Poppins-SemiBold.ttf + weight: 600 + # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8ef889c0..304229a5 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSaverPlugin")); DesktopWindowPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DesktopWindowPlugin")); + EmojiPickerFlutterPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); LocalNotifierPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 6a9445a3..fc681e5d 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST at_file_saver desktop_window + emoji_picker_flutter file_selector_windows local_notifier permission_handler_windows