From aaf6a12412ce7ac860d2673722533d0434791eb2 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA <31937920+Te-Z@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:54:52 +0200 Subject: [PATCH 01/40] TW-1955: Create permission dialogs for contacts and images (#1956) * TW-1955: Implement permission dialog for contact and media TW-1955: new chat and group chat updated with contact permission dialog TW-1955: permission dialog for media added TW-1955: add autoDisplayPermissionDialog notifier TW-1955: remove permission dialog before displaying native one TW-1955: remove data refresh when back to screen * TW-1955: Implement permission dialog for contact and media - part II * TW-1955: Implement permission dialog for contact and media - part III --------- Co-authored-by: HuyNguyen --- assets/l10n/intl_en.arb | 4 + assets/l10n/intl_fr.arb | 4 + .../contact_manager/contacts_manager.dart | 12 ++ lib/pages/chat_details/chat_details_edit.dart | 2 +- lib/pages/contacts_tab/contacts_tab.dart | 16 ++- .../contacts_tab/contacts_tab_body_view.dart | 4 +- lib/pages/new_group/contacts_selection.dart | 16 ++- .../new_group/contacts_selection_view.dart | 4 +- lib/pages/new_group/new_group_chat_info.dart | 2 +- .../new_private_chat/new_private_chat.dart | 10 ++ .../new_private_chat_style.dart | 7 + .../new_private_chat_view.dart | 44 +++++-- .../widget/expansion_list.dart | 11 -- .../settings_profile/settings_profile.dart | 2 +- .../mixins/common_media_picker_mixin.dart | 6 +- .../contacts_view_controller_mixin.dart | 103 ++++++++++++++- .../mixins/media_picker_mixin.dart | 2 +- lib/utils/permission_dialog.dart | 5 + lib/utils/permission_service.dart | 121 ++++++++++++++---- .../contacts_view_controller_mixin_test.dart | 39 ++++++ 20 files changed, 348 insertions(+), 66 deletions(-) create mode 100644 lib/pages/new_private_chat/new_private_chat_style.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index f718dcb1c1..6a84810e3b 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3041,6 +3041,10 @@ } } }, + "explainPermissionToAccessContacts": "Twake chat needs access to your contacts to find out whether your friends are on the matrix server. This is done locally, and your contacts are not synchronized with our server.", + "explainPermissionToAccessMedias": "Twake chat needs access to storage so you can send and save photos, videos, music and other documents. Press Settings > Authorizations, then activate Storage authorization: Photos and videos.", + "explainPermissionToAccessPhotos": "Twake chat needs access to your photos so you can send and save images. Press Settings > Permissions, then enable Storage permission: Photos.", + "explainPermissionToAccessVideos": "Twake chat needs access to your videos so you can send and save videos. Press Settings > Permissions, then enable Storage permission: Videos.", "downloading": "Downloading", "settingUpYourTwake": "Setting up your Twake\nIt could take a while", "performingAutomaticalLogin": "Performing automatical login via SSO", diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index ab65dd7510..8278672104 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -2714,6 +2714,10 @@ "count": {} } }, + "explainPermissionToAccessContacts": "Twake chat doit accéder à vos contacts pour savoir si vos amis sont sur le serveur matrix. Cela se fait localement et vos contacts ne sont pas synchronisés avec notre serveur.", + "explainPermissionToAccessMedias": "Twake chat a besoin d'accéder au stockage pour que vous puissiez envoyer et enregistrer des photos, vidéos, musiques et autres documents. Appuyez sur Paramètres > Autorisations puis activez l'autorisation de stockage: Photos et vidéos.", + "explainPermissionToAccessPhotos": "Twake chat a besoin d'accéder à vos photos pour que vous puissiez envoyer et de enregistrer des images. Allez dans Paramètres > Autorisations, puis activez l'autorisation de stockage : Photos.", + "explainPermissionToAccessVideos": "Twake chat a besoin d'accéder à vos vidéos pour que vous puissiez envoyer et de sauvegarder des vidéos. Allez dans Paramètres > Autorisations, puis activez l'autorisation de stockage : Vidéos.", "recentChat": "DISCUSSION RÉCENTE", "@recentChat": {}, "muteThisMessage": "Couper le son de ce salon", diff --git a/lib/domain/contact_manager/contacts_manager.dart b/lib/domain/contact_manager/contacts_manager.dart index 81a86b2fdd..1cac10b553 100644 --- a/lib/domain/contact_manager/contacts_manager.dart +++ b/lib/domain/contact_manager/contacts_manager.dart @@ -17,6 +17,8 @@ class ContactsManager { bool _doNotShowWarningContactsBannerAgain = false; + bool _doNotShowWarningContactsDialogAgain = false; + final ValueNotifierCustom> _contactsNotifier = ValueNotifierCustom(const Right(ContactsInitial())); @@ -41,10 +43,17 @@ class ContactsManager { bool get isDoNotShowWarningContactsBannerAgain => _doNotShowWarningContactsBannerAgain; + bool get isDoNotShowWarningContactsDialogAgain => + _doNotShowWarningContactsDialogAgain; + set updateNotShowWarningContactsBannerAgain(bool value) { _doNotShowWarningContactsBannerAgain = value; } + set updateNotShowWarningContactsDialogAgain(bool value) { + _doNotShowWarningContactsDialogAgain = value; + } + Future reSyncContacts() async { _contactsNotifier.value = const Right(ContactsInitial()); _phonebookContactsNotifier.value = @@ -92,4 +101,7 @@ class ContactsManager { }, ); } + + void refreshPhonebookContacts() => + _fetchPhonebookContacts(isAvailableSupportPhonebookContacts: true); } diff --git a/lib/pages/chat_details/chat_details_edit.dart b/lib/pages/chat_details/chat_details_edit.dart index ef44388ddb..bb5534c47b 100644 --- a/lib/pages/chat_details/chat_details_edit.dart +++ b/lib/pages/chat_details/chat_details_edit.dart @@ -136,7 +136,7 @@ class ChatDetailsEditController extends State _getImageOnWeb(context); return; } - final currentPermissionPhotos = await getCurrentMediaPermission(); + final currentPermissionPhotos = await getCurrentMediaPermission(context); if (currentPermissionPhotos != null) { final imagePickerController = createImagePickerController(); showImagePickerBottomSheet( diff --git a/lib/pages/contacts_tab/contacts_tab.dart b/lib/pages/contacts_tab/contacts_tab.dart index 7e02a631dd..cf5e4e429b 100644 --- a/lib/pages/contacts_tab/contacts_tab.dart +++ b/lib/pages/contacts_tab/contacts_tab.dart @@ -7,11 +7,12 @@ import 'package:fluffychat/presentation/model/contact/presentation_contact_const import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/utils/string_extension.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; class ContactsTab extends StatefulWidget { @@ -27,7 +28,10 @@ class ContactsTab extends StatefulWidget { } class ContactsTabController extends State - with ComparablePresentationContactMixin, ContactsViewControllerMixin { + with + ComparablePresentationContactMixin, + ContactsViewControllerMixin, + WidgetsBindingObserver { final responsive = getIt.get(); Client get client => Matrix.of(context).client; @@ -35,8 +39,10 @@ class ContactsTabController extends State @override void initState() { SchedulerBinding.instance.addPostFrameCallback((_) async { + WidgetsBinding.instance.addObserver(this); if (mounted) { initialFetchContacts( + context: context, client: Matrix.of(context).client, matrixLocalizations: MatrixLocals(L10n.of(context)!), ); @@ -100,9 +106,15 @@ class ContactsTabController extends State } } + @override + void didChangeAppLifecycleState(AppLifecycleState state) async { + await handleDidChangeAppLifecycleState(state); + } + @override void dispose() { disposeContactsMixin(); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } diff --git a/lib/pages/contacts_tab/contacts_tab_body_view.dart b/lib/pages/contacts_tab/contacts_tab_body_view.dart index 5ea6f239b4..1483528858 100644 --- a/lib/pages/contacts_tab/contacts_tab_body_view.dart +++ b/lib/pages/contacts_tab/contacts_tab_body_view.dart @@ -418,8 +418,8 @@ class _SliverWarningBanner extends StatelessWidget { child: ContactsWarningBannerView( warningBannerNotifier: controller.warningBannerNotifier, closeContactsWarningBanner: controller.closeContactsWarningBanner, - goToSettingsForPermissionActions: - controller.goToSettingsForPermissionActions, + goToSettingsForPermissionActions: () => + controller.displayContactPermissionDialog(context), ), ); } diff --git a/lib/pages/new_group/contacts_selection.dart b/lib/pages/new_group/contacts_selection.dart index b8cd478738..aeb9a6c8e0 100644 --- a/lib/pages/new_group/contacts_selection.dart +++ b/lib/pages/new_group/contacts_selection.dart @@ -6,14 +6,17 @@ import 'package:fluffychat/pages/new_group/selected_contacts_map_change_notifier import 'package:fluffychat/presentation/model/contact/presentation_contact.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter/cupertino.dart'; import 'package:matrix/matrix.dart'; abstract class ContactsSelectionController extends State - with InviteExternalContactMixin, ContactsViewControllerMixin { + with + InviteExternalContactMixin, + ContactsViewControllerMixin, + WidgetsBindingObserver { final selectedContactsMapNotifier = SelectedContactsMapChangeNotifier(); String getTitle(BuildContext context); @@ -37,8 +40,10 @@ abstract class ContactsSelectionController @override void initState() { SchedulerBinding.instance.addPostFrameCallback((_) async { + WidgetsBinding.instance.addObserver(this); if (mounted) { initialFetchContacts( + context: context, client: client, matrixLocalizations: MatrixLocals(L10n.of(context)!), ); @@ -47,9 +52,16 @@ abstract class ContactsSelectionController super.initState(); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) async { + await handleDidChangeAppLifecycleState(state); + } + @override void dispose() { + WidgetsBinding.instance.removeObserver(this); disposeContactsMixin(); + selectedContactsMapNotifier.dispose(); super.dispose(); } diff --git a/lib/pages/new_group/contacts_selection_view.dart b/lib/pages/new_group/contacts_selection_view.dart index 7c9ac986f4..81bc505041 100644 --- a/lib/pages/new_group/contacts_selection_view.dart +++ b/lib/pages/new_group/contacts_selection_view.dart @@ -54,8 +54,8 @@ class ContactsSelectionView extends StatelessWidget { warningBannerNotifier: controller.warningBannerNotifier, closeContactsWarningBanner: controller.closeContactsWarningBanner, - goToSettingsForPermissionActions: - controller.goToSettingsForPermissionActions, + goToSettingsForPermissionActions: () => + controller.displayContactPermissionDialog(context), ), ), SliverToBoxAdapter( diff --git a/lib/pages/new_group/new_group_chat_info.dart b/lib/pages/new_group/new_group_chat_info.dart index ccdfd22f69..fdb43d036b 100644 --- a/lib/pages/new_group/new_group_chat_info.dart +++ b/lib/pages/new_group/new_group_chat_info.dart @@ -284,7 +284,7 @@ class NewGroupChatInfoController extends State _getImageOnWeb(context); return; } - final currentPermissionPhotos = await getCurrentMediaPermission(); + final currentPermissionPhotos = await getCurrentMediaPermission(context); if (currentPermissionPhotos != null) { final imagePickerController = createImagePickerController(); groupNameFocusNode.unfocus(); diff --git a/lib/pages/new_private_chat/new_private_chat.dart b/lib/pages/new_private_chat/new_private_chat.dart index 05140cd85b..f8484b3e75 100644 --- a/lib/pages/new_private_chat/new_private_chat.dart +++ b/lib/pages/new_private_chat/new_private_chat.dart @@ -25,6 +25,7 @@ class NewPrivateChatController extends State ComparablePresentationContactMixin, ContactsViewControllerMixin, GoToDraftChatMixin, + WidgetsBindingObserver, InviteExternalContactMixin, GoToGroupChatMixin { final isShowContactsNotifier = ValueNotifier(true); @@ -34,8 +35,10 @@ class NewPrivateChatController extends State void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback((_) async { + WidgetsBinding.instance.addObserver(this); if (mounted) { initialFetchContacts( + context: context, client: Matrix.of(context).client, matrixLocalizations: MatrixLocals(L10n.of(context)!), ); @@ -84,9 +87,16 @@ class NewPrivateChatController extends State }); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) async { + await handleDidChangeAppLifecycleState(state); + } + @override void dispose() { super.dispose(); + WidgetsBinding.instance.removeObserver(this); + isShowContactsNotifier.dispose(); disposeContactsMixin(); scrollController.dispose(); } diff --git a/lib/pages/new_private_chat/new_private_chat_style.dart b/lib/pages/new_private_chat/new_private_chat_style.dart new file mode 100644 index 0000000000..ff06436bc8 --- /dev/null +++ b/lib/pages/new_private_chat/new_private_chat_style.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class NewPrivateChatStyle { + static const EdgeInsets paddingBody = EdgeInsets.only(left: 8.0, right: 10.0); + + static const EdgeInsets paddingWarningBanner = EdgeInsets.only(top: 16.0); +} diff --git a/lib/pages/new_private_chat/new_private_chat_view.dart b/lib/pages/new_private_chat/new_private_chat_view.dart index 652d19b696..996a8a51e1 100644 --- a/lib/pages/new_private_chat/new_private_chat_view.dart +++ b/lib/pages/new_private_chat/new_private_chat_view.dart @@ -1,8 +1,10 @@ import 'package:fluffychat/pages/new_private_chat/new_private_chat.dart'; +import 'package:fluffychat/pages/new_private_chat/new_private_chat_style.dart'; import 'package:fluffychat/pages/new_private_chat/widget/expansion_list.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_bars/searchable_app_bar.dart'; import 'package:fluffychat/widgets/app_bars/searchable_app_bar_style.dart'; +import 'package:fluffychat/widgets/contacts_warning_banner/contacts_warning_banner_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; @@ -31,20 +33,36 @@ class NewPrivateChatView extends StatelessWidget { keyboardDismissBehavior: PlatformInfos.isMobile ? ScrollViewKeyboardDismissBehavior.manual : ScrollViewKeyboardDismissBehavior.onDrag, - padding: const EdgeInsets.only(left: 8.0, right: 10.0), + padding: NewPrivateChatStyle.paddingBody, controller: controller.scrollController, - child: ExpansionList( - presentationContactsNotifier: controller.presentationContactNotifier, - goToNewGroupChat: () => controller.goToNewGroupChat(context), - isShowContactsNotifier: controller.isShowContactsNotifier, - onContactTap: controller.onContactAction, - onExternalContactTap: controller.onExternalContactAction, - toggleContactsList: controller.toggleContactsList, - textEditingController: controller.textEditingController, - warningBannerNotifier: controller.warningBannerNotifier, - closeContactsWarningBanner: controller.closeContactsWarningBanner, - goToSettingsForPermissionActions: - controller.goToSettingsForPermissionActions, + child: Column( + children: [ + Padding( + padding: NewPrivateChatStyle.paddingWarningBanner, + child: ContactsWarningBannerView( + warningBannerNotifier: controller.warningBannerNotifier, + closeContactsWarningBanner: + controller.closeContactsWarningBanner, + goToSettingsForPermissionActions: () => + controller.displayContactPermissionDialog(context), + isShowMargin: false, + ), + ), + ExpansionList( + presentationContactsNotifier: + controller.presentationContactNotifier, + goToNewGroupChat: () => controller.goToNewGroupChat(context), + isShowContactsNotifier: controller.isShowContactsNotifier, + onContactTap: controller.onContactAction, + onExternalContactTap: controller.onExternalContactAction, + toggleContactsList: controller.toggleContactsList, + textEditingController: controller.textEditingController, + warningBannerNotifier: controller.warningBannerNotifier, + closeContactsWarningBanner: controller.closeContactsWarningBanner, + goToSettingsForPermissionActions: () => + controller.displayContactPermissionDialog(context), + ), + ], ), ), ); diff --git a/lib/pages/new_private_chat/widget/expansion_list.dart b/lib/pages/new_private_chat/widget/expansion_list.dart index c4ef272367..4120b62d8c 100644 --- a/lib/pages/new_private_chat/widget/expansion_list.dart +++ b/lib/pages/new_private_chat/widget/expansion_list.dart @@ -10,7 +10,6 @@ import 'package:fluffychat/presentation/model/contact/get_presentation_contacts_ import 'package:fluffychat/presentation/model/contact/presentation_contact.dart'; import 'package:fluffychat/presentation/model/contact/presentation_contact_success.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; -import 'package:fluffychat/widgets/contacts_warning_banner/contacts_warning_banner_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; @@ -164,7 +163,6 @@ class ExpansionList extends StatelessWidget { const SizedBox( height: 12, ), - _contactsWarningBannerViewBuilder(), ..._buildResponsiveButtons(context), for (final child in expansionList) ...[child], ] else ...[ @@ -188,15 +186,6 @@ class ExpansionList extends StatelessWidget { ); } - Widget _contactsWarningBannerViewBuilder() { - return ContactsWarningBannerView( - warningBannerNotifier: warningBannerNotifier, - isShowMargin: false, - closeContactsWarningBanner: closeContactsWarningBanner, - goToSettingsForPermissionActions: goToSettingsForPermissionActions, - ); - } - Widget _buildTitle(BuildContext context, int countContacts) { return Padding( padding: const EdgeInsets.only(left: 8.0), diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart index 63bdb72b24..8fb0d4bc22 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart @@ -208,7 +208,7 @@ class SettingsProfileController extends State _getImageOnWeb(context); return; } - final currentPermissionPhotos = await getCurrentMediaPermission(); + final currentPermissionPhotos = await getCurrentMediaPermission(context); if (currentPermissionPhotos != null) { final imagePickerController = createImagePickerController(); showImagePickerBottomSheet( diff --git a/lib/presentation/mixins/common_media_picker_mixin.dart b/lib/presentation/mixins/common_media_picker_mixin.dart index e856d93ec3..19f43c81d9 100644 --- a/lib/presentation/mixins/common_media_picker_mixin.dart +++ b/lib/presentation/mixins/common_media_picker_mixin.dart @@ -14,8 +14,8 @@ mixin CommonMediaPickerMixin { final PermissionHandlerService _permissionHandlerService = PermissionHandlerService(); - Future? getCurrentMediaPermission() { - return _permissionHandlerService.requestPermissionForMediaActions(); + Future? getCurrentMediaPermission(BuildContext context) { + return _permissionHandlerService.requestPermissionForMediaActions(context); } Future? getCurrentCameraPermission() { @@ -23,7 +23,7 @@ mixin CommonMediaPickerMixin { } Future? getCurrentMicroPermission() { - return _permissionHandlerService.requestPermissionForMircoActions(); + return _permissionHandlerService.requestPermissionForMicroActions(); } void goToSettings( diff --git a/lib/presentation/mixins/contacts_view_controller_mixin.dart b/lib/presentation/mixins/contacts_view_controller_mixin.dart index 983192ba5f..a7eb750de5 100644 --- a/lib/presentation/mixins/contacts_view_controller_mixin.dart +++ b/lib/presentation/mixins/contacts_view_controller_mixin.dart @@ -20,9 +20,11 @@ import 'package:fluffychat/presentation/model/contact/presentation_contact.dart' import 'package:fluffychat/presentation/model/contact/presentation_contact_success.dart'; import 'package:fluffychat/presentation/model/search/presentation_search.dart'; import 'package:fluffychat/presentation/model/search/presentation_search_state_extension.dart'; +import 'package:fluffychat/utils/permission_dialog.dart'; import 'package:fluffychat/utils/permission_service.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -67,15 +69,106 @@ mixin class ContactsViewControllerMixin { final contactsManager = getIt.get(); - PermissionStatus contactsPermissionStatus = PermissionStatus.granted; + PermissionStatus? contactsPermissionStatus; + + Future displayContactPermissionDialog(BuildContext context) async { + final fetchContactsPermissionStatus = + await _permissionHandlerService.contactsPermissionStatus; + + contactsPermissionStatus = fetchContactsPermissionStatus; + + if (PlatformInfos.isMobile && !fetchContactsPermissionStatus.isGranted) { + await showDialog( + useRootNavigator: false, + context: context, + builder: (dialogContext) { + return PermissionDialog( + icon: const Icon(Icons.contact_page_outlined), + permission: Permission.contacts, + explainTextRequestPermission: Text( + L10n.of(context)!.explainPermissionToAccessContacts, + ), + onRefuseTap: _handleDenyPermissionDialog, + onAcceptButton: () async { + Navigator.of(dialogContext).pop(); + await _handleRequestContactsPermission(); + }, + ); + }, + ); + } + } + + void _handleDenyPermissionDialog() { + warningBannerNotifier.value = WarningContactsBannerState.display; + contactsManager.updateNotShowWarningContactsDialogAgain = true; + } + + Future _initWarningBanner() async { + final currentContactPermission = + await _permissionHandlerService.contactsPermissionStatus; + Logs().i( + 'ContactsViewControllerMixin::_initWarningBanner: Contact Permission $currentContactPermission', + ); + + if (currentContactPermission.isGranted) { + contactsPermissionStatus = currentContactPermission; + warningBannerNotifier.value = WarningContactsBannerState.hide; + return; + } + + if (!contactsManager.isDoNotShowWarningContactsBannerAgain && + contactsManager.isDoNotShowWarningContactsDialogAgain) { + warningBannerNotifier.value = WarningContactsBannerState.display; + return; + } + } + + Future handleDidChangeAppLifecycleState(AppLifecycleState state) async { + if (!PlatformInfos.isMobile) { + return; + } + Logs().i( + 'ContactsViewControllerMixin::handleDidChangeAppLifecycleState: $state', + ); + + if (state == AppLifecycleState.resumed) { + final currentContactPermission = + await _permissionHandlerService.contactsPermissionStatus; + + Logs().i( + 'ContactsViewControllerMixin::handleDidChangeAppLifecycleState: Contact Permission $currentContactPermission', + ); + + if (currentContactPermission != contactsPermissionStatus && + currentContactPermission.isDenied) { + if (!contactsManager.isDoNotShowWarningContactsBannerAgain) { + warningBannerNotifier.value = WarningContactsBannerState.display; + } + contactsPermissionStatus = currentContactPermission; + return; + } + + if (currentContactPermission != contactsPermissionStatus && + currentContactPermission.isGranted) { + contactsPermissionStatus = currentContactPermission; + warningBannerNotifier.value = WarningContactsBannerState.hide; + contactsManager.refreshPhonebookContacts(); + return; + } + } + } void initialFetchContacts({ + required BuildContext context, required Client client, required MatrixLocalizations matrixLocalizations, }) async { if (PlatformInfos.isMobile && - !contactsManager.isDoNotShowWarningContactsBannerAgain) { - await _handleRequestContactsPermission(); + !contactsManager.isDoNotShowWarningContactsDialogAgain) { + await displayContactPermissionDialog(context); + } else { + await _initWarningBanner(); } _refreshAllContacts( client: client, @@ -97,6 +190,7 @@ mixin class ContactsViewControllerMixin { }); contactsManager.initialSynchronizeContacts( isAvailableSupportPhonebookContacts: PlatformInfos.isMobile && + contactsPermissionStatus != null && contactsPermissionStatus == PermissionStatus.granted, ); } @@ -296,8 +390,11 @@ mixin class ContactsViewControllerMixin { final currentContactsPermissionStatus = await _permissionHandlerService.requestContactsPermissionActions(); if (currentContactsPermissionStatus == PermissionStatus.granted) { + contactsManager.refreshPhonebookContacts(); warningBannerNotifier.value = WarningContactsBannerState.hide; } else { + contactsManager.updateNotShowWarningContactsDialogAgain = true; + if (!contactsManager.isDoNotShowWarningContactsBannerAgain) { warningBannerNotifier.value = WarningContactsBannerState.display; } diff --git a/lib/presentation/mixins/media_picker_mixin.dart b/lib/presentation/mixins/media_picker_mixin.dart index 4877314899..90c854b978 100644 --- a/lib/presentation/mixins/media_picker_mixin.dart +++ b/lib/presentation/mixins/media_picker_mixin.dart @@ -44,7 +44,7 @@ mixin MediaPickerMixin on CommonMediaPickerMixin { TextEditingController? captionController, ValueKey? typeAheadKey, }) async { - final currentPermissionPhotos = await getCurrentMediaPermission(); + final currentPermissionPhotos = await getCurrentMediaPermission(context); if (currentPermissionPhotos != null) { showMediasPickerBottomSheet( context: context, diff --git a/lib/utils/permission_dialog.dart b/lib/utils/permission_dialog.dart index 59aa1ed099..c935dac392 100644 --- a/lib/utils/permission_dialog.dart +++ b/lib/utils/permission_dialog.dart @@ -3,6 +3,7 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; typedef OnAcceptButton = void Function()?; +typedef OnRefuseTap = void Function()?; class PermissionDialog extends StatefulWidget { final Permission permission; @@ -13,12 +14,15 @@ class PermissionDialog extends StatefulWidget { final OnAcceptButton onAcceptButton; + final OnRefuseTap onRefuseTap; + const PermissionDialog({ super.key, required this.permission, required this.explainTextRequestPermission, this.icon, this.onAcceptButton, + this.onRefuseTap, }); @override @@ -74,6 +78,7 @@ class _PermissionDialogState extends State context: context, text: L10n.of(context)!.deny, onPressed: () { + widget.onRefuseTap?.call(); Navigator.of(context).pop(); }, ), diff --git a/lib/utils/permission_service.dart b/lib/utils/permission_service.dart index 795dd2c6d7..57c770e0f4 100644 --- a/lib/utils/permission_service.dart +++ b/lib/utils/permission_service.dart @@ -1,5 +1,8 @@ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:fluffychat/utils/permission_dialog.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; class PermissionHandlerService { @@ -14,14 +17,16 @@ class PermissionHandlerService { PermissionHandlerService._internal(); - Future? requestPermissionForMediaActions() async { + Future? requestPermissionForMediaActions( + BuildContext context, + ) async { if (Platform.isIOS) { - return _handlePhotosPermissionIOSAction(); + return _handlePhotosPermissionIOSAction(context); } else if (Platform.isAndroid) { if (await _getCurrentAndroidVersion() >= 33) { - return _handleMediaPickerPermissionAndroidHigher33Action(); + return _handleMediaPickerPermissionAndroidHigher33Action(context); } - return _handleMediaPermissionAndroidAction(); + return _handleMediaPermissionAndroidAction(context); } else { return null; } @@ -45,7 +50,7 @@ class PermissionHandlerService { } } - Future requestPermissionForMircoActions() async { + Future requestPermissionForMicroActions() async { final currentStatus = await Permission.microphone.status; if (currentStatus == PermissionStatus.denied || currentStatus == PermissionStatus.permanentlyDenied) { @@ -55,28 +60,72 @@ class PermissionHandlerService { } } - Future _handlePhotosPermissionIOSAction() async { + Future _handlePhotosPermissionIOSAction( + BuildContext context, + ) async { final currentStatus = await Permission.photos.status; - return _handlePhotoPermission(currentStatus); + return _handlePhotoPermission( + currentStatus: currentStatus, + context: context, + ); } - Future _handleMediaPermissionAndroidAction() async { + Future _handleMediaPermissionAndroidAction( + BuildContext context, + ) async { final currentStatus = await Permission.storage.status; - return _handlePhotoPermission(currentStatus); + return _handlePhotoPermission( + currentStatus: currentStatus, + context: context, + ); } - Future - _handleMediaPickerPermissionAndroidHigher33Action() async { - PermissionStatus? photoPermission = await Permission.photos.status; - if (photoPermission == PermissionStatus.denied) { - photoPermission = await Permission.photos.request(); + Future _handleMediaPickerPermissionAndroidHigher33Action( + BuildContext context, + ) async { + if (await Permission.photos.status == PermissionStatus.denied) { + await showDialog( + useRootNavigator: false, + context: context, + builder: (dialogContext) { + return PermissionDialog( + icon: const Icon(Icons.photo), + permission: Permission.contacts, + explainTextRequestPermission: Text( + L10n.of(context)!.explainPermissionToAccessPhotos, + ), + onAcceptButton: () async { + Navigator.of(dialogContext).pop(); + await Permission.photos.request(); + }, + ); + }, + ); } - PermissionStatus? videosPermission = await Permission.videos.status; - if (videosPermission == PermissionStatus.denied) { - videosPermission = await Permission.videos.request(); + if (await Permission.videos.status == PermissionStatus.denied) { + await showDialog( + useRootNavigator: false, + context: context, + builder: (dialogContext) { + return PermissionDialog( + icon: const Icon(Icons.video_camera_back_outlined), + permission: Permission.contacts, + explainTextRequestPermission: Text( + L10n.of(context)!.explainPermissionToAccessVideos, + ), + onAcceptButton: () async { + Navigator.of(dialogContext).pop(); + await Permission.videos.request(); + }, + ); + }, + ); } + final photoPermission = await Permission.photos.status; + final videosPermission = await Permission.videos.status; + if (photoPermission == PermissionStatus.granted || videosPermission == PermissionStatus.granted) { return PermissionStatus.granted; @@ -85,15 +134,37 @@ class PermissionHandlerService { return PermissionStatus.denied; } - Future _handlePhotoPermission( - PermissionStatus currentStatus, - ) async { + Future _handlePhotoPermission({ + required PermissionStatus currentStatus, + required BuildContext context, + }) async { switch (currentStatus) { case PermissionStatus.permanentlyDenied: case PermissionStatus.denied: + await showDialog( + useRootNavigator: false, + context: context, + builder: (dialogContext) { + return PermissionDialog( + icon: const Icon(Icons.photo), + permission: + Platform.isIOS ? Permission.photos : Permission.storage, + explainTextRequestPermission: Text( + L10n.of(context)!.explainPermissionToAccessMedias, + ), + onAcceptButton: () async { + Navigator.of(dialogContext).pop(); + Platform.isIOS + ? await Permission.photos.request() + : await Permission.storage.request(); + }, + ); + }, + ); final newStatus = Platform.isIOS - ? await Permission.photos.request() - : await Permission.storage.request(); + ? await Permission.photos.status + : await Permission.storage.status; + return newStatus.isGranted ? PermissionStatus.granted : newStatus; case PermissionStatus.granted: @@ -118,10 +189,12 @@ class PermissionHandlerService { Future requestContactsPermissionActions() async { final currentStatus = await contactsPermissionStatus; - if (currentStatus == PermissionStatus.denied || - currentStatus == PermissionStatus.permanentlyDenied) { + if (currentStatus == PermissionStatus.denied) { final newStatus = await Permission.contacts.request(); return newStatus.isGranted ? PermissionStatus.granted : newStatus; + } else if (currentStatus == PermissionStatus.permanentlyDenied) { + goToSettingsForPermissionActions(); + return await contactsPermissionStatus; } else { return currentStatus; } diff --git a/test/mixin/contacts_view_controller_mixin_test.dart b/test/mixin/contacts_view_controller_mixin_test.dart index 6b12c652ae..6dfa7d9f2f 100644 --- a/test/mixin/contacts_view_controller_mixin_test.dart +++ b/test/mixin/contacts_view_controller_mixin_test.dart @@ -33,6 +33,7 @@ class ConcretePresentationSearch extends PresentationSearch { } @GenerateNiceMocks([ + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -114,11 +115,13 @@ void main() { late MockContactsViewControllerMixin mockContactsViewControllerMixin; late Client mockClient; late MatrixLocalizations mockMatrixLocalizations; + late BuildContext mockBuildContext; setUp(() { mockContactsViewControllerMixin = MockContactsViewControllerMixin(); mockMatrixLocalizations = MockMatrixLocalizations(); mockClient = MockClient(); + mockBuildContext = MockBuildContext(); }); group('Test ContactsViewControllerMixin on Web', () { @@ -157,12 +160,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -230,12 +235,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -305,12 +312,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -396,12 +405,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -493,12 +504,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -663,12 +676,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -833,12 +848,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1003,12 +1020,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1202,12 +1221,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1469,12 +1490,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1542,12 +1565,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1619,12 +1644,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1722,12 +1749,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1819,12 +1848,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -1989,12 +2020,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -2161,12 +2194,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -2333,12 +2368,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), @@ -2534,12 +2571,14 @@ void main() { ); mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ); verify( mockContactsViewControllerMixin.initialFetchContacts( + context: mockBuildContext, client: mockClient, matrixLocalizations: mockMatrixLocalizations, ), From 876cd3b27ed36980e9c63444dc9dfece185afdf5 Mon Sep 17 00:00:00 2001 From: Huy Quang Nguyen Date: Fri, 19 Jul 2024 17:07:35 +0700 Subject: [PATCH 02/40] Bump version to v2.6.0 (#1958) --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e5c21ca8..8015581ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## [2.6.0+2330] - 2024-07-18 + +### Fixed + +- #1879 Fix online status is not updated correctly +- #1892 Handle error recovery key lost +- #1898 Fix memory leak in file picker +- #1897 Fix profile image is not updated in multiple account +- #1903 Check dialog status before sending file +- #1911 Fix can't open chat when search exact Matrix ID +- #1921 Fix iOS is forced to log out many times +- #1910 Remove x when searchbar is empty +- #1938 Fix wrong responsive when size of screen is small +- #1930 Improve search exact Matrix ID inside Contact tab +- #1948 Fix jump exactly to message in the notification on Mobile +- #1946 Fix 500,404 error in POST request when login + +### Added + +- #1940 Upload feature +- #1890 Renamed artifact to describe the OS +- #1880 Standarlize Appbar and Appgrid popup +- #1889 Change style loading dialog +- #1894 Change message info dialog close button +- #1905 Update online status based on design +- #1937 Improve style for backup dialog +- #1951 Integration dynamic link on android mobile +- #1944 Update quick actions +- #1956 Create permission dialogs for contacts and media + ## [2.5.8+2330] - 2024-05-25 - #1781 Upgrade to Flutter SDK 3.22.0 @@ -2353,6 +2383,7 @@ interesting devices. If you have one, I would very like to see some screenshots This CHANGELOG.md was generated with [**Changelog for Dart**](https://pub.dartlang.org/packages/changelog) +[2.6.0+2330]: https://github.com/linagora/twake-on-matrix/releases/tag/2.6.0 [2.5.2+2330]: https://github.com/linagora/twake-on-matrix/releases/tag/2.5.2 [2.4.20+2330]: https://github.com/linagora/twake-on-matrix/releases/tag/2.4.20 [2.4.19+2330]: https://github.com/linagora/twake-on-matrix/releases/tag/2.4.19 diff --git a/pubspec.yaml b/pubspec.yaml index e43ec65d12..cc22794bf7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fluffychat description: A convenient Matrix-based tool for personal and corporate communication. publish_to: none -version: 2.5.8+2330 +version: 2.6.0+2330 environment: sdk: ">=3.1.3 <4.0.0" From 3173a1950fe6d78acd0090f952fe40eedcfb078d Mon Sep 17 00:00:00 2001 From: Yadd Date: Mon, 22 Jul 2024 11:34:32 +0400 Subject: [PATCH 03/40] Spelling errors (#1959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix "below" * L'écriture inclusive est inadaptée et illisible --- assets/l10n/intl_en.arb | 2 +- assets/l10n/intl_fr.arb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 6a84810e3b..4d09b484b7 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2580,7 +2580,7 @@ }, "noMessageHereYet": "No message here yet...", "@noMessageHereYet": {}, - "sendMessageGuide": "Send a message or tap on the greeting bellow.", + "sendMessageGuide": "Send a message or tap on the greeting below.", "@sendMessageGuide": {}, "youCreatedGroupChat": "You created a Group chat", "@youCreatedGroupChat": {}, diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index 8278672104..6fe07d62bc 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -89,7 +89,7 @@ "type": "text", "placeholders": {} }, - "areGuestsAllowedToJoin": "Les invités peuvent-i·e·ls rejoindre", + "areGuestsAllowedToJoin": "Les invités peuvent-ils/elles rejoindre", "@areGuestsAllowedToJoin": { "type": "text", "placeholders": {} @@ -358,7 +358,7 @@ "type": "text", "placeholders": {} }, - "chooseAUsername": "Choisissez un nom d'utilisateur·ice", + "chooseAUsername": "Choisissez un nom d'utilisateur/trice", "@chooseAUsername": { "type": "text", "placeholders": {} From 37f529eb19d8546087e30f4c79604c07d15097f8 Mon Sep 17 00:00:00 2001 From: Huy Quang Nguyen Date: Tue, 23 Jul 2024 22:46:32 +0700 Subject: [PATCH 04/40] Hot-fix: Can't get contact on web (#1966) --- lib/presentation/mixins/contacts_view_controller_mixin.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/presentation/mixins/contacts_view_controller_mixin.dart b/lib/presentation/mixins/contacts_view_controller_mixin.dart index a7eb750de5..75b6390cee 100644 --- a/lib/presentation/mixins/contacts_view_controller_mixin.dart +++ b/lib/presentation/mixins/contacts_view_controller_mixin.dart @@ -105,6 +105,9 @@ mixin class ContactsViewControllerMixin { } Future _initWarningBanner() async { + if (!PlatformInfos.isMobile) { + return; + } final currentContactPermission = await _permissionHandlerService.contactsPermissionStatus; Logs().i( From f24459006b3247fd9490be39f4982f800a9233b8 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA <31937920+Te-Z@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:13:36 +0200 Subject: [PATCH 05/40] Hotfix: Proeminent disclosure update (#1976) * update prominent disclosure * update android target sdk version --- android/app/build.gradle | 2 +- assets/l10n/intl_en.arb | 8 ++-- assets/l10n/intl_fr.arb | 8 ++-- .../mixins/media_picker_mixin.dart | 42 +++++++++++------ lib/utils/permission_service.dart | 47 ++++++++++++------- pubspec.lock | 2 +- 6 files changed, 67 insertions(+), 42 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cee5c1f7b6..191d884db1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -46,7 +46,7 @@ android { defaultConfig { applicationId "app.twake.android.chat" minSdkVersion 23 - targetSdkVersion 33 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 4d09b484b7..b1de832c9b 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3041,10 +3041,10 @@ } } }, - "explainPermissionToAccessContacts": "Twake chat needs access to your contacts to find out whether your friends are on the matrix server. This is done locally, and your contacts are not synchronized with our server.", - "explainPermissionToAccessMedias": "Twake chat needs access to storage so you can send and save photos, videos, music and other documents. Press Settings > Authorizations, then activate Storage authorization: Photos and videos.", - "explainPermissionToAccessPhotos": "Twake chat needs access to your photos so you can send and save images. Press Settings > Permissions, then enable Storage permission: Photos.", - "explainPermissionToAccessVideos": "Twake chat needs access to your videos so you can send and save videos. Press Settings > Permissions, then enable Storage permission: Videos.", + "explainPermissionToAccessContacts": "Twake chat collects your contacts only when you are on a contact search screen to find out whether your friends are on the Matrix server, enabling connection with them. Your contacts are not synchronized with our server.", + "explainPermissionToAccessMedias": "Twake chat collects your photos, videos, music, and other documents to enable sending and saving selected files. Please go to Settings > Permissions and activate Storage permission: Photos and Videos.", + "explainPermissionToAccessPhotos": "Twake chat collects your photos to enable sending and saving selected photos only when the app is in use. Press Settings > Permissions, then enable Storage permission: Photos.", + "explainPermissionToAccessVideos": "Twake chat collects your videos to enable sending and saving selected videos only when the app is in use. Press Settings > Permissions, then enable Storage permission: Videos.", "downloading": "Downloading", "settingUpYourTwake": "Setting up your Twake\nIt could take a while", "performingAutomaticalLogin": "Performing automatical login via SSO", diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index 6fe07d62bc..7081bb49dd 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -2714,10 +2714,10 @@ "count": {} } }, - "explainPermissionToAccessContacts": "Twake chat doit accéder à vos contacts pour savoir si vos amis sont sur le serveur matrix. Cela se fait localement et vos contacts ne sont pas synchronisés avec notre serveur.", - "explainPermissionToAccessMedias": "Twake chat a besoin d'accéder au stockage pour que vous puissiez envoyer et enregistrer des photos, vidéos, musiques et autres documents. Appuyez sur Paramètres > Autorisations puis activez l'autorisation de stockage: Photos et vidéos.", - "explainPermissionToAccessPhotos": "Twake chat a besoin d'accéder à vos photos pour que vous puissiez envoyer et de enregistrer des images. Allez dans Paramètres > Autorisations, puis activez l'autorisation de stockage : Photos.", - "explainPermissionToAccessVideos": "Twake chat a besoin d'accéder à vos vidéos pour que vous puissiez envoyer et de sauvegarder des vidéos. Allez dans Paramètres > Autorisations, puis activez l'autorisation de stockage : Vidéos.", + "explainPermissionToAccessContacts": "Twake chat collecte vos contacts uniquement lorsque vous êtes sur un écran de recherche de contacts pour vérifier si vos amis sont sur le serveur Matrix, ce qui permet de se connecter avec eux. Vos contacts ne sont pas synchronisés avec notre serveur.", + "explainPermissionToAccessMedias": "Twake chat collecte vos photos, vidéos, musiques et autres documents pour permettre l'envoi et la sauvegarde des fichiers sélectionnés. Veuillez aller dans Paramètres > Autorisations et activer l'autorisation de stockage : Photos et vidéos.", + "explainPermissionToAccessPhotos": "Twake chat collecte vos photos pour permettre l'envoi et la sauvegarde des photos sélectionnées uniquement lorsque l'application est en cours d'utilisation. Appuyez sur Paramètres > Autorisations, puis activez l'autorisation de stockage : Photos.", + "explainPermissionToAccessVideos": "Twake chat collecte vos vidéos pour permettre l'envoi et la sauvegarde des vidéos sélectionnées uniquement lorsque l'application est en cours d'utilisation. Appuyez sur Paramètres > Autorisations, puis activez l'autorisation de stockage : Vidéos.", "recentChat": "DISCUSSION RÉCENTE", "@recentChat": {}, "muteThisMessage": "Couper le son de ce salon", diff --git a/lib/presentation/mixins/media_picker_mixin.dart b/lib/presentation/mixins/media_picker_mixin.dart index 90c854b978..1fc9e056ec 100644 --- a/lib/presentation/mixins/media_picker_mixin.dart +++ b/lib/presentation/mixins/media_picker_mixin.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pages/chat/item_actions_bottom_widget.dart'; import 'package:fluffychat/pages/chat/send_file_dialog/send_file_dialog_style.dart'; import 'package:fluffychat/presentation/style/media_picker_style.dart'; import 'package:fluffychat/resource/image_paths.dart'; +import 'package:fluffychat/utils/permission_service.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -33,6 +34,9 @@ mixin MediaPickerMixin on CommonMediaPickerMixin { // PickerType.contact, ]; + final PermissionHandlerService _permissionHandlerService = + PermissionHandlerService(); + void showMediaPickerBottomSheetAction({ required BuildContext context, required ImagePickerGridController imagePickerGridController, @@ -44,21 +48,26 @@ mixin MediaPickerMixin on CommonMediaPickerMixin { TextEditingController? captionController, ValueKey? typeAheadKey, }) async { - final currentPermissionPhotos = await getCurrentMediaPermission(context); - if (currentPermissionPhotos != null) { - showMediasPickerBottomSheet( - context: context, - imagePickerController: imagePickerGridController, - permissionStatusPhotos: currentPermissionPhotos, - onSendTap: onSendTap, - room: room, - onPickerTypeTap: onPickerTypeTap, - onCameraPicked: onCameraPicked, - focusSuggestionController: focusSuggestionController, - captionController: captionController, - typeAheadKey: typeAheadKey, + await getCurrentMediaPermission(context)?.then((currentPermissionPhotos) { + if (currentPermissionPhotos != null) { + showMediasPickerBottomSheet( + context: context, + imagePickerController: imagePickerGridController, + permissionStatusPhotos: currentPermissionPhotos, + onSendTap: onSendTap, + room: room, + onPickerTypeTap: onPickerTypeTap, + onCameraPicked: onCameraPicked, + focusSuggestionController: focusSuggestionController, + captionController: captionController, + typeAheadKey: typeAheadKey, + ); + } + }).onError((error, _) { + Logs().e( + "MediaPickerMixin::showMediaPickerBottomSheetAction(): error - $error", ); - } + }); } Future showMediasPickerBottomSheet({ @@ -288,6 +297,11 @@ mixin MediaPickerMixin on CommonMediaPickerMixin { ), ), ), + onGoToSettings: (context) async { + Navigator.pop(context); + await _permissionHandlerService + .requestPermissionForMediaActions(context); + }, goToSettingsWidget: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/utils/permission_service.dart b/lib/utils/permission_service.dart index 57c770e0f4..81ff632b36 100644 --- a/lib/utils/permission_service.dart +++ b/lib/utils/permission_service.dart @@ -84,43 +84,52 @@ class PermissionHandlerService { BuildContext context, ) async { if (await Permission.photos.status == PermissionStatus.denied) { - await showDialog( + final result = await showDialog( useRootNavigator: false, context: context, + barrierDismissible: false, builder: (dialogContext) { return PermissionDialog( icon: const Icon(Icons.photo), - permission: Permission.contacts, + permission: Permission.photos, explainTextRequestPermission: Text( L10n.of(context)!.explainPermissionToAccessPhotos, ), onAcceptButton: () async { - Navigator.of(dialogContext).pop(); - await Permission.photos.request(); + Navigator.of(dialogContext).pop(true); }, ); }, ); + + if (result != null && result) { + final newStatus = await Permission.photos.request(); + return newStatus; + } } if (await Permission.videos.status == PermissionStatus.denied) { - await showDialog( + final result = await showDialog( useRootNavigator: false, context: context, + barrierDismissible: false, builder: (dialogContext) { return PermissionDialog( icon: const Icon(Icons.video_camera_back_outlined), - permission: Permission.contacts, + permission: Permission.videos, explainTextRequestPermission: Text( L10n.of(context)!.explainPermissionToAccessVideos, ), onAcceptButton: () async { - Navigator.of(dialogContext).pop(); - await Permission.videos.request(); + Navigator.of(dialogContext).pop(true); }, ); }, ); + if (result != null && result) { + final newStatus = await Permission.videos.request(); + return newStatus; + } } final photoPermission = await Permission.photos.status; @@ -141,9 +150,10 @@ class PermissionHandlerService { switch (currentStatus) { case PermissionStatus.permanentlyDenied: case PermissionStatus.denied: - await showDialog( + final result = await showDialog( useRootNavigator: false, context: context, + barrierDismissible: false, builder: (dialogContext) { return PermissionDialog( icon: const Icon(Icons.photo), @@ -153,19 +163,20 @@ class PermissionHandlerService { L10n.of(context)!.explainPermissionToAccessMedias, ), onAcceptButton: () async { - Navigator.of(dialogContext).pop(); - Platform.isIOS - ? await Permission.photos.request() - : await Permission.storage.request(); + Navigator.of(dialogContext).pop(true); }, ); }, ); - final newStatus = Platform.isIOS - ? await Permission.photos.status - : await Permission.storage.status; - - return newStatus.isGranted ? PermissionStatus.granted : newStatus; + if (result != null && result) { + final newStatus = Platform.isIOS + ? await Permission.photos.request() + : await Permission.storage.request(); + + return newStatus.isGranted ? PermissionStatus.granted : newStatus; + } else { + return currentStatus; + } case PermissionStatus.granted: case PermissionStatus.limited: diff --git a/pubspec.lock b/pubspec.lock index 07efc7182e..ff646d90fe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1703,7 +1703,7 @@ packages: description: path: "." ref: master - resolved-ref: ffbcb9dd4d6cefb7fb60b7a9b15ad684dae6bdff + resolved-ref: "0f666ca14fd4f0710775ca3de03cde73b21de7d6" url: "git@github.com:linagora/linagora-design-flutter.git" source: git version: "0.0.1" From a74b6eaf8c8a71f3fb08c4a1513ec29ca28245f1 Mon Sep 17 00:00:00 2001 From: Huy Quang Nguyen Date: Thu, 25 Jul 2024 19:57:19 +0700 Subject: [PATCH 06/40] TW-1906: Support send muliple files in draft chat (#1967) --- lib/pages/chat_draft/draft_chat.dart | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/pages/chat_draft/draft_chat.dart b/lib/pages/chat_draft/draft_chat.dart index 7fb0a90f74..7ffad50c3c 100644 --- a/lib/pages/chat_draft/draft_chat.dart +++ b/lib/pages/chat_draft/draft_chat.dart @@ -340,7 +340,8 @@ class DraftChatController extends State Room? room, }) async { final result = await FilePicker.platform.pickFiles( - withData: true, + withReadStream: true, + allowMultiple: true, ); if (result == null || result.files.isEmpty) return; @@ -357,15 +358,6 @@ class DraftChatController extends State BuildContext context, List matrixFilesList, ) async { - const int maxFileQuantity = 1; - if (matrixFilesList.length > maxFileQuantity) { - TwakeSnackBar.show( - context, - L10n.of(context)!.countFilesSendPerDialog(maxFileQuantity), - ); - return; - } - if (matrixFilesList.isEmpty) { TwakeSnackBar.show( context, @@ -376,7 +368,7 @@ class DraftChatController extends State final dialogStatus = await sendImagesWithCaption( context: context, - matrixFiles: [matrixFilesList.first], + matrixFiles: matrixFilesList, ); if (dialogStatus is SendMediaWithCaptionStatus) { From 3171fe0d00522b22325659fc42aec62ba24fafd4 Mon Sep 17 00:00:00 2001 From: sherlock <43041967+sherlockvn@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:16:43 +0700 Subject: [PATCH 07/40] TW-1901: crash app when open play video mobile (#1979) * TW-1844: add try/catch for thumbnail cannot display exception * TW-1844: add extension for thumbnails when sending images and videos --- lib/config/app_config.dart | 4 +++ .../extensions/send_file_extension.dart | 4 +-- .../extensions/send_file_web_extension.dart | 9 +++-- .../download_file_extension.dart | 4 +++ lib/widgets/mxc_image.dart | 35 ++++++++++--------- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 09f84c35b5..1d33ddf0e2 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -6,7 +6,9 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:matrix/matrix.dart'; +import 'package:video_thumbnail/video_thumbnail.dart'; abstract class AppConfig { static ResponsiveUtils responsive = getIt.get(); @@ -115,6 +117,8 @@ abstract class AppConfig { static const String iOSKeychainSharingAccount = 'app.twake.ios.chat.sessions'; static const int maxFilesSendPerDialog = 6; static const bool supportMultipleAccountsInTheSameHomeserver = false; + static const imageCompressFormmat = CompressFormat.jpeg; + static const videoThumbnailFormat = ImageFormat.JPEG; static String? issueId; diff --git a/lib/presentation/extensions/send_file_extension.dart b/lib/presentation/extensions/send_file_extension.dart index 5cb216ed3c..7b1ec0c8d4 100644 --- a/lib/presentation/extensions/send_file_extension.dart +++ b/lib/presentation/extensions/send_file_extension.dart @@ -575,7 +575,7 @@ extension SendFileExtension on Room { originalFile.filePath, targetPath, quality: AppConfig.thumbnailQuality, - format: CompressFormat.jpeg, + format: AppConfig.imageCompressFormmat, ); if (result == null) return null; final size = await result.length(); @@ -591,7 +591,7 @@ extension SendFileExtension on Room { } uploadStreamController?.add(const Right(GenerateThumbnailSuccess())); return ImageFileInfo( - result.name, + '${result.name}.${AppConfig.imageCompressFormmat.name}', result.path, size, width: width, diff --git a/lib/presentation/extensions/send_file_web_extension.dart b/lib/presentation/extensions/send_file_web_extension.dart index 8ce51baa67..a2a9a66afb 100644 --- a/lib/presentation/extensions/send_file_web_extension.dart +++ b/lib/presentation/extensions/send_file_web_extension.dart @@ -372,6 +372,7 @@ extension SendFileWebExtension on Room { final result = await FlutterImageCompress.compressWithList( originalFile.bytes!, quality: AppConfig.thumbnailQuality, + format: AppConfig.imageCompressFormmat, ); final blurHash = await runBenchmarked( @@ -385,7 +386,7 @@ extension SendFileWebExtension on Room { return MatrixImageFile( bytes: result, - name: originalFile.name, + name: '${originalFile.name}.${AppConfig.imageCompressFormmat.name}', mimeType: originalFile.mimeType, width: originalFile.width, height: originalFile.height, @@ -425,9 +426,10 @@ extension SendFileWebExtension on Room { ); throw exception; } + final result = await VideoThumbnail.thumbnailData( video: url, - imageFormat: ImageFormat.JPEG, + imageFormat: AppConfig.videoThumbnailFormat, quality: AppConfig.thumbnailQuality, ); final thumbnailBitmap = await convertUint8ListToBitmap(result); @@ -442,7 +444,8 @@ extension SendFileWebExtension on Room { return MatrixImageFile( bytes: result, - name: originalFile.name, + name: + '${originalFile.name}.${AppConfig.videoThumbnailFormat.name.toLowerCase()}', mimeType: originalFile.mimeType, width: thumbnailBitmap?.width, height: thumbnailBitmap?.height, diff --git a/lib/utils/matrix_sdk_extensions/download_file_extension.dart b/lib/utils/matrix_sdk_extensions/download_file_extension.dart index 1b8483cd54..daf0f62690 100644 --- a/lib/utils/matrix_sdk_extensions/download_file_extension.dart +++ b/lib/utils/matrix_sdk_extensions/download_file_extension.dart @@ -276,6 +276,10 @@ extension DownloadFileExtension on Event { throw "getFileInfo: This event hasn't any attachment or thumbnail."; } + if (getThumbnail && thumbnailMimetype.startsWith('image') != true) { + throw ('getFileInfo: This event has a thumbnail but it is not an image.'); + } + final isFileEncrypted = getThumbnail ? isThumbnailEncrypted : isAttachmentEncrypted; if (isEncryptionDisabled(isFileEncrypted)) { diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index b805845e4f..be84795e4c 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -182,26 +182,27 @@ class _MxcImageState extends State { } if (event != null) { - if (!PlatformInfos.isWeb) { - final fileInfo = await event.getFileInfo( - getThumbnail: widget.isThumbnail, - ); - if (fileInfo != null && fileInfo.filePath.isNotEmpty) { - setState(() { + try { + if (!PlatformInfos.isWeb) { + final fileInfo = await event.getFileInfo( + getThumbnail: widget.isThumbnail, + ); + if (fileInfo != null && fileInfo.filePath.isNotEmpty) { filePath = fileInfo.filePath; - }); - return; + return; + } } - } - final matrixFile = await event.downloadAndDecryptAttachment( - getThumbnail: widget.isThumbnail, - ); - if (!mounted) return; - setState(() { + final matrixFile = await event.downloadAndDecryptAttachment( + getThumbnail: widget.isThumbnail, + ); + if (!mounted) return; _imageData = matrixFile.bytes; - }); - return; + return; + } catch (e) { + Logs().e('MxcImage::Error while downloading image: $e'); + } + if (!mounted) return; } } @@ -372,6 +373,7 @@ class _ImageWidget extends StatelessWidget { height: height, width: width, fit: BoxFit.cover, + errorBuilder: imageErrorWidgetBuilder, ) : Image.memory( data!, @@ -426,6 +428,7 @@ class _ImageNativeBuilder extends StatelessWidget { height: height, width: width, fit: BoxFit.cover, + errorBuilder: imageErrorWidgetBuilder, ); } return Image.file( From c4a198e4fa06cf373bb52a8162a89076d0f0f456 Mon Sep 17 00:00:00 2001 From: sherlock <43041967+sherlockvn@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:19:08 +0700 Subject: [PATCH 08/40] TW-1808: fix navigatation problem in forward pages (#1971) --- lib/pages/chat/chat.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 089441c617..bd454ab015 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -828,7 +828,7 @@ class ChatController extends State ); } _clearSelectEvent(); - context.go( + context.push( '/rooms/forward', extra: ForwardArgument( fromRoomId: roomId ?? '', From 3efa605c077ebae9b3f825e84839d776dd64ea86 Mon Sep 17 00:00:00 2001 From: "khaled.njim" Date: Fri, 26 Jul 2024 11:22:21 +0100 Subject: [PATCH 09/40] TW-1918 fixed typo error in externalContactMessage --- assets/l10n/intl_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index b1de832c9b..d6d711d698 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2732,7 +2732,7 @@ "@chatsAndContacts": {}, "externalContactTitle": "Invite new users", "@externalContactTitle": {}, - "externalContactMessage": "Some of the users you want to add are not in your contacs. Do you want to invite them?", + "externalContactMessage": "Some of the users you want to add are not in your contacts. Do you want to invite them?", "@externalContactMessage": {}, "clear": "Clear", "@clear": {}, From dee29a085c1cf228d78a57f8846d88c53d2afed4 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 5 Aug 2024 09:56:23 +0700 Subject: [PATCH 10/40] TW-1813: Update event desc for pinned file in chat list --- lib/presentation/mixins/chat_list_item_mixin.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/presentation/mixins/chat_list_item_mixin.dart b/lib/presentation/mixins/chat_list_item_mixin.dart index ca1bc8def5..b7c346d875 100644 --- a/lib/presentation/mixins/chat_list_item_mixin.dart +++ b/lib/presentation/mixins/chat_list_item_mixin.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_view.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -112,6 +113,15 @@ mixin ChatListItemMixin { removeBreakLine: true, ) ?? L10n.of(context)!.emptyChat; + if (room.lastEvent?.isAFile == true) { + return Text( + "${snapshot.data!.calcDisplayname()}: ${room.lastEvent?.filename ?? subscriptions}", + softWrap: false, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: ChatLitSubSubtitleTextStyleView.textStyle.textStyle(room), + ); + } return Text( "${snapshot.data!.calcDisplayname()}: $subscriptions", From d678646441f8c1c29b357446759a0d0487a910d8 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 5 Aug 2024 09:56:54 +0700 Subject: [PATCH 11/40] TW-1813: Update event desc for pinned file in pin message banner --- .../chat_pinned_events/pinned_events_view.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/pages/chat/chat_pinned_events/pinned_events_view.dart b/lib/pages/chat/chat_pinned_events/pinned_events_view.dart index 8443f59353..1322fe5aa0 100644 --- a/lib/pages/chat/chat_pinned_events/pinned_events_view.dart +++ b/lib/pages/chat/chat_pinned_events/pinned_events_view.dart @@ -2,6 +2,7 @@ import 'package:fluffychat/domain/app_state/room/chat_get_pinned_events_state.da import 'package:fluffychat/pages/chat/chat_pinned_events/pinned_events_argument.dart'; import 'package:fluffychat/pages/chat/chat_pinned_events/pinned_events_style.dart'; import 'package:fluffychat/utils/extension/build_context_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; @@ -230,6 +231,22 @@ class _PinnedEventsContentWidget extends StatelessWidget { hideReply: true, ), builder: (context, snapshot) { + if (currentEvent.isAFile) { + return LinkText( + text: currentEvent.filename, + maxLines: 1, + textStyle: + LinagoraTextStyle.material().bodyMedium3.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + overflow: TextOverflow.ellipsis, + decoration: currentEvent.redacted + ? TextDecoration.lineThrough + : null, + ), + ); + } return LinkText( text: snapshot.data ?? currentEvent.calcLocalizedBodyFallback( From 4d2df45eaec7e08454d13de6ce89cf1e796cb208 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 5 Aug 2024 09:58:16 +0700 Subject: [PATCH 12/40] TW-1813: Update event desc for pinned file in reply content --- lib/pages/chat/events/reply_content.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/pages/chat/events/reply_content.dart b/lib/pages/chat/events/reply_content.dart index 0188a559c8..ece7623140 100644 --- a/lib/pages/chat/events/reply_content.dart +++ b/lib/pages/chat/events/reply_content.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/events/reply_content_style.dart'; +import 'package:fluffychat/resource/image_paths.dart'; import 'package:fluffychat/utils/extension/event_info_extension.dart'; import 'package:fluffychat/utils/extension/mime_type_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; @@ -51,6 +52,13 @@ class ReplyContent extends StatelessWidget { room: displayEvent.room, emoteSize: ReplyContentStyle.fontSizeDisplayContent * 1.5, ); + } else if (displayEvent.isAFile) { + replyBody = Text( + displayEvent.filename, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: ReplyContentStyle.replyBodyTextStyle(context), + ); } else { replyBody = Text( displayEvent.calcLocalizedBodyFallback( @@ -152,6 +160,16 @@ class ReplyPreviewIconBuilder extends StatelessWidget { height: ReplyContentStyle.replyContentSize, ); } + if (event.isAFile) { + return SvgPicture.asset( + event.mimeType?.getIcon( + fileType: event.fileType, + ) ?? + ImagePaths.icFileUnknown, + width: ReplyContentStyle.replyContentSize, + height: ReplyContentStyle.replyContentSize, + ); + } return ClipRRect( borderRadius: ReplyContentStyle.previewedImageBorderRadius, child: MxcImage( From 1b253feabc5bc34b6a2ec5eae81d7fcd8c7cf6f3 Mon Sep 17 00:00:00 2001 From: KhaledNjim <160496984+KhaledNjim@users.noreply.github.com> Date: Mon, 19 Aug 2024 06:44:20 +0100 Subject: [PATCH 13/40] TW-1886 update chatlist UI part1 (#1943) * TW-1886 update design for chats appbar * TW1886 update design for bottom navigationBar * TW-1886 Changed NavigationBar to BottomNavigationBar --- lib/config/themes.dart | 44 +++++++++- lib/pages/chat_list/chat_list_header.dart | 12 +++ .../chat_list/chat_list_header_style.dart | 8 +- lib/utils/responsive/responsive_utils.dart | 2 +- .../avatar/bottom_navigation_avatar.dart | 43 ++++++++++ .../bottom_navigation_avatar_style.dart | 5 ++ ...ive_scaffold_primary_navigation_style.dart | 8 ++ ...tive_scaffold_primary_navigation_view.dart | 7 +- .../app_adaptive_scaffold_body.dart | 38 +++++++++ .../app_adaptive_scaffold_body_view.dart | 61 +++++++++++--- ...app_adaptive_scaffold_body_view_style.dart | 13 +++ .../enum/adaptive_destinations_enum.dart | 80 +++++++++++++++++-- .../twake_components/twake_header.dart | 47 +++-------- .../twake_components/twake_header_style.dart | 1 + .../twake_navigation_icon.dart | 9 ++- lib/widgets/unread_rooms_badge.dart | 10 ++- 16 files changed, 326 insertions(+), 62 deletions(-) create mode 100644 lib/widgets/avatar/bottom_navigation_avatar.dart create mode 100644 lib/widgets/avatar/bottom_navigation_avatar_style.dart diff --git a/lib/config/themes.dart b/lib/config/themes.dart index d474774b73..ea3c76febb 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -1,3 +1,5 @@ +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; @@ -19,6 +21,8 @@ abstract class TwakeThemes { static bool getDisplayNavigationRail(BuildContext context) => !(GoRouterState.of(context).path?.startsWith('/settings') == true); + static ResponsiveUtils responsive = getIt.get(); + static var fallbackTextTheme = TextTheme( bodyLarge: GoogleFonts.inter( fontWeight: FontWeight.w500, @@ -64,6 +68,7 @@ abstract class TwakeThemes { ), titleLarge: GoogleFonts.inter( fontWeight: FontWeight.w600, + letterSpacing: -0.15, ), titleMedium: GoogleFonts.inter( fontWeight: FontWeight.w500, @@ -314,6 +319,25 @@ abstract class TwakeThemes { ), navigationBarTheme: NavigationBarThemeData( height: 64, + labelTextStyle: WidgetStateProperty.resolveWith( + (states) { + if (states.contains(WidgetState.selected)) { + return fallbackTextTheme.labelSmall?.copyWith( + fontSize: 11, + color: LinagoraSysColors.material().primary, + ); + } + return responsive.isDesktop(context) + ? fallbackTextTheme.labelSmall?.copyWith( + fontSize: 11, + color: LinagoraRefColors.material().neutral[10], + ) + : fallbackTextTheme.labelSmall?.copyWith( + fontSize: 11, + color: LinagoraSysColors.material().tertiary, + ); + }, + ), backgroundColor: brightness == Brightness.light ? LinagoraSysColors.material().surface : LinagoraSysColors.material().surfaceDark, @@ -321,10 +345,15 @@ abstract class TwakeThemes { ? Colors.black.withOpacity(0.15) : Colors.white.withOpacity(0.15), elevation: 4.0, + overlayColor: WidgetStateColor.resolveWith( + (states) { + return Colors.transparent; + }, + ), ), navigationRailTheme: NavigationRailThemeData( indicatorColor: brightness == Brightness.light - ? LinagoraSysColors.material().secondaryContainer + ? LinagoraSysColors.material().inversePrimary : LinagoraSysColors.material().secondaryContainerDark, ), bottomSheetTheme: BottomSheetThemeData( @@ -335,6 +364,19 @@ abstract class TwakeThemes { ? LinagoraSysColors.material().background : LinagoraSysColors.material().backgroundDark, ), + bottomNavigationBarTheme: BottomNavigationBarThemeData( + backgroundColor: LinagoraSysColors.material().surface, + selectedLabelStyle: fallbackTextTheme.labelSmall?.copyWith( + fontSize: 11, + color: LinagoraSysColors.material().primary, + ), + unselectedLabelStyle: fallbackTextTheme.labelSmall?.copyWith( + fontSize: 11, + color: LinagoraSysColors.material().tertiary, + ), + selectedItemColor: LinagoraSysColors.material().primary, + unselectedItemColor: LinagoraSysColors.material().tertiary, + ), ); } diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index 55e463585a..a0f3c174a8 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -6,7 +6,9 @@ import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/swipe_to_dismiss_wrap.dart'; import 'package:fluffychat/widgets/twake_components/twake_header.dart'; import 'package:flutter/material.dart'; +import 'package:linagora_design_flutter/colors/linagora_state_layer.dart'; import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; class ChatListHeader extends StatelessWidget { final ChatListController controller; @@ -31,12 +33,22 @@ class ChatListHeader extends StatelessWidget { onClickAvatar: controller.onClickAvatar, ), Container( + color: ChatListHeaderStyle.responsive.isMobile(context) + ? LinagoraSysColors.material().background + : Colors.transparent, height: ChatListHeaderStyle.searchBarContainerHeight, padding: ChatListHeaderStyle.searchInputPadding, child: PlatformInfos.isWeb ? _normalModeWidgetWeb(context) : _normalModeWidgetsMobile(context), ), + if (ChatListHeaderStyle.responsive.isMobile(context)) + Divider( + height: ChatListHeaderStyle.dividerHeight, + thickness: ChatListHeaderStyle.dividerThickness, + color: LinagoraStateLayer(LinagoraSysColors.material().surfaceTint) + .opacityLayer3, + ), ], ); } diff --git a/lib/pages/chat_list/chat_list_header_style.dart b/lib/pages/chat_list/chat_list_header_style.dart index bdbaa9ee18..255f7d0fa4 100644 --- a/lib/pages/chat_list/chat_list_header_style.dart +++ b/lib/pages/chat_list/chat_list_header_style.dart @@ -9,13 +9,14 @@ class ChatListHeaderStyle { static ResponsiveUtils responsive = getIt.get(); static const double searchRadiusBorder = 24.0; - static const double searchBarContainerHeight = 64.0; + static const double searchBarContainerHeight = 57.0; static const double searchIconSize = 24.0; static const EdgeInsetsDirectional searchInputPadding = EdgeInsetsDirectional.only( start: 16, end: 16, + bottom: 8, ); static const EdgeInsetsDirectional paddingZero = EdgeInsetsDirectional.zero; @@ -41,11 +42,14 @@ class ChatListHeaderStyle { ), floatingLabelBehavior: FloatingLabelBehavior.never, prefixIcon: Icon( - Icons.search_outlined, + Icons.search, size: ChatListHeaderStyle.searchIconSize, color: prefixIconColor ?? Theme.of(context).colorScheme.onSurface, ), suffixIcon: const SizedBox.shrink(), ); } + + static const dividerHeight = 1.0; + static const dividerThickness = 1.0; } diff --git a/lib/utils/responsive/responsive_utils.dart b/lib/utils/responsive/responsive_utils.dart index 74f63f2e4b..04f1e89a11 100644 --- a/lib/utils/responsive/responsive_utils.dart +++ b/lib/utils/responsive/responsive_utils.dart @@ -18,7 +18,7 @@ class ResponsiveUtils { static const double defaultSizeBodyLayoutDesktop = 280; static const double heightBottomNavigation = 72; - static const double heightBottomNavigationBar = 56; + static const double heightBottomNavigationBar = 48; static const double bodyWithRightColumnRatio = 0.64; static const double groupDetailsMinWidth = 370; diff --git a/lib/widgets/avatar/bottom_navigation_avatar.dart b/lib/widgets/avatar/bottom_navigation_avatar.dart new file mode 100644 index 0000000000..6d45ea4f71 --- /dev/null +++ b/lib/widgets/avatar/bottom_navigation_avatar.dart @@ -0,0 +1,43 @@ +import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:fluffychat/widgets/avatar/bottom_navigation_avatar_style.dart'; +import 'package:flutter/material.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; +import 'package:matrix/matrix.dart'; + +class BottomNavigationAvatar extends StatelessWidget { + final bool isSelected; + final ValueNotifier profile; + + const BottomNavigationAvatar({ + super.key, + required this.isSelected, + required this.profile, + }); + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: profile, + builder: (context, profile, child) { + return Container( + decoration: isSelected + ? ShapeDecoration( + shape: CircleBorder( + side: BorderSide( + width: + BottomNavigationAvatarStyle.selectedavatarBorderWidth, + color: LinagoraSysColors.material().primary, + ), + ), + ) + : null, + child: Avatar( + name: profile?.displayName, + mxContent: profile?.avatarUrl, + size: BottomNavigationAvatarStyle.avatarSize, + fontSize: BottomNavigationAvatarStyle.avatarFontSize, + ), + ); + }, + ); + } +} diff --git a/lib/widgets/avatar/bottom_navigation_avatar_style.dart b/lib/widgets/avatar/bottom_navigation_avatar_style.dart new file mode 100644 index 0000000000..74cc3ed998 --- /dev/null +++ b/lib/widgets/avatar/bottom_navigation_avatar_style.dart @@ -0,0 +1,5 @@ +class BottomNavigationAvatarStyle { + static const double avatarSize = 24.0; + static const double avatarFontSize = 10.0; + static const double selectedavatarBorderWidth = 2.0; +} diff --git a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_style.dart b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_style.dart index 5fcde1a526..29b2c06663 100644 --- a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_style.dart +++ b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_style.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; class AdaptiveScaffoldPrimaryNavigationStyle { static const EdgeInsetsDirectional primaryNavigationMargin = @@ -13,6 +14,13 @@ class AdaptiveScaffoldPrimaryNavigationStyle { ); } + static TextStyle? selectedLabelTextStyle(BuildContext context) { + return Theme.of(context).textTheme.labelMedium?.copyWith( + color: LinagoraSysColors.material().primary, + overflow: TextOverflow.ellipsis, + ); + } + static const double primaryNavigationWidth = 80; static const double avatarSize = 56; static const double dividerSize = 2; diff --git a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart index 5e210b2826..ffd73abd36 100644 --- a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart +++ b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/widgets/layouts/adaptive_layout/adaptive_scaffold_pri import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; import 'package:matrix/matrix.dart'; class AdaptiveScaffoldPrimaryNavigationView extends StatelessWidget { @@ -25,7 +26,6 @@ class AdaptiveScaffoldPrimaryNavigationView extends StatelessWidget { return Material( color: Theme.of(context).colorScheme.surface, child: Container( - margin: AdaptiveScaffoldPrimaryNavigationStyle.primaryNavigationMargin, width: AdaptiveScaffoldPrimaryNavigationStyle.primaryNavigationWidth, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, @@ -40,14 +40,15 @@ class AdaptiveScaffoldPrimaryNavigationView extends StatelessWidget { onDestinationSelected: onDestinationSelected, labelType: NavigationRailLabelType.all, backgroundColor: Theme.of(context).colorScheme.surface, - selectedLabelTextStyle: - AdaptiveScaffoldPrimaryNavigationStyle.labelTextStyle( + selectedLabelTextStyle: AdaptiveScaffoldPrimaryNavigationStyle + .selectedLabelTextStyle( context, ), unselectedLabelTextStyle: AdaptiveScaffoldPrimaryNavigationStyle.labelTextStyle( context, ), + indicatorColor: LinagoraSysColors.material().secondaryContainer, ), ), Column( diff --git a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart index 2c06f52fb2..5ed048d058 100644 --- a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart +++ b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:fluffychat/config/first_column_inner_routes.dart'; +import 'package:fluffychat/event/twake_inapp_event_types.dart'; import 'package:fluffychat/presentation/enum/settings/settings_action_enum.dart'; import 'package:fluffychat/presentation/mixins/connect_page_mixin.dart'; import 'package:fluffychat/utils/extension/build_context_extension.dart'; @@ -6,6 +9,7 @@ import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart'; import 'package:fluffychat/widgets/layouts/agruments/app_adaptive_scaffold_body_args.dart'; +import 'package:fluffychat/widgets/layouts/agruments/logged_in_other_account_body_args.dart'; import 'package:fluffychat/widgets/layouts/agruments/logout_body_args.dart'; import 'package:fluffychat/widgets/layouts/agruments/switch_active_account_body_args.dart'; import 'package:fluffychat/widgets/layouts/enum/adaptive_destinations_enum.dart'; @@ -42,6 +46,8 @@ class AppAdaptiveScaffoldBodyController extends State ValueNotifier(AdaptiveDestinationEnum.rooms); final activeRoomIdNotifier = ValueNotifier(null); + final currentProfileNotifier = ValueNotifier(Profile(userId: '')); + StreamSubscription? onAccountDataSubscription; final PageController pageController = PageController(initialPage: 1, keepPage: true); @@ -117,11 +123,32 @@ class AppAdaptiveScaffoldBodyController extends State void _handleLogout(AppAdaptiveScaffoldBody oldWidget) { activeNavigationBarNotifier.value = AdaptiveDestinationEnum.rooms; pageController.jumpToPage(AdaptiveDestinationEnum.rooms.index); + getCurrentProfile(); + onAccountDataSubscription?.cancel(); + _handleProfileDataChange(); } void _handleSwitchAccount(AppAdaptiveScaffoldBody oldWidget) { activeNavigationBarNotifier.value = AdaptiveDestinationEnum.rooms; pageController.jumpToPage(AdaptiveDestinationEnum.rooms.index); + getCurrentProfile(); + onAccountDataSubscription?.cancel(); + _handleProfileDataChange(); + } + + void getCurrentProfile() async { + final profile = + await matrix.client.fetchOwnProfile(getFromRooms: false, cache: false); + currentProfileNotifier.value = profile; + } + + void _handleProfileDataChange() { + onAccountDataSubscription = + matrix.client.onAccountData.stream.listen((event) { + if (event.type == TwakeInappEventTypes.uploadAvatarEvent) { + getCurrentProfile(); + } + }); } MatrixState get matrix => Matrix.of(context); @@ -130,6 +157,8 @@ class AppAdaptiveScaffoldBodyController extends State void initState() { activeRoomIdNotifier.value = widget.activeRoomId; resetLocationPathWithLoginToken(); + getCurrentProfile(); + _handleProfileDataChange(); super.initState(); } @@ -146,6 +175,12 @@ class AppAdaptiveScaffoldBodyController extends State _handleLogout(oldWidget); } + if (oldWidget.args != widget.args && + widget.args is LoggedInOtherAccountBodyArgs) { + getCurrentProfile(); + _handleProfileDataChange(); + } + if (oldWidget.args != widget.args && widget.args is SwitchActiveAccountBodyArgs) { _handleSwitchAccount(oldWidget); @@ -158,6 +193,8 @@ class AppAdaptiveScaffoldBodyController extends State activeRoomIdNotifier.dispose(); activeNavigationBarNotifier.dispose(); pageController.dispose(); + currentProfileNotifier.dispose(); + onAccountDataSubscription?.cancel(); super.dispose(); } @@ -172,5 +209,6 @@ class AppAdaptiveScaffoldBodyController extends State onPopInvoked: _onPopInvoked, onOpenSettings: _onOpenSettingsPage, adaptiveScaffoldBodyArgs: widget.args, + currentProfile: currentProfileNotifier, ); } diff --git a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart index 772c5d3b5c..122a873283 100644 --- a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart +++ b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart' hide WidgetBuilder; +import 'package:matrix/matrix.dart'; class AppAdaptiveScaffoldBodyView extends StatelessWidget { final List destinations; @@ -24,7 +25,7 @@ class AppAdaptiveScaffoldBodyView extends StatelessWidget { final OnPopInvoked onPopInvoked; final VoidCallback onOpenSettings; final AbsAppAdaptiveScaffoldBodyArgs? adaptiveScaffoldBodyArgs; - + final ValueNotifier currentProfile; final ValueNotifier activeRoomIdNotifier; static const ValueKey scaffoldWithNestedNavigationKey = @@ -46,6 +47,7 @@ class AppAdaptiveScaffoldBodyView extends StatelessWidget { required this.onPopInvoked, required this.onOpenSettings, this.adaptiveScaffoldBodyArgs, + required this.currentProfile, }) : super(key: key ?? scaffoldWithNestedNavigationKey); @override @@ -126,6 +128,7 @@ class AppAdaptiveScaffoldBodyView extends StatelessWidget { onOpenSettings: onOpenSettings, adaptiveScaffoldBodyArgs: adaptiveScaffoldBodyArgs, + currentProfile: currentProfile, ); }, ); @@ -152,6 +155,7 @@ class AppAdaptiveScaffoldBodyView extends StatelessWidget { bottomNavigationKey: bottomNavigationKey, onOpenSettings: onOpenSettings, adaptiveScaffoldBodyArgs: adaptiveScaffoldBodyArgs, + currentProfile: currentProfile, ), ), ], @@ -163,9 +167,23 @@ class AppAdaptiveScaffoldBodyView extends StatelessWidget { ); } + List getNavigationDestinationsForBottomNavigation( + BuildContext context, + ) { + return destinations.map((destination) { + return destination.getNavigationDestinationForBottomBar( + context, + currentProfile, + ); + }).toList(); + } + List getNavigationDestinations(BuildContext context) { return destinations.map((destination) { - return destination.getNavigationDestination(context); + return destination.getNavigationDestination( + context, + currentProfile, + ); }).toList(); } } @@ -180,6 +198,7 @@ class _ColumnPageView extends StatelessWidget { final ValueNotifier activeRoomIdNotifier; final VoidCallback onOpenSettings; final AbsAppAdaptiveScaffoldBodyArgs? adaptiveScaffoldBodyArgs; + final ValueNotifier currentProfile; const _ColumnPageView({ required this.activeNavigationBarNotifier, @@ -191,6 +210,7 @@ class _ColumnPageView extends StatelessWidget { required this.bottomNavigationKey, required this.onOpenSettings, required this.adaptiveScaffoldBodyArgs, + required this.currentProfile, }); @override @@ -248,20 +268,23 @@ class _ColumnPageView extends StatelessWidget { key: bottomNavigationKey, builder: (_) { return Container( + decoration: AppAdaptiveScaffoldBodyViewStyle.navBarBorder, height: ResponsiveUtils.heightBottomNavigation, - color: LinagoraSysColors.material().surface, padding: AppAdaptiveScaffoldBodyViewStyle.paddingBottomNavigation, child: ListView( - padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), children: [ - NavigationBar( - backgroundColor: LinagoraSysColors.material().surface, - elevation: AppAdaptiveScaffoldBodyViewStyle.elevation, - height: ResponsiveUtils.heightBottomNavigationBar, - selectedIndex: _getActiveBottomNavigationBarIndex(), - destinations: getNavigationDestinations(context), - onDestinationSelected: onDestinationSelected, + Theme( + data: Theme.of(context).copyWith( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + ), + child: BottomNavigationBar( + elevation: AppAdaptiveScaffoldBodyViewStyle.elevation, + currentIndex: _getActiveBottomNavigationBarIndex(), + onTap: onDestinationSelected, + items: getNavigationDestinationsForBottomBar(context), + ), ), ], ), @@ -274,7 +297,21 @@ class _ColumnPageView extends StatelessWidget { List getNavigationDestinations(BuildContext context) { return destinations.map((destination) { - return destination.getNavigationDestination(context); + return destination.getNavigationDestination( + context, + currentProfile, + ); + }).toList(); + } + + List getNavigationDestinationsForBottomBar( + BuildContext context, + ) { + return destinations.map((destination) { + return destination.getNavigationDestinationForBottomBar( + context, + currentProfile, + ); }).toList(); } diff --git a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view_style.dart b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view_style.dart index fd4f6d81db..f4fc11a025 100644 --- a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view_style.dart +++ b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view_style.dart @@ -1,4 +1,6 @@ import 'package:flutter/cupertino.dart'; +import 'package:linagora_design_flutter/colors/linagora_state_layer.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; class AppAdaptiveScaffoldBodyViewStyle { static const double elevation = 0.0; @@ -6,4 +8,15 @@ class AppAdaptiveScaffoldBodyViewStyle { static const EdgeInsets paddingBottomNavigation = EdgeInsets.only( top: 4, ); + + static BoxDecoration navBarBorder = BoxDecoration( + color: LinagoraSysColors.material().surface, + border: Border( + top: BorderSide( + color: LinagoraStateLayer( + LinagoraSysColors.material().surfaceTint, + ).opacityLayer3, + ), + ), + ); } diff --git a/lib/widgets/layouts/enum/adaptive_destinations_enum.dart b/lib/widgets/layouts/enum/adaptive_destinations_enum.dart index 7a93b27376..02175db127 100644 --- a/lib/widgets/layouts/enum/adaptive_destinations_enum.dart +++ b/lib/widgets/layouts/enum/adaptive_destinations_enum.dart @@ -1,34 +1,55 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; +import 'package:fluffychat/widgets/avatar/bottom_navigation_avatar.dart'; import 'package:fluffychat/widgets/twake_components/twake_navigation_icon/twake_navigation_icon.dart'; import 'package:fluffychat/widgets/unread_rooms_badge.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; +import 'package:matrix/matrix.dart'; enum AdaptiveDestinationEnum { contacts, rooms, settings; - NavigationDestination getNavigationDestination(BuildContext context) { + NavigationDestination getNavigationDestination( + BuildContext context, + ValueNotifier profile, + ) { switch (this) { case AdaptiveDestinationEnum.contacts: return NavigationDestination( - icon: const TwakeNavigationIcon( - icon: Icons.contacts_outlined, + icon: TwakeNavigationIcon( + color: LinagoraSysColors.material().onBackground, + icon: Icons.supervised_user_circle_outlined, ), label: L10n.of(context)!.contacts, + selectedIcon: const TwakeNavigationIcon( + icon: Icons.supervised_user_circle_outlined, + isSelected: true, + ), ); case AdaptiveDestinationEnum.rooms: return NavigationDestination( icon: UnreadRoomsBadge( + color: LinagoraSysColors.material().onBackground, + filter: (room) => !room.isSpace && !room.isStoryRoom, + ), + selectedIcon: UnreadRoomsBadge( filter: (room) => !room.isSpace && !room.isStoryRoom, + isSelected: true, ), label: L10n.of(context)!.chats, ); case AdaptiveDestinationEnum.settings: return NavigationDestination( - icon: const TwakeNavigationIcon( - icon: Icons.settings, + icon: TwakeNavigationIcon( + color: LinagoraSysColors.material().onBackground, + icon: Icons.settings_outlined, + ), + selectedIcon: const TwakeNavigationIcon( + icon: Icons.settings_outlined, + isSelected: true, ), label: L10n.of(context)!.settings, ); @@ -41,4 +62,53 @@ enum AdaptiveDestinationEnum { ); } } + + BottomNavigationBarItem getNavigationDestinationForBottomBar( + BuildContext context, + ValueNotifier profile, + ) { + switch (this) { + case AdaptiveDestinationEnum.contacts: + return BottomNavigationBarItem( + icon: TwakeNavigationIcon( + color: LinagoraSysColors.material().tertiary, + icon: Icons.supervised_user_circle_outlined, + ), + label: L10n.of(context)!.contacts, + activeIcon: const TwakeNavigationIcon( + icon: Icons.supervised_user_circle_outlined, + isSelected: true, + ), + ); + case AdaptiveDestinationEnum.rooms: + return BottomNavigationBarItem( + icon: UnreadRoomsBadge( + color: LinagoraSysColors.material().tertiary, + filter: (room) => !room.isSpace && !room.isStoryRoom, + ), + activeIcon: UnreadRoomsBadge( + filter: (room) => !room.isSpace && !room.isStoryRoom, + isSelected: true, + ), + label: L10n.of(context)!.chats, + ); + case AdaptiveDestinationEnum.settings: + return BottomNavigationBarItem( + icon: BottomNavigationAvatar( + profile: profile, + isSelected: false, + ), + activeIcon: + BottomNavigationAvatar(profile: profile, isSelected: true), + label: L10n.of(context)!.settings, + ); + default: + return BottomNavigationBarItem( + icon: UnreadRoomsBadge( + filter: (room) => !room.isSpace && !room.isStoryRoom, + ), + label: L10n.of(context)!.chats, + ); + } + } } diff --git a/lib/widgets/twake_components/twake_header.dart b/lib/widgets/twake_components/twake_header.dart index e7ea46d8f0..bd5e275067 100644 --- a/lib/widgets/twake_components/twake_header.dart +++ b/lib/widgets/twake_components/twake_header.dart @@ -1,7 +1,7 @@ +import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/presentation/enum/chat_list/chat_list_enum.dart'; import 'package:fluffychat/presentation/model/chat_list/chat_selection_actions.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/mixins/on_account_data_listen_mixin.dart'; import 'package:fluffychat/widgets/mixins/show_dialog_mixin.dart'; @@ -42,6 +42,8 @@ class _TwakeHeaderState extends State Profile(userId: ''), ); + static ResponsiveUtils responsive = getIt.get(); + void getCurrentProfile(Client client) async { currentProfileNotifier.value = Profile(userId: ''); final profile = await client.getProfileFromUserId( @@ -96,7 +98,9 @@ class _TwakeHeaderState extends State @override Widget build(BuildContext context) { return AppBar( - backgroundColor: LinagoraSysColors.material().onPrimary, + backgroundColor: responsive.isMobile(context) + ? LinagoraSysColors.material().background + : LinagoraSysColors.material().onPrimary, toolbarHeight: TwakeHeaderStyle.toolbarHeight, automaticallyImplyLeading: false, leadingWidth: TwakeHeaderStyle.leadingWidth, @@ -106,10 +110,15 @@ class _TwakeHeaderState extends State return Align( alignment: TwakeHeaderStyle.alignment, child: Row( + mainAxisAlignment: responsive.isMobile(context) + ? TwakeHeaderStyle.mobileTitleAllignement + : MainAxisAlignment.start, children: [ if (selectMode != SelectMode.select) ...[ Padding( - padding: TwakeHeaderStyle.paddingTitleHeader, + padding: responsive.isMobile(context) + ? EdgeInsets.zero + : TwakeHeaderStyle.paddingTitleHeader, child: Text( L10n.of(context)!.chats, style: Theme.of(context).textTheme.titleLarge?.copyWith( @@ -169,36 +178,6 @@ class _TwakeHeaderState extends State ), ), ], - Expanded( - flex: TwakeHeaderStyle.flexActions, - child: Padding( - padding: TwakeHeaderStyle.actionsPadding, - child: Align( - alignment: Alignment.centerRight, - child: PlatformInfos.isMobile - ? InkWell( - hoverColor: Colors.transparent, - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - onTap: widget.onClickAvatar, - child: ValueListenableBuilder( - valueListenable: currentProfileNotifier, - builder: (context, profile, _) { - return Avatar( - mxContent: profile.avatarUrl, - name: profile.displayName ?? - profile.userId.localpart, - size: TwakeHeaderStyle.avatarSize, - fontSize: - TwakeHeaderStyle.avatarFontSizeInAppBar, - ); - }, - ), - ) - : const SizedBox.shrink(), - ), - ), - ), ], ), ); diff --git a/lib/widgets/twake_components/twake_header_style.dart b/lib/widgets/twake_components/twake_header_style.dart index caf0da79f6..780bdd7f6a 100644 --- a/lib/widgets/twake_components/twake_header_style.dart +++ b/lib/widgets/twake_components/twake_header_style.dart @@ -20,6 +20,7 @@ class TwakeHeaderStyle { static bool isDesktop(BuildContext context) => responsive.isDesktop(context); static AlignmentGeometry alignment = AlignmentDirectional.centerStart; + static MainAxisAlignment mobileTitleAllignement = MainAxisAlignment.center; static const EdgeInsetsDirectional logoAppOfMultiplePadding = EdgeInsetsDirectional.all(16); diff --git a/lib/widgets/twake_components/twake_navigation_icon/twake_navigation_icon.dart b/lib/widgets/twake_components/twake_navigation_icon/twake_navigation_icon.dart index 8b0bc0582f..01f448e898 100644 --- a/lib/widgets/twake_components/twake_navigation_icon/twake_navigation_icon.dart +++ b/lib/widgets/twake_components/twake_navigation_icon/twake_navigation_icon.dart @@ -1,14 +1,19 @@ import 'package:fluffychat/widgets/twake_components/twake_navigation_icon/twake_navigation_icon_style.dart'; import 'package:flutter/material.dart'; +import 'package:linagora_design_flutter/linagora_design_flutter.dart'; class TwakeNavigationIcon extends StatelessWidget { final IconData icon; final int notificationCount; + final bool isSelected; + final Color? color; const TwakeNavigationIcon({ super.key, required this.icon, this.notificationCount = 0, + this.isSelected = false, + this.color, }); @override @@ -25,7 +30,9 @@ class TwakeNavigationIcon extends StatelessWidget { ), child: Icon( icon, - color: Theme.of(context).colorScheme.onSurfaceVariant, + color: isSelected + ? LinagoraSysColors.material().primary + : color ?? Theme.of(context).iconTheme.color, ), ); } diff --git a/lib/widgets/unread_rooms_badge.dart b/lib/widgets/unread_rooms_badge.dart index 4c691b9db8..752734f486 100644 --- a/lib/widgets/unread_rooms_badge.dart +++ b/lib/widgets/unread_rooms_badge.dart @@ -1,17 +1,19 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/widgets/twake_components/twake_navigation_icon/twake_navigation_icon.dart'; import 'package:flutter/material.dart'; - import 'package:matrix/matrix.dart'; - import 'matrix.dart'; class UnreadRoomsBadge extends StatelessWidget { final bool Function(Room) filter; + final bool isSelected; + final Color? color; const UnreadRoomsBadge({ super.key, required this.filter, + this.isSelected = false, + this.color, }); @override @@ -26,8 +28,10 @@ class UnreadRoomsBadge extends StatelessWidget { final unreadCount = getNotificationsCount(context); return TwakeNavigationIcon( - icon: Icons.chat, + icon: Icons.chat_bubble, notificationCount: unreadCount, + isSelected: isSelected, + color: color, ); }, ); From 9fb354a0a2a498606b90950a9e53591d810577e0 Mon Sep 17 00:00:00 2001 From: sherlock <43041967+sherlockvn@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:52:15 +0700 Subject: [PATCH 14/40] TW-1980: convert HEIC files to jpg file (#1988) --- .../extensions/send_file_extension.dart | 46 +++++++++++++++++-- .../manager/storage_directory_manager.dart | 11 +++++ pubspec.lock | 8 ++++ pubspec.yaml | 1 + 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/lib/presentation/extensions/send_file_extension.dart b/lib/presentation/extensions/send_file_extension.dart index 7b1ec0c8d4..0dd435204d 100644 --- a/lib/presentation/extensions/send_file_extension.dart +++ b/lib/presentation/extensions/send_file_extension.dart @@ -20,6 +20,7 @@ import 'package:fluffychat/utils/extension/mime_type_extension.dart'; import 'package:fluffychat/utils/manager/storage_directory_manager.dart'; import 'package:fluffychat/utils/manager/upload_manager/upload_state.dart'; import 'package:flutter/widgets.dart'; +import 'package:heif_converter/heif_converter.dart'; import 'package:image/image.dart' as img; import 'package:blurhash_dart/blurhash_dart.dart'; import 'package:flutter/foundation.dart'; @@ -49,7 +50,6 @@ extension SendFileExtension on Room { CancelToken? cancelToken, DateTime? sentDate, }) async { - FileInfo tempfileInfo = fileInfo; // Check media config of the server before sending the file. Stop if the // Media config is unreachable or the file is bigger than the given maxsize. try { @@ -84,16 +84,25 @@ extension SendFileExtension on Room { if (TwakeMimeTypeExtension.heicMimeTypes.contains(fileInfo.mimeType) && fileInfo is ImageFileInfo) { + try { + final oldFilePath = fileInfo.filePath; + fileInfo = await convertHeicToJpgImage(fileInfo); + File(oldFilePath).delete(); + } catch (e) { + Logs().e('sendFileEvent::Error while converting heic to jpg', e); + } + final formattedDateTime = DateTime.now().getFormattedCurrentDateTime(); final targetPath = await File('${tempDir.path}/$formattedDateTime${fileInfo.fileName}') .create(); + fileInfo = fileInfo as ImageFileInfo; await _generateThumbnail( fileInfo, targetPath: targetPath.path, uploadStreamController: uploadStreamController, ); - fileInfo = ImageFileInfo( + thumbnail = ImageFileInfo( fileInfo.fileName, targetPath.path, await targetPath.length(), @@ -224,7 +233,7 @@ extension SendFileExtension on Room { ); } - tempfileInfo = FileInfo( + fileInfo = FileInfo( fileInfo.fileName, tempEncryptedFile.path, fileInfo.fileSize, @@ -268,7 +277,7 @@ extension SendFileExtension on Room { try { final mediaApi = getIt.get(); final response = await mediaApi.uploadFileMobile( - fileInfo: tempfileInfo, + fileInfo: fileInfo, cancelToken: cancelToken, onSendProgress: (receive, total) { if (uploadStreamController?.isClosed == true) return; @@ -418,6 +427,35 @@ extension SendFileExtension on Room { return eventId; } + Future convertHeicToJpgImage(ImageFileInfo fileInfo) async { + final convertedFilePath = + StorageDirectoryManager.instance.convertFileExtension( + fileInfo.filePath, + 'jpg', + ); + final newPath = await HeifConverter.convert( + fileInfo.filePath, + output: convertedFilePath, + ); + Logs().d('sendFileEvent::Heic converted to jpg', newPath); + if (newPath != null) { + final newConvertedFile = File(convertedFilePath); + fileInfo = ImageFileInfo( + newConvertedFile.path.split("/").last, + newConvertedFile.path, + await newConvertedFile.length(), + width: fileInfo.width, + height: fileInfo.height, + ); + } else { + Logs().e( + 'sendFileEvent::Error while converting heic to jpg:newPath is null', + ); + throw Exception('sendFileEvent::Error while converting heic to jpg'); + } + return fileInfo; + } + Future _copyFileInMemToAppDownloadsFolder({ required String sendingEventId, required String eventId, diff --git a/lib/utils/manager/storage_directory_manager.dart b/lib/utils/manager/storage_directory_manager.dart index a45902f8f5..d6d30fe836 100644 --- a/lib/utils/manager/storage_directory_manager.dart +++ b/lib/utils/manager/storage_directory_manager.dart @@ -76,4 +76,15 @@ class StorageDirectoryManager { await StorageDirectoryManager.instance.getFileStoreDirectory(); return '$fileStoreDirectory/$eventId/decrypted-$fileName'; } + + String convertFileExtension(String filename, String newExtension) { + final lastDotIndex = filename.lastIndexOf('.'); + + if (lastDotIndex == -1) { + return '$filename.$newExtension'; + } else { + final nameWithoutExtension = filename.substring(0, lastDotIndex); + return '$nameWithoutExtension.$newExtension'; + } + } } diff --git a/pubspec.lock b/pubspec.lock index ff646d90fe..1260adb0d6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1379,6 +1379,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.9" + heif_converter: + dependency: "direct main" + description: + name: heif_converter + sha256: "63ffc2a72026942de3fcb61450da197100759936085c8279aef35b995b3c1bb3" + url: "https://pub.dev" + source: hosted + version: "1.0.0" highlight: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cc22794bf7..0af0e24111 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -185,6 +185,7 @@ dependencies: gal: 2.3.0 auto_size_text: 3.0.0 flutter_avif: 2.4.1 + heif_converter: 1.0.0 dev_dependencies: build_runner: ^2.3.3 From f49e26d9b7c6f608ae490b287fe5b14bb1f617df Mon Sep 17 00:00:00 2001 From: Huy Quang Nguyen Date: Tue, 20 Aug 2024 20:37:32 +0700 Subject: [PATCH 15/40] TW-1972: Update logic for cancel upload files with captions (#1990) * TW-1972: Update logic for cancel upload files with captions * TW-1972: Update UI/UX for cancel upload * TW-1972: Add adr for this ticket --- .../0024-cancel-upload-file-with-caption.md | 40 +++++ .../sending_image_info_widget.dart | 24 ++- .../chat/events/message_upload_content.dart | 3 + .../events/message_video_upload_content.dart | 14 +- .../chat/events/sending_video_widget.dart | 31 ++-- .../models/upload_file_info.dart | 4 + .../upload_manager/upload_manager.dart | 148 ++++++++++++------ 7 files changed, 196 insertions(+), 68 deletions(-) create mode 100644 docs/adr/0024-cancel-upload-file-with-caption.md diff --git a/docs/adr/0024-cancel-upload-file-with-caption.md b/docs/adr/0024-cancel-upload-file-with-caption.md new file mode 100644 index 0000000000..9110ce3a77 --- /dev/null +++ b/docs/adr/0024-cancel-upload-file-with-caption.md @@ -0,0 +1,40 @@ +# 24. Cancel upload file with caption + +Date: 2024-08-19 + +## Status + +Accepted + +- Issue: [#1972](https://github.com/linagora/twake-on-matrix/issues/1972) + +## Context + +- The caption is sent anyway, there's also a weird upload preview that disappears after you reload the page. + +## Decision +1. **Update logic for Cancelling Upload files with captions** + - If the user uploads files with captions (on Web and mobile), update the logic for this case: + - In the last element of the file upload list, add information about the caption. + - When the user cancels a file upload, check if the file has associated caption information, and remove both the file and the caption. + - If the file doesn’t have caption information, remove only the file. + - Example: + - User upload 3 files: [ file1, file2, file3]. + - User add a caption for upload files. => [ file1, file2, file3 (caption)]. + - And add an information of caption to last file. `file3` has an information. + - If user cancel upload cancel randomly a file. + - Check if the caption information is not null. + - If the caption information is not null => remove both the file and the caption. + - If the caption information is null => remove only the file. + - If the user cancels the upload of `file1`, only `file1` is removed. + - If the user cancels the upload of `file2`, only `file2` is removed.. + - If the user cancels the upload of `file3`, both `file3` and the caption are removed. + +2. **Update UI/UX for Cancelling Upload** + - Display a cancel upload icon during file upload and while receiving upload progress. + - If upload progress cannot be received, only display a loading icon and disable the cancel upload option. + +## Consequences + +- Cancel upload of media, the caption isn't sent either. +- There's no weird preview if I canceled upload \ No newline at end of file diff --git a/lib/pages/chat/events/images_builder/sending_image_info_widget.dart b/lib/pages/chat/events/images_builder/sending_image_info_widget.dart index 3e55151aa4..874b38a202 100644 --- a/lib/pages/chat/events/images_builder/sending_image_info_widget.dart +++ b/lib/pages/chat/events/images_builder/sending_image_info_widget.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/pages/image_viewer/image_viewer.dart'; +import 'package:fluffychat/presentation/model/chat/upload_file_ui_state.dart'; import 'package:fluffychat/presentation/model/file/display_image_info.dart'; import 'package:fluffychat/utils/extension/build_context_extension.dart'; import 'package:fluffychat/utils/interactive_viewer_gallery.dart'; @@ -87,14 +88,23 @@ class _SendingImageInfoWidgetState extends State strokeWidth: 2, color: LinagoraRefColors.material().primary[100], ), - InkWell( - child: Icon( - Icons.close, - color: LinagoraRefColors.material().primary[100], - ), - onTap: () { - uploadManager.cancelUpload(widget.event); + ValueListenableBuilder( + valueListenable: uploadFileStateNotifier, + builder: (context, state, child) { + if (state is UploadFileSuccessUIState) { + return child!; + } + return InkWell( + child: Icon( + Icons.close, + color: LinagoraRefColors.material().primary[100], + ), + onTap: () { + uploadManager.cancelUpload(widget.event); + }, + ); }, + child: const SizedBox.shrink(), ), ], ], diff --git a/lib/pages/chat/events/message_upload_content.dart b/lib/pages/chat/events/message_upload_content.dart index e758799624..54e4161c00 100644 --- a/lib/pages/chat/events/message_upload_content.dart +++ b/lib/pages/chat/events/message_upload_content.dart @@ -112,6 +112,9 @@ class _MessageUploadingContentState extends State ), InkWell( onTap: () { + if (uploadFileState is UploadFileSuccessUIState) { + return; + } uploadManager.cancelUpload(event); }, mouseCursor: SystemMouseCursors.click, diff --git a/lib/pages/chat/events/message_video_upload_content.dart b/lib/pages/chat/events/message_video_upload_content.dart index e50bf0f137..0c6fdaa434 100644 --- a/lib/pages/chat/events/message_video_upload_content.dart +++ b/lib/pages/chat/events/message_video_upload_content.dart @@ -49,6 +49,9 @@ class _MessageVideoUploadContentWebState width: widget.width, height: widget.height, onVideoTapped: () { + if (uploadState is UploadFileSuccessUIState) { + return; + } uploadManager.cancelUpload(event); }, centerWidget: Stack( @@ -58,10 +61,13 @@ class _MessageVideoUploadContentWebState width: MessageContentStyle.videoCenterButtonSize, height: MessageContentStyle.videoCenterButtonSize, ), - const CenterVideoButton( - icon: Icons.close, - iconSize: MessageContentStyle.cancelButtonSize, - ), + if (uploadState is UploadFileSuccessUIState) ...[ + const SizedBox.shrink(), + ] else + const CenterVideoButton( + icon: Icons.close, + iconSize: MessageContentStyle.cancelButtonSize, + ), SizedBox( width: MessageContentStyle.iconInsideVideoButtonSize, height: MessageContentStyle.iconInsideVideoButtonSize, diff --git a/lib/pages/chat/events/sending_video_widget.dart b/lib/pages/chat/events/sending_video_widget.dart index dac779a2e5..11efd6a969 100644 --- a/lib/pages/chat/events/sending_video_widget.dart +++ b/lib/pages/chat/events/sending_video_widget.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/presentation/mixins/play_video_action_mixin.dart'; +import 'package:fluffychat/presentation/model/chat/upload_file_ui_state.dart'; import 'package:fluffychat/presentation/model/file/display_image_info.dart'; import 'package:fluffychat/widgets/mixins/upload_file_mixin.dart'; import 'package:flutter/material.dart'; @@ -61,18 +62,26 @@ class _SendingVideoWidgetState extends State _PlayVideoButton( event: widget.event, ), - InkWell( - onTap: () { - uploadManager.cancelUpload(widget.event); + ValueListenableBuilder( + valueListenable: uploadFileStateNotifier, + builder: (context, state, child) { + return InkWell( + onTap: () { + if (state is UploadFileSuccessUIState) { + return; + } + uploadManager.cancelUpload(widget.event); + }, + child: SizedBox( + width: MessageContentStyle.videoCenterButtonSize, + height: MessageContentStyle.videoCenterButtonSize, + child: CircularProgressIndicator( + strokeWidth: MessageContentStyle.strokeVideoWidth, + color: LinagoraRefColors.material().primary[100], + ), + ), + ); }, - child: SizedBox( - width: MessageContentStyle.videoCenterButtonSize, - height: MessageContentStyle.videoCenterButtonSize, - child: CircularProgressIndicator( - strokeWidth: MessageContentStyle.strokeVideoWidth, - color: LinagoraRefColors.material().primary[100], - ), - ), ), ], ), diff --git a/lib/utils/manager/upload_manager/models/upload_file_info.dart b/lib/utils/manager/upload_manager/models/upload_file_info.dart index 7ca79f16a2..f8049a15a3 100644 --- a/lib/utils/manager/upload_manager/models/upload_file_info.dart +++ b/lib/utils/manager/upload_manager/models/upload_file_info.dart @@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/utils/manager/upload_manager/models/upload_caption_info.dart'; import 'package:fluffychat/utils/manager/upload_manager/models/upload_info.dart'; class UploadFileInfo extends UploadInfo { @@ -10,6 +11,7 @@ class UploadFileInfo extends UploadInfo { final Stream> uploadStream; final CancelToken cancelToken; final DateTime createdAt; + final UploadCaptionInfo? captionInfo; UploadFileInfo({ required super.txid, @@ -17,6 +19,7 @@ class UploadFileInfo extends UploadInfo { required this.uploadStateStreamController, required this.uploadStream, required this.cancelToken, + this.captionInfo, }); @override @@ -26,5 +29,6 @@ class UploadFileInfo extends UploadInfo { uploadStream, cancelToken, createdAt, + captionInfo, ]; } diff --git a/lib/utils/manager/upload_manager/upload_manager.dart b/lib/utils/manager/upload_manager/upload_manager.dart index 6e17bf2eab..2bab843576 100644 --- a/lib/utils/manager/upload_manager/upload_manager.dart +++ b/lib/utils/manager/upload_manager/upload_manager.dart @@ -34,11 +34,33 @@ class UploadManager { Future cancelUpload(Event event) async { final cancelToken = _eventIdMapUploadFileInfo[event.eventId]?.cancelToken; + final captionInfo = _eventIdMapUploadFileInfo[event.eventId]?.captionInfo; if (cancelToken != null) { Logs().d('Remove eventid: ${event.eventId}'); - cancelToken.cancel(); _clearFileTask(event.eventId); event.remove(); + cancelToken.cancel(); + if (captionInfo != null) { + _handleCancelCaptionEvent( + txid: captionInfo.txid, + room: event.room, + ); + } + } + } + + Future _handleCancelCaptionEvent({ + required String txid, + required Room room, + }) async { + try { + _clearCaptionTask(txid); + final captionEvent = await room.getEventById(txid); + captionEvent?.remove(); + } catch (e) { + Logs().e( + 'UploadManager::_handleCancelCaptionEvent(): $e', + ); } } @@ -50,7 +72,7 @@ class UploadManager { .close(); } catch (e) { Logs().e( - 'UploadManager::_clear(): $e', + 'UploadManager::_clear(): Error $e', ); } finally { _eventIdMapUploadFileInfo.remove(eventId); @@ -80,6 +102,8 @@ class UploadManager { void _initUploadFileInfo({ required String txid, + required Room room, + String? captionInfo, }) { final uploadController = StreamController>(); @@ -89,6 +113,12 @@ class UploadManager { uploadStream: uploadController.stream.asBroadcastStream(), cancelToken: CancelToken(), createdAt: DateTime.now(), + captionInfo: captionInfo != null && captionInfo.isNotEmpty + ? UploadCaptionInfo( + txid: room.client.generateUniqueTransactionId(), + caption: captionInfo, + ) + : null, ); } @@ -105,35 +135,40 @@ class UploadManager { entities: entities, ); - for (final txid in txids.keys) { - final fakeSendingFileInfo = txids[txid]; + for (final txid in txids.entries) { + final txidKey = txid.key; + final fakeSendingFileInfo = txids[txidKey]; if (fakeSendingFileInfo == null) { continue; } - Logs().d('UploadManager::uploadMediaMobile(): txid: $txid'); + Logs().d('UploadManager::uploadMediaMobile(): txid: $txidKey'); - _initUploadFileInfo(txid: txid); + _initUploadFileInfo( + txid: txidKey, + room: room, + captionInfo: txidKey == txids.keys.last ? caption : null, + ); - final sentDate = _eventIdMapUploadFileInfo[txid]?.createdAt; + final sentDate = _eventIdMapUploadFileInfo[txidKey]?.createdAt; final fakeImageEvent = await room.sendFakeImagePickerFileEvent( fakeSendingFileInfo.fileInfo, - txid: txid, + txid: txidKey, messageType: fakeSendingFileInfo.messageType, sentDate: sentDate, ); final streamController = - _eventIdMapUploadFileInfo[txid]?.uploadStateStreamController; + _eventIdMapUploadFileInfo[txidKey]?.uploadStateStreamController; - final cancelToken = _eventIdMapUploadFileInfo[txid]?.cancelToken; + final cancelToken = _eventIdMapUploadFileInfo[txidKey]?.cancelToken; if (streamController == null || cancelToken == null) { Logs().e( 'DownloadManager::download(): streamController or cancelToken is null', ); - _eventIdMapUploadFileInfo[txid]?.uploadStateStreamController.add( + _eventIdMapUploadFileInfo[txidKey]?.uploadStateStreamController.add( Left( UploadFileFailedState( exception: Exception( @@ -152,7 +187,7 @@ class UploadManager { ); _addFileTaskToWorkerQueueMobile( - txid: txid, + txid: txidKey, fakeImageEvent: fakeImageEvent, room: room, fileInfo: fakeSendingFileInfo.fileInfo, @@ -161,12 +196,16 @@ class UploadManager { sentDate: sentDate, shrinkImageMaxDimension: _shrinkImageMaxDimension, ); - } - if (caption != null && caption.isNotEmpty) { - _addCaptionTaskToWorkerQueue( - room: room, - caption: caption, - ); + + if (_eventIdMapUploadFileInfo[txidKey]?.captionInfo != null) { + _addCaptionTaskToWorkerQueue( + room: room, + messageTxid: + _eventIdMapUploadFileInfo[txidKey]?.captionInfo?.txid ?? '', + caption: + _eventIdMapUploadFileInfo[txidKey]?.captionInfo?.caption ?? '', + ); + } } } @@ -176,14 +215,20 @@ class UploadManager { Map? thumbnails, String? caption, }) async { - for (final MatrixFile matrixFile in files) { + for (final matrixFile in files.asMap().entries) { final txid = room.client.generateUniqueTransactionId(); + final fileIndex = matrixFile.key; + final fileInfo = matrixFile.value; - _initUploadFileInfo(txid: txid); + _initUploadFileInfo( + txid: txid, + room: room, + captionInfo: fileIndex == files.length - 1 ? caption : null, + ); - room.sendingFilePlaceholders[txid] = matrixFile; + room.sendingFilePlaceholders[txid] = fileInfo; final fakeFileEvent = await room.sendFakeFileEvent( - matrixFile, + fileInfo, txid: txid, ); @@ -220,19 +265,19 @@ class UploadManager { txid: txid, fakeImageEvent: fakeFileEvent, room: room, - matrixFile: matrixFile, + matrixFile: fileInfo, streamController: streamController, cancelToken: cancelToken, - thumbnail: thumbnails?[matrixFile], + thumbnail: thumbnails?[fileInfo], sentDate: sentDate, ); - } - - if (caption != null && caption.isNotEmpty) { - _addCaptionTaskToWorkerQueue( - room: room, - caption: caption, - ); + if (_eventIdMapUploadFileInfo[txid]?.captionInfo != null) { + _addCaptionTaskToWorkerQueue( + room: room, + messageTxid: _eventIdMapUploadFileInfo[txid]?.captionInfo?.txid ?? '', + caption: _eventIdMapUploadFileInfo[txid]?.captionInfo?.caption ?? '', + ); + } } } @@ -241,21 +286,28 @@ class UploadManager { required List fileInfos, String? caption, }) async { - for (final fileInfo in fileInfos) { + for (final fileInfo in fileInfos.asMap().entries) { + final fileIndex = fileInfo.key; + final fileValue = fileInfo.value; + final txid = room.storePlaceholderFileInMem( - fileInfo: fileInfo, + fileInfo: fileValue, ); Logs().d('UploadManager::uploadFileMobile(): txid: $txid'); - _initUploadFileInfo(txid: txid); + _initUploadFileInfo( + txid: txid, + room: room, + captionInfo: fileIndex == fileInfos.length - 1 ? caption : null, + ); final sentDate = _eventIdMapUploadFileInfo[txid]?.createdAt; final fakeEvent = await room.sendFakeImagePickerFileEvent( - fileInfo, + fileValue, txid: txid, - messageType: fileInfo.msgType, + messageType: fileValue.msgType, sentDate: sentDate, ); @@ -290,26 +342,30 @@ class UploadManager { txid: txid, fakeImageEvent: fakeEvent, room: room, - fileInfo: fileInfo, + fileInfo: fileValue, streamController: streamController, cancelToken: cancelToken, sentDate: sentDate, ); - } - if (caption != null && caption.isNotEmpty) { - _addCaptionTaskToWorkerQueue( - room: room, - caption: caption, - ); + if (_eventIdMapUploadFileInfo[txid]?.captionInfo != null) { + _addCaptionTaskToWorkerQueue( + room: room, + messageTxid: _eventIdMapUploadFileInfo[txid]?.captionInfo?.txid ?? '', + caption: _eventIdMapUploadFileInfo[txid]?.captionInfo?.caption ?? '', + ); + } } } Future _addCaptionTaskToWorkerQueue({ required Room room, - required String caption, + String? messageTxid, + String? caption, }) async { - final messageTxid = room.client.generateUniqueTransactionId(); - + if ((messageTxid == null && messageTxid!.isEmpty) || + (caption == null && caption!.isEmpty)) { + return; + } final messageContent = room.getEventContentFromMsgText(message: caption); _initUploadCaptionInfo( From 192228e547762f2d3c74ddc8e733b1c058cfc599 Mon Sep 17 00:00:00 2001 From: Huy Quang Nguyen Date: Tue, 20 Aug 2024 20:38:03 +0700 Subject: [PATCH 16/40] TW-1968: Break UI when search with keyword (#1993) * TW-1968: Fix break UI when search with keyword '\' * TW-1968: Write unit test for this case --- lib/utils/string_extension.dart | 5 +- test/utils/string_extension_test.dart | 222 ++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) diff --git a/lib/utils/string_extension.dart b/lib/utils/string_extension.dart index c688a6d20e..02d326a305 100644 --- a/lib/utils/string_extension.dart +++ b/lib/utils/string_extension.dart @@ -251,10 +251,13 @@ extension StringCasingExtension on String { ]; } + // Escape special characters in the highlightText + final escapedHighlightText = RegExp.escape(highlightText); + // Split the text into parts by the search word and create a TextSpan for // each part. The search word is not case sensitive. final List spans = splitMapJoinToList( - RegExp(highlightText, caseSensitive: false), + RegExp(escapedHighlightText, caseSensitive: false), onMatch: (Match match) { return TextSpan( text: match.group(0), diff --git a/test/utils/string_extension_test.dart b/test/utils/string_extension_test.dart index d37b6f8164..96fcbda0c9 100644 --- a/test/utils/string_extension_test.dart +++ b/test/utils/string_extension_test.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/utils/string_extension.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -377,4 +378,225 @@ void main() { expect(''.extractInnerText(), isEmpty); }); }); + + group('buildHighlightTextSpans tests', () { + test( + 'buildHighlightTextSpans handles special * and \\ characters in highlightText', + () { + const text = + 'This is a test string with special characters like * and \\.'; + const highlightText = '* and \\'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '* and \\', style: TextStyle(color: Colors.red)), + const TextSpan(text: '.', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + + test( + 'buildHighlightTextSpans handles special \\ characters in highlightText', + () { + const text = 'This is a test string with special characters like 123\\.'; + const highlightText = '123'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '123', style: TextStyle(color: Colors.red)), + const TextSpan(text: '\\.', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + + test('buildHighlightTextSpans handles special characters in highlightText', + () { + const text = 'This is a test string with special characters like 123.'; + const highlightText = '123'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '123', style: TextStyle(color: Colors.red)), + const TextSpan(text: '.', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + + test('buildHighlightTextSpans handles special characters in highlightText', + () { + const text = 'This is a test string with special characters like 123.'; + const highlightText = '123.'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '123.', style: TextStyle(color: Colors.red)), + const TextSpan(text: '', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + + test('buildHighlightTextSpans handles special characters in highlightText', + () { + const text = 'This is a test string with special characters like 123\\'; + const highlightText = '123\\'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '123\\', style: TextStyle(color: Colors.red)), + const TextSpan(text: '', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + + test('buildHighlightTextSpans handles special characters in highlightText', + () { + const text = 'This is a test string with special characters like 123@@++'; + const highlightText = '123@@++'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '123@@++', style: TextStyle(color: Colors.red)), + const TextSpan(text: '', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + + test('buildHighlightTextSpans handles special characters in highlightText', + () { + const text = 'This is a test string with special characters like .123'; + const highlightText = '.123'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '.123', style: TextStyle(color: Colors.red)), + const TextSpan(text: '', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + + test('buildHighlightTextSpans handles special characters in highlightText', + () { + const text = 'This is a test string with special characters like \\123'; + const highlightText = '\\123'; + final expectedSpans = [ + const TextSpan( + text: 'This is a test string with special characters like ', + style: TextStyle(color: Colors.black), + ), + const TextSpan(text: '\\123', style: TextStyle(color: Colors.red)), + const TextSpan(text: '', style: TextStyle(color: Colors.black)), + ]; + + final result = text.buildHighlightTextSpans( + highlightText, + style: const TextStyle(color: Colors.black), + highlightStyle: const TextStyle(color: Colors.red), + ); + + expect(result.length, expectedSpans.length); + + for (int i = 0; i < result.length; i++) { + expect(result[i].text, expectedSpans[i].text); + expect(result[i].style, expectedSpans[i].style); + } + }); + }); } From 7ed3c82471487259590599b43dd5c595e619d5a0 Mon Sep 17 00:00:00 2001 From: Huy Quang Nguyen Date: Tue, 20 Aug 2024 21:00:53 +0700 Subject: [PATCH 17/40] TW-1994: Fix load more for chat search (#1997) --- .../search/server_search_controller.dart | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/pages/search/server_search_controller.dart b/lib/pages/search/server_search_controller.dart index 938426a5f3..db490e0134 100644 --- a/lib/pages/search/server_search_controller.dart +++ b/lib/pages/search/server_search_controller.dart @@ -89,7 +89,25 @@ class ServerSearchController with SearchDebouncerMixin { if (success is ServerSearchChatSuccess) { updateNextBatch(success.nextBatch); if (success.results?.isEmpty == true) { - searchResultsNotifier.value = PresentationServerSideEmptySearch(); + if (isLoadingMoreNotifier.value) { + searchResultsNotifier.value = PresentationServerSideSearch( + searchResults: [ + if (searchResultsNotifier.value + is PresentationServerSideSearch) + ...(searchResultsNotifier.value + as PresentationServerSideSearch) + .searchResults, + ...success.results ?? [], + ] + .where( + (result) => + result.isDisplayableResult(context: currentContext), + ) + .toList(), + ); + } else { + searchResultsNotifier.value = PresentationServerSideEmptySearch(); + } } else { searchResultsNotifier.value = PresentationServerSideSearch( searchResults: [ From a0116d0e9d9e2237323bfcc5b7578cfd2c641bc9 Mon Sep 17 00:00:00 2001 From: sherlock <43041967+sherlockvn@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:41:43 +0700 Subject: [PATCH 18/40] TW-1542: make message highlight brighter (#1998) --- lib/config/themes.dart | 1 + lib/pages/chat/chat.dart | 2 +- lib/pages/chat/chat_event_list.dart | 3 +-- lib/pages/chat/chat_pinned_events/pinned_events_view.dart | 1 + lib/pages/chat/events/message/message.dart | 1 + lib/pages/chat/events/message/message_style.dart | 3 +++ 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/config/themes.dart b/lib/config/themes.dart index ea3c76febb..22cd1a9668 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -162,6 +162,7 @@ abstract class TwakeThemes { ), ), ), + highlightColor: LinagoraRefColors.material().tertiary[80], colorScheme: ColorScheme.fromSeed( seedColor: seed ?? AppConfig.colorSchemeSeed, brightness: brightness, diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index bd454ab015..e8ff7bce34 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -954,7 +954,7 @@ class ChatController extends State return eventIndex + addedHeadItemsInChat; } - Future scrollToEventId(String eventId, {bool highlight = false}) async { + Future scrollToEventId(String eventId, {bool highlight = true}) async { final eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); if (eventIndex == -1) { loadTimelineFuture = _getTimeline(eventContextId: eventId).onError( diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 6592c74fcd..c245c1b612 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -9,7 +9,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:inview_notifier_list/inview_notifier_list.dart'; -import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; @@ -150,7 +149,7 @@ class ChatEventList extends StatelessWidget { key: ValueKey(event.eventId), index: index, controller: controller.scrollController, - highlightColor: LinagoraRefColors.material().primary[99], + highlightColor: Theme.of(context).highlightColor, child: event.isVisibleInGui ? Message( key: ValueKey(event.eventId), diff --git a/lib/pages/chat/chat_pinned_events/pinned_events_view.dart b/lib/pages/chat/chat_pinned_events/pinned_events_view.dart index 1322fe5aa0..39bb3b31a7 100644 --- a/lib/pages/chat/chat_pinned_events/pinned_events_view.dart +++ b/lib/pages/chat/chat_pinned_events/pinned_events_view.dart @@ -188,6 +188,7 @@ class _PinnedEventsIndicator extends StatelessWidget { currentEvent.eventId, ), index: index, + highlightColor: Theme.of(context).highlightColor, controller: scrollController, child: Container( width: PinnedEventsStyle.maxWidthIndicator, diff --git a/lib/pages/chat/events/message/message.dart b/lib/pages/chat/events/message/message.dart index 6790e63dc8..1a471eb29e 100644 --- a/lib/pages/chat/events/message/message.dart +++ b/lib/pages/chat/events/message/message.dart @@ -258,6 +258,7 @@ class _MessageState extends State { constraints: BoxConstraints( maxWidth: ChatViewBodyStyle.chatScreenMaxWidth, ), + padding: MessageStyle.paddingMessage, alignment: Alignment.bottomCenter, child: SwipeableMessage( event: widget.event, diff --git a/lib/pages/chat/events/message/message_style.dart b/lib/pages/chat/events/message/message_style.dart index 0ebce83e8c..3743ef5774 100644 --- a/lib/pages/chat/events/message/message_style.dart +++ b/lib/pages/chat/events/message/message_style.dart @@ -92,6 +92,9 @@ class MessageStyle { bottom: 4.0, ); + static EdgeInsets get paddingMessage => + const EdgeInsets.symmetric(vertical: 2.0); + static EdgeInsets get paddingTimestamp => const EdgeInsets.only( left: 8.0, right: 4.0, From 6973f32c764161871463958e3998b37528d4f3ac Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Thu, 22 Aug 2024 10:16:30 +0700 Subject: [PATCH 19/40] TW-2002: Remove deeplink `matrix.to` on android --- android/app/src/main/AndroidManifest.xml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5dbba0bcfc..58016494b2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -61,26 +61,12 @@ - - - - - - - - - - - - From ff02b03216873adbcf80846506c529b4845cfaca Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Tue, 27 Aug 2024 10:28:13 +0700 Subject: [PATCH 20/40] TW-2002: Upgrade `flutter_local_notifications` to fix USE_FULL_SCREEN_INTENT permission --- pubspec.lock | 12 ++++++------ pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 1260adb0d6..c893e8b643 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1048,26 +1048,26 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef" + sha256: c500d5d9e7e553f06b61877ca6b9c8b92c570a4c8db371038702e8ce57f8a50f url: "https://pub.dev" source: hosted - version: "17.1.2" + version: "17.2.2" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af url: "https://pub.dev" source: hosted - version: "4.0.0+1" + version: "4.0.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.2.0" flutter_localizations: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 0af0e24111..fa41dbcbe5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -86,7 +86,7 @@ dependencies: flutter_blurhash: ^0.8.2 flutter_cache_manager: ^3.3.0 flutter_foreground_task: ^3.10.0 - flutter_local_notifications: ^17.1.2 + flutter_local_notifications: 17.2.2 flutter_localizations: sdk: flutter flutter_map: ^4.0.0 From 81ea9319f08a5e01816f81385a2c9c16e8a51fcf Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 6 Aug 2024 15:53:18 +0700 Subject: [PATCH 21/40] TW-1844: fix distorted images when using camera in android phone --- .../usecase/send_images_interactor.dart | 52 ------------------- .../extensions/send_file_extension.dart | 7 ++- .../mixins/media_picker_mixin.dart | 11 +++- 3 files changed, 15 insertions(+), 55 deletions(-) delete mode 100644 lib/domain/usecase/send_images_interactor.dart diff --git a/lib/domain/usecase/send_images_interactor.dart b/lib/domain/usecase/send_images_interactor.dart deleted file mode 100644 index 14a043e779..0000000000 --- a/lib/domain/usecase/send_images_interactor.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:fluffychat/presentation/extensions/send_file_extension.dart'; -import 'package:fluffychat/presentation/model/file/file_asset_entity.dart'; -import 'package:matrix/matrix.dart'; - -class SendMediaInteractor { - Future execute({ - required Room room, - required List entities, - String? caption, - }) async { - try { - final txIdMapToFileInfo = await room.sendPlaceholdersForImagePickerFiles( - entities: entities, - ); - - String? messageID; - Map? msgEventContent; - if (caption != null && caption.isNotEmpty) { - messageID = room.client.generateUniqueTransactionId(); - msgEventContent = room.getEventContentFromMsgText(message: caption); - await room.sendFakeMessage( - content: msgEventContent, - messageId: messageID, - ); - } - - for (final txId in txIdMapToFileInfo.keys) { - final fakeSendingFileInfo = txIdMapToFileInfo[txId]; - if (fakeSendingFileInfo == null) { - continue; - } - - await room.sendFileEventMobile( - fakeSendingFileInfo.fileInfo, - msgType: fakeSendingFileInfo.messageType, - fakeImageEvent: fakeSendingFileInfo.fakeImageEvent, - shrinkImageMaxDimension: 1600, - txid: txId, - ); - } - if (messageID != null && msgEventContent != null) { - await room.sendMessageContent( - EventTypes.Message, - msgEventContent, - txid: messageID, - ); - } - } catch (error) { - Logs().d("SendImagesInteractor: execute(): $error"); - } - } -} diff --git a/lib/presentation/extensions/send_file_extension.dart b/lib/presentation/extensions/send_file_extension.dart index 0dd435204d..68be47220b 100644 --- a/lib/presentation/extensions/send_file_extension.dart +++ b/lib/presentation/extensions/send_file_extension.dart @@ -174,7 +174,10 @@ extension SendFileExtension on Room { txid, uploadStreamController: uploadStreamController, ); - if (fileInfo.width == null || fileInfo.height == null) { + if (fileInfo.width == null || + fileInfo.height == null || + fileInfo.width == 0 || + fileInfo.height == 0) { fileInfo = VideoFileInfo( fileInfo.fileName, fileInfo.filePath, @@ -619,7 +622,7 @@ extension SendFileExtension on Room { final size = await result.length(); var width = originalFile.width; var height = originalFile.height; - if (width == null || height == null) { + if (width == null || height == null || width == 0 || height == 0) { final imageDimension = await runBenchmarked( '_calculateImageDimension', () => _calculateImageDimension(result.path), diff --git a/lib/presentation/mixins/media_picker_mixin.dart b/lib/presentation/mixins/media_picker_mixin.dart index 1fc9e056ec..6854bc98c5 100644 --- a/lib/presentation/mixins/media_picker_mixin.dart +++ b/lib/presentation/mixins/media_picker_mixin.dart @@ -360,12 +360,21 @@ mixin MediaPickerMixin on CommonMediaPickerMixin { OnCameraPicked? onCameraPicked, bool onlyImage = false, }) async { - final assetEntity = + var assetEntity = await pickMediaFromCameraAction(context: context, onlyImage: onlyImage); Logs().d( "MediaPickerMixin::_pickFromCameraAction(): assetEntity - $assetEntity", ); if (assetEntity != null) { + // TODO: TW-1844: Remove this when the issue https://github.com/fluttercandies/flutter_wechat_camera_picker/issues/266 + if (PlatformInfos.isAndroid) { + assetEntity = AssetEntity( + id: assetEntity.id, + width: 0, + height: 0, + typeInt: assetEntity.typeInt, + ); + } imagePickerGridController.pickAssetFromCamera(assetEntity); if (onCameraPicked != null) { From 04da959ea6e1ef821c6a992ed675861ff436632b Mon Sep 17 00:00:00 2001 From: sherlock <43041967+sherlockvn@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:25:35 +0700 Subject: [PATCH 22/40] Tw 2001: No previews for video (#2004) --- .../extensions/send_file_extension.dart | 21 ++++++++++++------- .../extensions/send_file_web_extension.dart | 13 ++++++++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/presentation/extensions/send_file_extension.dart b/lib/presentation/extensions/send_file_extension.dart index 68be47220b..4042f80559 100644 --- a/lib/presentation/extensions/send_file_extension.dart +++ b/lib/presentation/extensions/send_file_extension.dart @@ -93,17 +93,15 @@ extension SendFileExtension on Room { } final formattedDateTime = DateTime.now().getFormattedCurrentDateTime(); - final targetPath = - await File('${tempDir.path}/$formattedDateTime${fileInfo.fileName}') - .create(); - fileInfo = fileInfo as ImageFileInfo; + final fileName = _generateThumbnailFileName(formattedDateTime, fileInfo); + final targetPath = await _createThumbnailFile(tempDir, fileName); await _generateThumbnail( - fileInfo, + fileInfo as ImageFileInfo, targetPath: targetPath.path, uploadStreamController: uploadStreamController, ); thumbnail = ImageFileInfo( - fileInfo.fileName, + fileName, targetPath.path, await targetPath.length(), width: fileInfo.width, @@ -430,6 +428,15 @@ extension SendFileExtension on Room { return eventId; } + Future _createThumbnailFile(Directory tempDir, String fileName) async => + await File('${tempDir.path}/$fileName').create(); + + String _generateThumbnailFileName( + String formattedDateTime, + FileInfo fileInfo, + ) => + '$formattedDateTime${fileInfo.fileName}.${AppConfig.imageCompressFormmat.name}'; + Future convertHeicToJpgImage(ImageFileInfo fileInfo) async { final convertedFilePath = StorageDirectoryManager.instance.convertFileExtension( @@ -632,7 +639,7 @@ extension SendFileExtension on Room { } uploadStreamController?.add(const Right(GenerateThumbnailSuccess())); return ImageFileInfo( - '${result.name}.${AppConfig.imageCompressFormmat.name}', + result.name, result.path, size, width: width, diff --git a/lib/presentation/extensions/send_file_web_extension.dart b/lib/presentation/extensions/send_file_web_extension.dart index a2a9a66afb..42f08ff02a 100644 --- a/lib/presentation/extensions/send_file_web_extension.dart +++ b/lib/presentation/extensions/send_file_web_extension.dart @@ -19,6 +19,7 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dar import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:matrix/matrix.dart'; import 'package:image/image.dart'; +import 'package:mime/mime.dart'; import 'package:video_player/video_player.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; @@ -169,7 +170,7 @@ extension SendFileWebExtension on Room { : null; if (uploadThumbnail != null && uploadThumbnail.bytes != null) { final uploadThumbnailResponse = await mediaApi.uploadFileWeb( - file: file, + file: uploadThumbnail, cancelToken: cancelToken, onSendProgress: (receive, total) { uploadStreamController?.add( @@ -442,11 +443,12 @@ extension SendFileWebExtension on Room { const Right(GenerateThumbnailSuccess()), ); + final thumbnailFileName = _getVideoThumbnailFileName(originalFile); + return MatrixImageFile( bytes: result, - name: - '${originalFile.name}.${AppConfig.videoThumbnailFormat.name.toLowerCase()}', - mimeType: originalFile.mimeType, + name: thumbnailFileName, + mimeType: lookupMimeType(thumbnailFileName) ?? 'image/jpeg', width: thumbnailBitmap?.width, height: thumbnailBitmap?.height, blurhash: blurHash, @@ -464,6 +466,9 @@ extension SendFileWebExtension on Room { } } + String _getVideoThumbnailFileName(MatrixVideoFile originalFile) => + '${originalFile.name}.${AppConfig.videoThumbnailFormat.name.toLowerCase()}'; + Future _getVideoDuration( MatrixVideoFile originalFile, ) async { From 57a7a0159508a0bb3a4884e5488704059ed38a35 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Tue, 13 Aug 2024 11:02:09 +0700 Subject: [PATCH 23/40] TW-1974: Update logic for load more --- lib/pages/chat/chat_event_list.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index c245c1b612..576c23187f 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -128,11 +128,18 @@ class ChatEventList extends StatelessWidget { ); } if (controller.timeline!.canRequestHistory) { - return Center( - child: IconButton( - onPressed: controller.requestHistory, - icon: const Icon(Icons.refresh_outlined), - ), + return Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback( + (_) => controller.requestHistory(), + ); + return Center( + child: IconButton( + onPressed: controller.requestHistory, + icon: const Icon(Icons.refresh_outlined), + ), + ); + }, ); } return const SizedBox.shrink(); From 4c95d53c9bf3baf7258820540c4733b19bdb224a Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Tue, 13 Aug 2024 11:03:27 +0700 Subject: [PATCH 24/40] TW-1974: Add highlight item when click on reply message --- lib/pages/chat/chat_event_list.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 576c23187f..5e4fbaf556 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -174,7 +174,10 @@ class ChatEventList extends StatelessWidget { onSelect: controller.onSelectMessage, selectMode: controller.selectMode, scrollToEventId: (String eventId) => - controller.scrollToEventId(eventId), + controller.scrollToEventId( + eventId, + highlight: true, + ), selected: controller.selectedEvents .any((e) => e.eventId == event.eventId), timeline: controller.timeline!, From ef051c40eb26b647fde06795d9f16afe8e06d7a0 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Tue, 13 Aug 2024 11:26:32 +0700 Subject: [PATCH 25/40] TW-1974: Update scroll to message --- lib/pages/chat/chat.dart | 48 ++++++++++++++----- lib/pages/chat/chat_event_list.dart | 17 ++----- .../pinned_events_controller.dart | 18 ++++--- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index e8ff7bce34..db897f0583 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -19,7 +19,6 @@ import 'package:fluffychat/widgets/mixins/popup_menu_widget_style.dart'; import 'package:fluffychat/widgets/mixins/twake_context_menu_mixin.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:fluffychat/utils/extension/global_key_extension.dart'; -import 'package:inview_notifier_list/inview_notifier_list.dart'; import 'package:universal_html/html.dart' as html; import 'package:adaptive_dialog/adaptive_dialog.dart'; @@ -388,11 +387,20 @@ class ChatController extends State if (scrollController.position.pixels == 0 || scrollController.position.pixels == _isPortionAvailableToScroll) { requestFuture(); - } else if (scrollController.position.pixels == + } + + _handleRequestHistory(); + } + + void _handleRequestHistory() { + if (scrollController.position.pixels == scrollController.position.maxScrollExtent || scrollController.position.pixels + _isPortionAvailableToScroll == scrollController.position.maxScrollExtent) { - await requestHistory(); + if (timeline?.isRequestingHistory == true) return; + if (timeline?.canRequestHistory == true) { + requestHistory(); + } } } @@ -934,6 +942,7 @@ class ChatController extends State if (timeline == null) return; if (!timeline!.allowNewEvent) { setState(() { + timeline = null; loadTimelineFuture = _getTimeline().onError( (e, s) { Logs().e('Chat::scrollDown(): Unable to load timeline', e, s); @@ -954,26 +963,42 @@ class ChatController extends State return eventIndex + addedHeadItemsInChat; } + int _getEventIndex(String eventId) { + final foundEvent = + timeline!.events.firstWhereOrNull((event) => event.eventId == eventId); + + final eventIndex = foundEvent == null + ? -1 + : timeline!.events.indexWhere( + (event) => event.eventId == foundEvent.eventId, + ); + + return eventIndex; + } + Future scrollToEventId(String eventId, {bool highlight = true}) async { - final eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); + final eventIndex = _getEventIndex(eventId); if (eventIndex == -1) { - loadTimelineFuture = _getTimeline(eventContextId: eventId).onError( - (e, s) { - Logs().e('Chat::scrollToEventId(): Unable to load timeline', e, s); - }, - ); + setState(() { + timeline = null; + loadTimelineFuture = _getTimeline(eventContextId: eventId).onError( + (e, s) { + Logs().e('Chat::scrollToEventId(): Unable to load timeline', e, s); + }, + ); + }); await loadTimelineFuture; WidgetsBinding.instance.addPostFrameCallback((timeStamp) { scrollToEventId(eventId, highlight: highlight); }); - setState(() {}); return; } + await scrollToIndex(getDisplayEventIndex(eventIndex), highlight: highlight); _updateScrollController(); } - Future scrollToIndex(int index, {bool highlight = false}) async { + Future scrollToIndex(int index, {bool highlight = true}) async { await scrollController.scrollToIndex( index, preferPosition: AutoScrollPosition.middle, @@ -2001,7 +2026,6 @@ class ChatController extends State pinnedMessageScrollController.dispose(); onUpdateEventStreamSubcription?.cancel(); keyboardVisibilitySubscription?.cancel(); - InViewNotifierListCustom.of(context)?.dispose(); replyEventNotifier.dispose(); cachedPresenceStreamController.close(); cachedPresenceNotifier.dispose(); diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 5e4fbaf556..4a01430949 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -128,18 +128,11 @@ class ChatEventList extends StatelessWidget { ); } if (controller.timeline!.canRequestHistory) { - return Builder( - builder: (context) { - WidgetsBinding.instance.addPostFrameCallback( - (_) => controller.requestHistory(), - ); - return Center( - child: IconButton( - onPressed: controller.requestHistory, - icon: const Icon(Icons.refresh_outlined), - ), - ); - }, + return Center( + child: IconButton( + onPressed: controller.requestHistory, + icon: const Icon(Icons.refresh_outlined), + ), ); } return const SizedBox.shrink(); diff --git a/lib/pages/chat/chat_pinned_events/pinned_events_controller.dart b/lib/pages/chat/chat_pinned_events/pinned_events_controller.dart index 9b3e08745f..bf47cdb744 100644 --- a/lib/pages/chat/chat_pinned_events/pinned_events_controller.dart +++ b/lib/pages/chat/chat_pinned_events/pinned_events_controller.dart @@ -79,21 +79,27 @@ class PinnedEventsController { void Function(String)? scrollToEventId, }) async { final nextIndex = _nextIndexOfPinnedMessage(pinnedEvents); - final event = pinnedEvents[nextIndex]; + final nextEvent = pinnedEvents[nextIndex]; + final currentEvent = currentPinnedEventNotifier.value; Logs().d( - "PinnedEventsController()::jumpToPinnedMessage(): eventID: ${event?.eventId}", + "PinnedEventsController()::jumpToPinnedMessage(): eventID: ${nextEvent?.eventId}", ); - if (event != null) { - currentPinnedEventNotifier.value = event; + if (currentEvent != null) { + currentPinnedEventNotifier.value = nextEvent; if (scrollToEventId != null) { - scrollToEventId.call(event.eventId); + scrollToEventId.call(currentEvent.eventId); } } } int currentIndexOfPinnedMessage(List pinnedEvents) { final index = pinnedEvents.indexWhere( - (event) => event?.eventId == currentPinnedEventNotifier.value?.eventId, + (event) { + Logs().d( + "PinnedEventsController()::currentIndexOfPinnedMessage(): ${currentPinnedEventNotifier.value?.eventId}", + ); + return event?.eventId == currentPinnedEventNotifier.value?.eventId; + }, ); if (index < 0) { currentPinnedEventNotifier.value = pinnedEvents.first; From 309578a3c59361e8f85104b948eed7745cfd4415 Mon Sep 17 00:00:00 2001 From: "quanghnguyen@linagora.com" Date: Fri, 30 Aug 2024 16:52:30 +0700 Subject: [PATCH 26/40] TW-1982: Fix can't create direct chat when search in contact tab --- .../contacts_view_controller_mixin.dart | 55 +++++++++++++++---- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/presentation/mixins/contacts_view_controller_mixin.dart b/lib/presentation/mixins/contacts_view_controller_mixin.dart index 75b6390cee..c5528fadea 100644 --- a/lib/presentation/mixins/contacts_view_controller_mixin.dart +++ b/lib/presentation/mixins/contacts_view_controller_mixin.dart @@ -236,17 +236,23 @@ mixin class ContactsViewControllerMixin { contactsManager.getContactsNotifier().value.fold( (failure) { if (failure is GetContactsFailure) { - return Left( - GetPresentationContactsFailure( - keyword: keyword, + return _handleSearchExternalContact( + keyword, + otherResult: Left( + GetPresentationContactsFailure( + keyword: keyword, + ), ), ); } if (failure is GetContactsIsEmpty) { - return Left( - GetPresentationContactsEmpty( - keyword: keyword, + return _handleSearchExternalContact( + keyword, + otherResult: Left( + GetPresentationContactsEmpty( + keyword: keyword, + ), ), ); } @@ -296,17 +302,23 @@ mixin class ContactsViewControllerMixin { contactsManager.getPhonebookContactsNotifier().value.fold( (failure) { if (failure is GetPhonebookContactsFailure) { - return Left( - GetPresentationContactsFailure( - keyword: keyword, + return _handleSearchExternalContact( + keyword, + otherResult: Left( + GetPresentationContactsFailure( + keyword: keyword, + ), ), ); } if (failure is GetPhonebookContactsIsEmpty) { - return Left( - GetPresentationContactsEmpty( - keyword: keyword, + return _handleSearchExternalContact( + keyword, + otherResult: Left( + GetPresentationContactsEmpty( + keyword: keyword, + ), ), ); } @@ -338,6 +350,25 @@ mixin class ContactsViewControllerMixin { ); } + Either _handleSearchExternalContact( + String keyword, { + required Either otherResult, + }) { + if (keyword.isValidMatrixId && keyword.startsWith("@")) { + return Right( + PresentationExternalContactSuccess( + contact: PresentationContact( + matrixId: keyword, + displayName: keyword.substring(1), + type: ContactType.external, + ), + ), + ); + } else { + return otherResult; + } + } + Future _refreshRecentContacts({ required Client client, required MatrixLocalizations matrixLocalizations, From 7661c07048f628c42e0fe1809b58cf4406f71649 Mon Sep 17 00:00:00 2001 From: sherlock <43041967+sherlockvn@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:00:15 +0700 Subject: [PATCH 27/40] TW-1936: fix the share screen don't show up when sharing an image (#1991) --- lib/config/go_routes/go_router.dart | 9 ------ .../participant_list_item.dart | 4 +-- .../receive_sharing_intent_mixin.dart | 30 +++++++++++++++++-- lib/pages/share/share.dart | 2 ++ 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/config/go_routes/go_router.dart b/lib/config/go_routes/go_router.dart index 808467d4c1..b38bdec6e1 100644 --- a/lib/config/go_routes/go_router.dart +++ b/lib/config/go_routes/go_router.dart @@ -17,7 +17,6 @@ import 'package:fluffychat/pages/login/on_auth_redirect.dart'; import 'package:fluffychat/pages/new_group/new_group_chat_info.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_app_language/settings_app_language.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile.dart'; -import 'package:fluffychat/pages/share/share.dart'; import 'package:fluffychat/pages/story/story_page.dart'; import 'package:fluffychat/pages/twake_welcome/twake_welcome.dart'; import 'package:fluffychat/presentation/model/chat/chat_router_input_argument.dart'; @@ -519,14 +518,6 @@ abstract class AppRoutes { ), ], ), - GoRoute( - path: '/share', - pageBuilder: (context, state) => defaultPageBuilder( - context, - const Share(), - ), - redirect: loggedOutRedirect, - ), ], ), ]; diff --git a/lib/pages/chat_details/participant_list_item/participant_list_item.dart b/lib/pages/chat_details/participant_list_item/participant_list_item.dart index e059fa7509..6f8150a01b 100644 --- a/lib/pages/chat_details/participant_list_item/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item/participant_list_item.dart @@ -186,11 +186,11 @@ class ParticipantListItem extends StatelessWidget { Icons.person_search, color: LinagoraSysColors.material().onSurface, ), - label: L10n.of(context)?.viewProfile != null + label: L10n.of(bottomSheetContext)?.viewProfile != null ? Row( children: [ Text( - L10n.of(context)!.viewProfile, + L10n.of(bottomSheetContext)!.viewProfile, style: TextStyle( color: LinagoraSysColors.material() .onSurface, diff --git a/lib/pages/chat_list/receive_sharing_intent_mixin.dart b/lib/pages/chat_list/receive_sharing_intent_mixin.dart index d827a8b5a4..838da4d041 100644 --- a/lib/pages/chat_list/receive_sharing_intent_mixin.dart +++ b/lib/pages/chat_list/receive_sharing_intent_mixin.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/event/twake_event_types.dart'; +import 'package:fluffychat/pages/share/share.dart'; import 'package:fluffychat/presentation/extensions/shared_media_file_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/url_launcher.dart'; @@ -35,9 +36,32 @@ mixin ReceiveSharingIntentMixin on State { }, ) .toList(); - TwakeApp.router.go('/share'); + openSharePage(); } + void openSharePage() { + if (isCurrentPageIsNotRooms()) { + return; + } + if (isCurrentPageIsInRooms()) { + TwakeApp.router.go('/rooms'); + } + + Navigator.of(TwakeApp.routerKey.currentContext!).push( + MaterialPageRoute( + builder: (context) => const Share(), + ), + ); + } + + bool isCurrentPageIsInRooms() => + TwakeApp.router.routeInformationProvider.value.uri.path + .startsWith('/rooms/'); + + bool isCurrentPageIsNotRooms() => + !TwakeApp.router.routeInformationProvider.value.uri.path + .startsWith('/rooms'); + void _processIncomingSharedText(String? text) { if (text == null) return; if (_intentOpenApp(text)) { @@ -53,7 +77,7 @@ mixin ReceiveSharingIntentMixin on State { 'msgtype': 'm.text', 'body': text, }; - TwakeApp.router.go('/share'); + openSharePage(); } void _processIncomingUris(String? text) async { @@ -62,7 +86,7 @@ mixin ReceiveSharingIntentMixin on State { if (_intentOpenApp(text)) { return; } - TwakeApp.router.go('/share'); + openSharePage(); WidgetsBinding.instance.addPostFrameCallback((_) { UrlLauncher(context, url: text).openMatrixToUrl(); }); diff --git a/lib/pages/share/share.dart b/lib/pages/share/share.dart index 1072679aae..70de9b41d2 100644 --- a/lib/pages/share/share.dart +++ b/lib/pages/share/share.dart @@ -93,6 +93,7 @@ class ShareController extends State Map? textContent, }) { if (textContent == null) return; + Navigator.pop(context); room.sendEvent(textContent); context.go('/rooms/${room.id}'); } @@ -107,6 +108,7 @@ class ShareController extends State content?.tryGet('msgtype') == TwakeEventTypes.shareFileEventType, )) { + Navigator.pop(context); context.go( '/rooms/${room.id}', extra: ChatRouterInputArgument( From 6901c88e595b714f0775cecf36eb5967cb85b71d Mon Sep 17 00:00:00 2001 From: --global Date: Thu, 8 Aug 2024 15:20:33 +0700 Subject: [PATCH 28/40] TW-1926: upgrade flutter to 3.24.0 --- .github/workflows/build.yaml | 2 +- .github/workflows/gh-pages.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/tests.yaml | 2 +- .gitlab-ci.yml | 2 +- Dockerfile | 2 +- ios/Runner/AppDelegate.swift | 2 +- .../receive_sharing_intent_mixin.dart | 37 ++- .../shared_media_file_extension.dart | 4 +- lib/utils/voip/callkeep_manager.dart | 49 ++-- lib/utils/voip/user_media_manager.dart | 6 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + macos/Podfile.lock | 102 +++++--- macos/Runner.xcodeproj/project.pbxproj | 27 ++- pubspec.lock | 220 +++++++++++------- pubspec.yaml | 45 ++-- .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 20 files changed, 348 insertions(+), 173 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 65ced8e53c..1ddce5067a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,7 +6,7 @@ on: name: Build env: - FLUTTER_VERSION: 3.22.2 + FLUTTER_VERSION: 3.24.0 XCODE_VERSION: ^15.0.1 jobs: diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index a05f24d3f1..60d8b36c78 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -2,7 +2,7 @@ on: pull_request: env: - FLUTTER_VERSION: 3.22.2 + FLUTTER_VERSION: 3.24.0 LIBOLM_VERSION: 3.2.16 name: Deploying on GitHub Pages diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9900cf2e99..6f78ddbfbf 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,7 +4,7 @@ on: - "v*.*.*" env: - FLUTTER_VERSION: 3.22.2 + FLUTTER_VERSION: 3.24.0 XCODE_VERSION: ^15.0.1 name: Release diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 69d251ff18..685041ed13 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -4,7 +4,7 @@ on: name: Tests env: - FLUTTER_VERSION: 3.22.2 + FLUTTER_VERSION: 3.24.0 jobs: code_analyze: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2dcffc6a5a..f048d25f30 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - FLUTTER_VERSION: 3.22.2 + FLUTTER_VERSION: 3.24.0 image: name: cirrusci/flutter:${FLUTTER_VERSION} diff --git a/Dockerfile b/Dockerfile index 8843fe8c2f..705320acd4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Specify versions -ARG FLUTTER_VERSION=3.22.2 +ARG FLUTTER_VERSION=3.24.0 ARG OLM_VERSION=3.2.16 ARG NIX_VERSION=2.22.1 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6fe9d3c340..98384d5d84 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -3,7 +3,7 @@ import Flutter let apnTokenKey = "apnToken" -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { var twakeApnChannel: FlutterMethodChannel? var initialNotiInfo: Any? diff --git a/lib/pages/chat_list/receive_sharing_intent_mixin.dart b/lib/pages/chat_list/receive_sharing_intent_mixin.dart index 838da4d041..70e085750f 100644 --- a/lib/pages/chat_list/receive_sharing_intent_mixin.dart +++ b/lib/pages/chat_list/receive_sharing_intent_mixin.dart @@ -1,3 +1,4 @@ +import 'package:app_links/app_links.dart'; import 'package:fluffychat/event/twake_event_types.dart'; import 'package:fluffychat/pages/share/share.dart'; import 'package:fluffychat/presentation/extensions/shared_media_file_extension.dart'; @@ -11,7 +12,6 @@ import 'dart:async'; import 'package:fluffychat/config/app_config.dart'; import 'package:matrix/matrix.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; -import 'package:uni_links/uni_links.dart'; mixin ReceiveSharingIntentMixin on State { MatrixState get matrixState; @@ -96,24 +96,41 @@ mixin ReceiveSharingIntentMixin on State { if (!PlatformInfos.isMobile) return; // For sharing images coming from outside the app while the app is in the memory - intentFileStreamSubscription = ReceiveSharingIntent.getMediaStream() + intentFileStreamSubscription = ReceiveSharingIntent.instance + .getMediaStream() .listen(_processIncomingSharedFiles, onError: print); // For sharing images coming from outside the app while the app is closed - ReceiveSharingIntent.getInitialMedia().then(_processIncomingSharedFiles); - - // For sharing or opening urls/text coming from outside the app while the app is in the memory - intentDataStreamSubscription = ReceiveSharingIntent.getTextStream() - .listen(_processIncomingSharedText, onError: print); + ReceiveSharingIntent.instance + .getInitialMedia() + .then(_processIncomingSharedFiles); + + ReceiveSharingIntent.instance.getMediaStream().listen((value) { + // TODO: Handle mutliple text sharing + if (value.isNotEmpty) { + if (value.first is String) { + _processIncomingSharedText(value.first as String); + } + } + }); // For sharing or opening urls/text coming from outside the app while the app is closed - ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText); + ReceiveSharingIntent.instance.getInitialMedia().then((value) { + // TODO: Handle mutliple text sharing + if (value.isNotEmpty) { + if (value.first is String) { + _processIncomingSharedText(value.first as String); + } + } + }); // For receiving shared Uris - intentUriStreamSubscription = linkStream.listen(_processIncomingUris); + final appLinks = AppLinks(); + intentUriStreamSubscription = + appLinks.stringLinkStream.listen(_processIncomingUris); if (TwakeApp.gotInitialLink == false) { TwakeApp.gotInitialLink = true; - getInitialLink().then(_processIncomingUris); + appLinks.getInitialLinkString().then(_processIncomingUris); } } } diff --git a/lib/presentation/extensions/shared_media_file_extension.dart b/lib/presentation/extensions/shared_media_file_extension.dart index 3f064277be..5dcf70b706 100644 --- a/lib/presentation/extensions/shared_media_file_extension.dart +++ b/lib/presentation/extensions/shared_media_file_extension.dart @@ -6,14 +6,14 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart'; extension SharedMediaFileExtension on SharedMediaFile { MatrixFile toMatrixFile() { - if (type == SharedMediaType.IMAGE) { + if (type == SharedMediaType.image) { return MatrixImageFile( bytes: null, name: path.split("/").last, filePath: path, ); } - if (type == SharedMediaType.VIDEO) { + if (type == SharedMediaType.video) { Uint8List? thumbnailBytes; if (thumbnail != null) { thumbnailBytes = File(thumbnail!).readAsBytesSync(); diff --git a/lib/utils/voip/callkeep_manager.dart b/lib/utils/voip/callkeep_manager.dart index 32a9c1ac00..696f86705a 100644 --- a/lib/utils/voip/callkeep_manager.dart +++ b/lib/utils/voip/callkeep_manager.dart @@ -157,8 +157,8 @@ class CallKeepManager { } void didDisplayIncomingCall(CallKeepDidDisplayIncomingCall event) { - final callUUID = event.callUUID; - final number = event.handle; + final callUUID = event.callData.callUUID; + final number = event.callData.handle; Logs().v('[displayIncomingCall] $callUUID number: $number'); // addCall(callUUID, CallKeeper(this null)); } @@ -168,20 +168,18 @@ class CallKeepManager { } Future initialize() async { - _callKeep.on(CallKeepPerformAnswerCallAction(), answerCall); - _callKeep.on(CallKeepDidPerformDTMFAction(), didPerformDTMFAction); - _callKeep.on( - CallKeepDidReceiveStartCallAction(), + _callKeep.on(answerCall); + _callKeep.on(didPerformDTMFAction); + _callKeep.on( didReceiveStartCallAction, ); - _callKeep.on(CallKeepDidToggleHoldAction(), didToggleHoldCallAction); - _callKeep.on( - CallKeepDidPerformSetMutedCallAction(), + _callKeep.on(didToggleHoldCallAction); + _callKeep.on( didPerformSetMutedCallAction, ); - _callKeep.on(CallKeepPerformEndCallAction(), endCall); - _callKeep.on(CallKeepPushKitToken(), onPushKitToken); - _callKeep.on(CallKeepDidDisplayIncomingCall(), didDisplayIncomingCall); + _callKeep.on(endCall); + _callKeep.on(onPushKitToken); + _callKeep.on(didDisplayIncomingCall); Logs().i('[VOIP] Initialized'); } @@ -217,13 +215,13 @@ class CallKeepManager { if (isIOS) { await _callKeep.updateDisplay( callUUID, - displayName: 'New Name', + callerName: 'New Name', handle: callUUID, ); } else { await _callKeep.updateDisplay( callUUID, - displayName: callUUID, + callerName: callUUID, handle: 'New Name', ); } @@ -235,8 +233,7 @@ class CallKeepManager { await _callKeep.displayIncomingCall( call.callId, '${call.room.getLocalizedDisplayname()} (Twake Chat)', - localizedCallerName: - '${call.room.getLocalizedDisplayname()} (Twake Chat)', + callerName: '${call.room.getLocalizedDisplayname()} (Twake Chat)', handleType: 'number', hasVideo: call.type == CallType.kVideo, ); @@ -298,8 +295,8 @@ class CallKeepManager { /// CallActions. Future answerCall(CallKeepPerformAnswerCallAction event) async { - final callUUID = event.callUUID; - final keeper = calls[event.callUUID]!; + final callUUID = event.callData.callUUID; + final keeper = calls[event.callData.callUUID]!; if (!keeper.connected) { Logs().e('answered'); // Answer Call @@ -325,17 +322,21 @@ class CallKeepManager { Future didReceiveStartCallAction( CallKeepDidReceiveStartCallAction event, ) async { - if (event.handle == null) { + if (event.callData.handle == null) { // @TODO: sometime we receive `didReceiveStartCallAction` with handle` undefined` return; } - final callUUID = event.callUUID!; - if (event.callUUID == null) { - final call = - await _voipPlugin!.voip.inviteToCall(event.handle!, CallType.kVideo); + final callUUID = event.callData.callUUID!; + if (event.callData.callUUID == null) { + final call = await _voipPlugin!.voip + .inviteToCall(event.callData.handle!, CallType.kVideo); addCall(callUUID, CallKeeper(this, call)); } - await _callKeep.startCall(callUUID, event.handle!, event.handle!); + await _callKeep.startCall( + callUUID, + event.callData.handle!, + event.callData.handle!, + ); Timer(const Duration(seconds: 1), () { _callKeep.setCurrentCallActive(callUUID); }); diff --git a/lib/utils/voip/user_media_manager.dart b/lib/utils/voip/user_media_manager.dart index 874da93ee1..5d7cd39401 100644 --- a/lib/utils/voip/user_media_manager.dart +++ b/lib/utils/voip/user_media_manager.dart @@ -16,9 +16,11 @@ class UserMediaManager { AudioPlayer? _assetsAudioPlayer; + static final _flutterRingtonePlayer = FlutterRingtonePlayer(); + Future startRingingTone() async { if (PlatformInfos.isMobile) { - await FlutterRingtonePlayer.playRingtone(volume: 80); + await _flutterRingtonePlayer.playRingtone(volume: 80); } else if ((kIsWeb || PlatformInfos.isMacOS) && _assetsAudioPlayer != null) { const path = 'assets/sounds/phone.ogg'; @@ -31,7 +33,7 @@ class UserMediaManager { Future stopRingingTone() async { if (PlatformInfos.isMobile) { - await FlutterRingtonePlayer.stop(); + await _flutterRingtonePlayer.stop(); } await _assetsAudioPlayer?.stop(); _assetsAudioPlayer = null; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 090402fe16..f774eab097 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin"); flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar); + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); g_autoptr(FlPluginRegistrar) handy_window_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "HandyWindowPlugin"); handy_window_plugin_register_with_registrar(handy_window_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e59cbc86d0..fbbabf044e 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_avif_linux flutter_secure_storage_linux flutter_webrtc + gtk handy_window irondash_engine_context media_kit_libs_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5a9d26a988..ae0532078f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import app_links import appkit_ui_element_colors import audio_session import connectivity_plus @@ -16,6 +17,7 @@ import emoji_picker_flutter import file_saver import file_selector_macos import firebase_core +import firebase_messaging import flutter_app_badger import flutter_avif_macos import flutter_image_compress_macos @@ -48,6 +50,7 @@ import wakelock_plus import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) @@ -59,6 +62,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin")) FlutterAvifPlugin.register(with: registry.registrar(forPlugin: "FlutterAvifPlugin")) FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 1e88dd260d..ccb36350d1 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -20,25 +20,43 @@ PODS: - FlutterMacOS - file_selector_macos (0.0.1): - FlutterMacOS - - Firebase/CoreOnly (9.6.0): - - FirebaseCore (= 9.6.0) - - firebase_core (1.24.0): - - Firebase/CoreOnly (~> 9.6.0) - - FlutterMacOS - - FirebaseCore (9.6.0): - - FirebaseCoreDiagnostics (~> 9.0) - - FirebaseCoreInternal (~> 9.0) - - GoogleUtilities/Environment (~> 7.7) - - GoogleUtilities/Logger (~> 7.7) - - FirebaseCoreDiagnostics (9.6.0): - - GoogleDataTransport (< 10.0.0, >= 9.1.4) - - GoogleUtilities/Environment (~> 7.7) - - GoogleUtilities/Logger (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCoreInternal (9.6.0): - - "GoogleUtilities/NSData+zlib (~> 7.7)" + - Firebase/CoreOnly (10.25.0): + - FirebaseCore (= 10.25.0) + - Firebase/Messaging (10.25.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 10.25.0) + - firebase_core (2.32.0): + - Firebase/CoreOnly (~> 10.25.0) + - FlutterMacOS + - firebase_messaging (14.9.4): + - Firebase/CoreOnly (~> 10.25.0) + - Firebase/Messaging (~> 10.25.0) + - firebase_core + - FlutterMacOS + - FirebaseCore (10.25.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.12) + - GoogleUtilities/Logger (~> 7.12) + - FirebaseCoreInternal (10.29.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseInstallations (10.29.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) + - PromisesObjC (~> 2.1) + - FirebaseMessaging (10.25.0): + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleDataTransport (~> 9.3) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Reachability (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) + - nanopb (< 2.30911.0, >= 2.30908.0) - flutter_app_badger (1.3.0): - FlutterMacOS + - flutter_avif_macos (0.0.1): + - FlutterMacOS - flutter_image_compress_macos (1.0.0): - FlutterMacOS - flutter_inappwebview_macos (0.0.1): @@ -61,15 +79,31 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.13.3): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy - GoogleUtilities/Environment (7.13.3): - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) - GoogleUtilities/Logger (7.13.3): - GoogleUtilities/Environment - GoogleUtilities/Privacy + - GoogleUtilities/Network (7.13.3): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability - "GoogleUtilities/NSData+zlib (7.13.3)": - GoogleUtilities/Privacy - GoogleUtilities/Privacy (7.13.3) + - GoogleUtilities/Reachability (7.13.3): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (7.13.3): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy - irondash_engine_context (0.0.1): - FlutterMacOS - just_audio (0.0.1): @@ -84,11 +118,11 @@ PODS: - FlutterMacOS - media_kit_video (0.0.1): - FlutterMacOS - - nanopb (2.30909.1): - - nanopb/decode (= 2.30909.1) - - nanopb/encode (= 2.30909.1) - - nanopb/decode (2.30909.1) - - nanopb/encode (2.30909.1) + - nanopb (2.30910.0): + - nanopb/decode (= 2.30910.0) + - nanopb/encode (= 2.30910.0) + - nanopb/decode (2.30910.0) + - nanopb/encode (2.30910.0) - OrderedSet (5.0.0) - package_info_plus (0.0.1): - FlutterMacOS @@ -141,7 +175,9 @@ DEPENDENCIES: - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) + - firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`) - flutter_app_badger (from `Flutter/ephemeral/.symlinks/plugins/flutter_app_badger/macos`) + - flutter_avif_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_avif_macos/macos`) - flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) @@ -177,8 +213,9 @@ SPEC REPOS: trunk: - Firebase - FirebaseCore - - FirebaseCoreDiagnostics - FirebaseCoreInternal + - FirebaseInstallations + - FirebaseMessaging - GoogleDataTransport - GoogleUtilities - nanopb @@ -210,8 +247,12 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos firebase_core: :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos + firebase_messaging: + :path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos flutter_app_badger: :path: Flutter/ephemeral/.symlinks/plugins/flutter_app_badger/macos + flutter_avif_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_avif_macos/macos flutter_image_compress_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos flutter_inappwebview_macos: @@ -284,12 +325,15 @@ SPEC CHECKSUMS: emoji_picker_flutter: 533634326b1c5de9a181ba14b9758e6dfe967a20 file_saver: 44e6fbf666677faf097302460e214e977fdd977b file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2 - Firebase: 5ae8b7cf8efce559a653aef0ad95bab3f427c351 - firebase_core: 970bc7db019f0985976324d90cdc370527c31461 - FirebaseCore: 2082fffcd855f95f883c0a1641133eb9bbe76d40 - FirebaseCoreDiagnostics: 99a495094b10a57eeb3ae8efa1665700ad0bdaa6 - FirebaseCoreInternal: bca76517fe1ed381e989f5e7d8abb0da8d85bed3 + Firebase: 0312a2352584f782ea56f66d91606891d4607f06 + firebase_core: b5b8b60dad71f93132bbaa21e8d1379367d824f0 + firebase_messaging: d821ad7103878837085612e389fe66203c5c6c0c + FirebaseCore: 7ec4d0484817f12c3373955bc87762d96842d483 + FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934 + FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd + FirebaseMessaging: 88950ba9485052891ebe26f6c43a52bb62248952 flutter_app_badger: 55a64b179f8438e89d574320c77b306e327a1730 + flutter_avif_macos: 065347857175874d00f4736233a6ae0fc2b66f0a flutter_image_compress_macos: c26c3c13ea0f28ae6dea4e139b3292e7729f99f1 flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 @@ -307,7 +351,7 @@ SPEC CHECKSUMS: media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 - nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 + nanopb: 438bc412db1928dac798aa6fd75726007be04262 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 9f7368bd71..66c697ac00 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -184,6 +184,7 @@ 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 28EE3A13194E86CCA3B8236A /* [CP] Embed Pods Frameworks */, + 6CEE6CE5DB9536C808CAEA5B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -261,8 +262,9 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCoreInternal/FirebaseCoreInternal.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseMessaging/FirebaseMessaging.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", "${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.framework", @@ -279,6 +281,7 @@ "${BUILT_PRODUCTS_DIR}/file_saver/file_saver.framework", "${BUILT_PRODUCTS_DIR}/file_selector_macos/file_selector_macos.framework", "${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework", + "${BUILT_PRODUCTS_DIR}/flutter_avif_macos/flutter_avif_macos.framework", "${BUILT_PRODUCTS_DIR}/flutter_image_compress_macos/flutter_image_compress_macos.framework", "${BUILT_PRODUCTS_DIR}/flutter_inappwebview_macos/flutter_inappwebview_macos.framework", "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", @@ -332,8 +335,9 @@ name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreInternal.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseMessaging.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OrderedSet.framework", @@ -350,6 +354,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_saver.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_selector_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_avif_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_image_compress_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_inappwebview_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", @@ -443,6 +448,24 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; }; + 6CEE6CE5DB9536C808CAEA5B /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/firebase_messaging/firebase_messaging_Privacy.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/firebase_messaging_Privacy.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; E26D3B733E5CAE51F86A6BE1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/pubspec.lock b/pubspec.lock index c893e8b643..1167d5d6f9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "67.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" + url: "https://pub.dev" + source: hosted + version: "1.3.35" adaptive_dialog: dependency: "direct main" description: @@ -49,6 +57,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + app_links: + dependency: "direct main" + description: + name: app_links + sha256: "4acba851087b25136e8f6e32a53bd4536eb3bec69947ddb66e7b9a5792ceb0c7" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" appkit_ui_element_colors: dependency: transitive description: @@ -220,10 +260,11 @@ packages: callkeep: dependency: "direct main" description: - name: callkeep - sha256: "9e86e9632a603a61f7045c179ea5ca0ee4da0a49fc5f80c2fe09fb422b96d3c6" - url: "https://pub.dev" - source: hosted + path: "." + ref: master + resolved-ref: "7d8a0a6f1d40c926e2c287d8a1bdf089dd499cf6" + url: "https://github.com/flutter-webrtc/callkeep.git" + source: git version: "0.3.3" camera: dependency: transitive @@ -365,8 +406,8 @@ packages: dependency: "direct main" description: path: "." - ref: master - resolved-ref: "083024c6200f4773e34c471b4001cd76fe1c9cec" + ref: upgrade-targetAndCompileSdkVersion + resolved-ref: a0862ae22bf07ab3cfed8b31724b990df84bcd29 url: "git@github.com:linagora/flutter_contacts.git" source: git version: "0.6.3" @@ -605,10 +646,11 @@ packages: fcm_shared_isolate: dependency: "direct main" description: - name: fcm_shared_isolate - sha256: "45c66353aad6a237437b3d071bddddd35d391b75c3e06aaec535a9df32d44dbe" - url: "https://pub.dev" - source: hosted + path: "." + ref: main + resolved-ref: "908336895e7bbd3c0c6823366699b2417462fc0e" + url: "https://gitlab.com/sherlockvn/fcm_shared_isolate.git" + source: git version: "0.1.0" ffi: dependency: "direct overridden" @@ -710,26 +752,50 @@ packages: dependency: transitive description: name: firebase_core - sha256: "4f1d7c13a909e82ff026679c9b8493cdeb35a9c76bc46c42bf9e2240c9e57e80" + sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" url: "https://pub.dev" source: hosted - version: "1.24.0" + version: "2.32.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 + sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "5.2.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "839f1b48032a61962792cea1225fae030d4f27163867f181d6d2072dd40acbee" + sha256: e8d1e22de72cb21cdcfc5eed7acddab3e99cd83f3b317f54f7a96c32f25fd11e + url: "https://pub.dev" + source: hosted + version: "2.17.4" + firebase_messaging: + dependency: transitive + description: + name: firebase_messaging + sha256: a1662cc95d9750a324ad9df349b873360af6f11414902021f130c68ec02267c4 url: "https://pub.dev" source: hosted - version: "1.7.3" + version: "14.9.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "87c4a922cb6f811cfb7a889bdbb3622702443c52a0271636cbc90d813ceac147" + url: "https://pub.dev" + source: hosted + version: "4.5.37" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "0d34dca01a7b103ed7f20138bffbb28eb0e61a677bf9e78a028a932e2c7322d5" + url: "https://pub.dev" + source: hosted + version: "3.8.7" fixnum: dependency: transitive description: @@ -754,11 +820,12 @@ packages: flutter_app_badger: dependency: "direct main" description: - name: flutter_app_badger - sha256: "64d4a279bab862ed28850431b9b446b9820aaae0bf363322d51077419f930fa8" - url: "https://pub.dev" - source: hosted - version: "1.5.0" + path: "." + ref: master + resolved-ref: "0cc96b63db11b3a3615d2a798022fd35511fa4ac" + url: "https://github.com/bitsydarel/flutter_app_badger.git" + source: git + version: "1.5.2" flutter_app_lock: dependency: "direct main" description: @@ -1150,10 +1217,10 @@ packages: dependency: "direct main" description: name: flutter_ringtone_player - sha256: "0b036416fda0654da52221989bd1a8ccd2876cea57f61ecc3a4fc272bd738c67" + sha256: d0277a04e629a6582d776f5dcc2a879a733f7326ba073b872a9ccfbff9d9b51f url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0+3" flutter_rust_bridge: dependency: transitive description: @@ -1311,18 +1378,18 @@ packages: dependency: "direct overridden" description: name: geolocator_android - sha256: a4834a98fab5124f2d5b881e62a40ebb4a71d6aad6ad577e047a3ffb69b67dac - url: "https://hanntech-gmbh.gitlab.io/free2pass/flutter-geolocator-floss/" + sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47" + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "4.6.1" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface - sha256: "9d6f34a8a4b704d504f34acc5e52d880a7d2caedd99739902d6319179b0336d4" + sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "4.2.4" get_it: dependency: "direct main" description: @@ -1371,6 +1438,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" handy_window: dependency: "direct main" description: @@ -1470,10 +1545,11 @@ packages: image_gallery_saver: dependency: "direct main" description: - name: image_gallery_saver - sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816" - url: "https://pub.dev" - source: hosted + path: "." + ref: master + resolved-ref: "47f36a58a1e3cecf988dc83dfd10f7a8a5941e02" + url: "https://github.com/FlutterStudioIst/image_gallery_saver.git" + source: git version: "2.0.3" image_picker: dependency: "direct main" @@ -1686,18 +1762,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1731,6 +1807,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + logger: + dependency: transitive + description: + name: logger + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + url: "https://pub.dev" + source: hosted + version: "2.4.0" logging: dependency: transitive description: @@ -1783,10 +1867,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" matrix: dependency: "direct main" description: @@ -1898,10 +1982,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mgrs_dart: dependency: transitive description: @@ -1945,10 +2029,11 @@ packages: native_imaging: dependency: "direct main" description: - name: native_imaging - sha256: "182ccd8e0815a8a2158500ef66c828c030f6b9e05783e41e22f33bbcfd46a3d5" - url: "https://pub.dev" - source: hosted + path: "." + ref: main + resolved-ref: "4f7dd0b060686536003c5312a7634d695e772d35" + url: "https://gitlab.com/sherlockvn/native_imaging.git" + source: git version: "0.1.1" nested: dependency: transitive @@ -2170,10 +2255,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" platform_detect: dependency: transitive description: @@ -2337,12 +2422,11 @@ packages: receive_sharing_intent: dependency: "direct main" description: - path: "." - ref: "receive-.txt-v2" - resolved-ref: "7ac2cccb3e1ac4bddce7e287adcb69726f368b89" - url: "git@github.com:krabbenprgr/receive_sharing_intent.git" - source: git - version: "1.4.5" + name: receive_sharing_intent + sha256: f127989f8662ea15e193bd1e10605e5a0ab6bb92dffd51f3ce002feb0ce24c93 + url: "https://pub.dev" + source: hosted + version: "1.8.0" record: dependency: "direct main" description: @@ -2769,10 +2853,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timezone: dependency: transitive description: @@ -2829,30 +2913,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - uni_links: - dependency: "direct main" - description: - name: uni_links - sha256: "051098acfc9e26a9fde03b487bef5d3d228ca8f67693480c6f33fd4fbb8e2b6e" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - uni_links_platform_interface: - dependency: transitive - description: - name: uni_links_platform_interface - sha256: "929cf1a71b59e3b7c2d8a2605a9cf7e0b125b13bc858e55083d88c62722d4507" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - uni_links_web: - dependency: transitive - description: - name: uni_links_web - sha256: "7539db908e25f67de2438e33cc1020b30ab94e66720b5677ba6763b25f6394df" - url: "https://pub.dev" - source: hosted - version: "0.1.0" unicode: dependency: transitive description: @@ -3141,10 +3201,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" volume_controller: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fa41dbcbe5..1025701822 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,10 +14,7 @@ dependencies: url: git@github.com:linagora/matrix-dart-sdk.git ref: twake-supported-0.22.6 - receive_sharing_intent: - git: - url: git@github.com:krabbenprgr/receive_sharing_intent.git - ref: receive-.txt-v2 + receive_sharing_intent: 1.8.0 # TODO: Android native lib build error: https://github.com/jonataslaw/VideoCompress/issues/240 # video_compress: ^3.1.1 @@ -38,7 +35,7 @@ dependencies: contacts_service: git: url: git@github.com:linagora/flutter_contacts.git - ref: master + ref: upgrade-targetAndCompileSdkVersion # TODO: Remove it after this PR merged # https://github.com/justsoft/video_thumbnail/pull/135 @@ -64,7 +61,11 @@ dependencies: flutter_adaptive_scaffold: ^0.1.4 animations: ^2.0.7 blurhash_dart: ^1.1.0 - callkeep: ^0.3.2 + # TODO: remove it after it release a new version + callkeep: + git: + url: https://github.com/flutter-webrtc/callkeep.git + ref: master chewie: ^1.3.6 collection: ^1.16.0 connectivity_plus: ^3.0.2 @@ -77,11 +78,18 @@ dependencies: emoji_picker_flutter: ^1.5.1 emoji_proposal: ^0.0.1 emojis: ^0.9.9 - fcm_shared_isolate: ^0.1.0 + fcm_shared_isolate: + git: + url: https://gitlab.com/sherlockvn/fcm_shared_isolate.git + ref: main file_picker: ^8.0.5 flutter: sdk: flutter - flutter_app_badger: ^1.5.0 + # TODO: remove this after this PR is merged https://github.com/g123k/flutter_app_badger/pull/92 + flutter_app_badger: + git: + url: https://github.com/bitsydarel/flutter_app_badger.git + ref: master flutter_app_lock: ^3.0.0 flutter_blurhash: ^0.8.2 flutter_cache_manager: ^3.3.0 @@ -93,7 +101,7 @@ dependencies: # flutter_matrix_html: ^1.1.0 flutter_olm: ^1.2.0 flutter_openssl_crypto: ^0.3.0 - flutter_ringtone_player: ^3.1.1 + flutter_ringtone_player: 4.0.0+3 flutter_secure_storage: ^9.2.1 flutter_svg: ^0.22.0 flutter_typeahead: ^5.1.0 @@ -114,7 +122,10 @@ dependencies: latlong2: ^0.8.1 matrix_homeserver_recommendations: ^0.3.0 matrix_link_text: ^2.0.0 - native_imaging: ^0.1.0 + native_imaging: + git: + url: https://gitlab.com/sherlockvn/native_imaging.git + ref: main package_info_plus: ^8.0.0 path_provider: ^2.0.15 permission_handler: ^11.0.1 @@ -134,8 +145,8 @@ dependencies: url: https://github.com/alirezat66/skeletons.git ref: master tor_detector_web: ^1.1.0 - uni_links: ^0.5.1 - unifiedpush: ^5.0.1 + app_links: ^6.2.0 + unifiedpush: ^5.0.1 universal_html: ^2.0.8 url_launcher: ^6.0.20 vibration: ^1.7.4-nullsafety.0 @@ -163,7 +174,10 @@ dependencies: async: ^2.11.0 cached_network_image: ^3.2.3 flutter_image_compress: ^2.0.4 - image_gallery_saver: ^2.0.3 + image_gallery_saver: + git: + url: https://github.com/FlutterStudioIst/image_gallery_saver.git + ref: master file_saver: ^0.2.12 flutter_keyboard_visibility: ^6.0.0 media_kit: ^1.1.7 @@ -258,10 +272,7 @@ dependency_overrides: git: url: https://gitlab.com/TheOneWithTheBraid/flutter_secure_storage_windows.git ref: main - geolocator_android: - hosted: - name: geolocator_android - url: https://hanntech-gmbh.gitlab.io/free2pass/flutter-geolocator-floss + geolocator_android: 4.6.1 # waiting for null safety # Upstream pull request: https://github.com/AntoineMarcel/keyboard_shortcuts/pull/13 keyboard_shortcuts: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index fd7b1097da..758a8ece95 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +30,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); DesktopDropPluginRegisterWithRegistrar( @@ -42,6 +46,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSaverPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); FlutterAvifWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterAvifWindowsPlugin")); FlutterWebRTCPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index c03bed2529..13b0d4c970 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + app_links connectivity_plus desktop_drop desktop_lifecycle @@ -10,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST emoji_picker_flutter file_saver file_selector_windows + firebase_core flutter_avif_windows flutter_webrtc gal From 01527b8ec82ca8d089eecaebcf300c3a7085ee59 Mon Sep 17 00:00:00 2001 From: --global Date: Thu, 8 Aug 2024 15:21:25 +0700 Subject: [PATCH 29/40] TW-1926: remove the permission when paste in iOS --- docs/adr/0024-upgrade-flutter-3.24.md | 27 +++++++++++++ lib/pages/bootstrap/bootstrap_dialog.dart | 3 ++ .../tom_bootstrap_dialog_mobile_view.dart | 2 + lib/pages/chat/add_widget_tile_view.dart | 2 +- lib/pages/chat/input_bar/input_bar.dart | 9 ++--- .../chat_details/chat_details_edit_view.dart | 3 ++ lib/pages/chat_list/chat_list_header.dart | 2 + .../receive_sharing_intent_mixin.dart | 30 ++++---------- lib/pages/chat_search/chat_search_view.dart | 2 + .../homeserver_picker/homeserver_app_bar.dart | 2 + .../homeserver_picker_view.dart | 2 + lib/pages/login/login_view.dart | 3 ++ .../new_group/new_group_chat_info_view.dart | 2 + lib/pages/search/search_text_field.dart | 2 + .../settings_emotes/settings_emotes_view.dart | 4 ++ .../settings_ignore_list_view.dart | 2 + .../settings_profile_item.dart | 2 + lib/pages/share/share.dart | 3 +- lib/pages/story/story_view.dart | 2 + .../shared_media_file_extension.dart | 4 +- lib/utils/voip/callkeep_manager.dart | 35 +++++++++-------- lib/widgets/app_bars/searchable_app_bar.dart | 2 + ..._builder_ios_paste_without_permission.dart | 15 +++++++ .../app_adaptive_scaffold_body_view.dart | 1 + pubspec.lock | 39 ++++++++++--------- pubspec.yaml | 20 ++++++---- 26 files changed, 145 insertions(+), 75 deletions(-) create mode 100644 docs/adr/0024-upgrade-flutter-3.24.md create mode 100644 lib/widgets/context_menu_builder_ios_paste_without_permission.dart diff --git a/docs/adr/0024-upgrade-flutter-3.24.md b/docs/adr/0024-upgrade-flutter-3.24.md new file mode 100644 index 0000000000..9174d5eeac --- /dev/null +++ b/docs/adr/0024-upgrade-flutter-3.24.md @@ -0,0 +1,27 @@ +# 23. Flutter 3.24.0 Upgrade + +**Date:** 2024-08-26 + +## Status + +**Accepted** + +## Context + +To enable features like text pasting without requiring user permission on iOS, we need to upgrade to Flutter 3.24.0.[See detail](https://github.com/flutter/flutter/issues/103163#issuecomment-2320611190) + +## Decision + +During the upgrade to Flutter 3.24.0, some dependencies must also be updated to ensure successful release builds. Specifically, the `compileSdkVersion` needs to be updated to at least 31. Below is the list of packages that require a minimum `compileSdkVersion` of 31 or higher for successful compilation: + +- **`callkeep`**: Upgrade `compileSdkVersion` from 28 to 31. [See PR #190](https://github.com/flutter-webrtc/callkeep/pull/190) +- **`receive_sharing_intent`**: Switch to the version maintained by Linagora, which has `compileSdkVersion` set to 33. [See Linagora Repository](https://github.com/linagora/receive_sharing_intent) +- **`flutter_contacts`**: Upgrade `compileSdkVersion` from 31 to 33. [See PR #11](https://github.com/linagora/flutter_contacts/pull/11) +- **`fcm_shared_isolate`**: [See Merge Request](https://gitlab.com/famedly/company/frontend/libraries/fcm_shared_isolate/-/merge_requests/10) +- **`native_image`**: [See Merge Request](https://gitlab.com/famedly/company/frontend/libraries/native_imaging/-/merge_requests/22) +- **`flutter_app_badger`**: [See PR #92](https://github.com/g123k/flutter_app_badger/pull/92) +- **`image_gallery_saver`**: [See GitHub Repository](https://github.com/FlutterStudioIst/image_gallery_saver.git) + +Additionally, update `@UIApplicationMain` to `@main` in the codebase. For more information, refer to [Swift Evolution Proposal #0383](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md). + +The build debug version of window platform has not been success, due to the error tracking here: https://github.com/firebase/flutterfire/issues/12051. diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index 734147a0ba..143897e1f4 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/pages/bootstrap/tom_bootstrap_dialog.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/adaptive_flat_button.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -136,6 +137,7 @@ class BootstrapDialogState extends State { maxLines: 4, readOnly: true, style: GoogleFonts.robotoMono(), + contextMenuBuilder: mobileTwakeContextMenuBuilder, controller: TextEditingController(text: key), decoration: const InputDecoration( contentPadding: EdgeInsets.all(16), @@ -261,6 +263,7 @@ class BootstrapDialogState extends State { maxLines: 2, autocorrect: false, readOnly: _recoveryKeyInputLoading, + contextMenuBuilder: mobileTwakeContextMenuBuilder, autofillHints: _recoveryKeyInputLoading ? null : [AutofillHints.password], diff --git a/lib/pages/bootstrap/tom_bootstrap_dialog_mobile_view.dart b/lib/pages/bootstrap/tom_bootstrap_dialog_mobile_view.dart index 50a94fc7c5..0f8ded0a9d 100644 --- a/lib/pages/bootstrap/tom_bootstrap_dialog_mobile_view.dart +++ b/lib/pages/bootstrap/tom_bootstrap_dialog_mobile_view.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/pages/bootstrap/tom_bootstrap_dialog_style.dart'; import 'package:fluffychat/pages/chat_list/chat_list_header_style.dart'; import 'package:fluffychat/pages/chat_list/chat_list_skeletonizer_widget.dart'; import 'package:fluffychat/resource/image_paths.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -55,6 +56,7 @@ class TomBootstrapDialogMobileView extends StatelessWidget { child: TextField( textInputAction: TextInputAction.search, enabled: false, + contextMenuBuilder: mobileTwakeContextMenuBuilder, decoration: ChatListHeaderStyle.searchInputDecoration( context, hintText: '', diff --git a/lib/pages/chat/add_widget_tile_view.dart b/lib/pages/chat/add_widget_tile_view.dart index d7ac53ef2b..18153fabaa 100644 --- a/lib/pages/chat/add_widget_tile_view.dart +++ b/lib/pages/chat/add_widget_tile_view.dart @@ -59,7 +59,7 @@ class AddWidgetTileView extends StatelessWidget { ), ), ), - ButtonBar( + OverflowBar( children: [ TextButton( onPressed: controller.addWidget, diff --git a/lib/pages/chat/input_bar/input_bar.dart b/lib/pages/chat/input_bar/input_bar.dart index 1ebac66e21..2835cf3abe 100644 --- a/lib/pages/chat/input_bar/input_bar.dart +++ b/lib/pages/chat/input_bar/input_bar.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/presentation/mixins/paste_image_mixin.dart'; import 'package:fluffychat/utils/clipboard.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import 'package:flutter/material.dart'; @@ -445,12 +446,8 @@ class _InputBarState extends State with PasteImageMixin { widget.onChanged!(text); } }, - contextMenuBuilder: PlatformInfos.isWeb - ? null - : (_, editableTextState) => - AdaptiveTextSelectionToolbar.editableText( - editableTextState: editableTextState, - ), + contextMenuBuilder: + PlatformInfos.isWeb ? null : mobileTwakeContextMenuBuilder, onTap: () async { await Future.delayed(InputBar.debounceDurationTap); FocusScope.of(context).requestFocus(focusNode); diff --git a/lib/pages/chat_details/chat_details_edit_view.dart b/lib/pages/chat_details/chat_details_edit_view.dart index 5f64d3bd85..9ea1802228 100644 --- a/lib/pages/chat_details/chat_details_edit_view.dart +++ b/lib/pages/chat_details/chat_details_edit_view.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/pages/chat_details/chat_details_edit_view_style.dart' import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; @@ -319,6 +320,7 @@ class _GroupNameField extends StatelessWidget { child: TextField( style: ChatDetailEditViewStyle.textFieldStyle(context), controller: controller.groupNameTextEditingController, + contextMenuBuilder: mobileTwakeContextMenuBuilder, focusNode: controller.groupNameFocusNode, decoration: InputDecoration( border: OutlineInputBorder( @@ -370,6 +372,7 @@ class _DescriptionField extends StatelessWidget { TextField( style: ChatDetailEditViewStyle.textFieldStyle(context), controller: controller.descriptionTextEditingController, + contextMenuBuilder: mobileTwakeContextMenuBuilder, focusNode: controller.descriptionFocusNode, decoration: InputDecoration( border: OutlineInputBorder( diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index a0f3c174a8..8f8f1ecf2e 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list_header_style.dart'; import 'package:fluffychat/pages/search/search.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/swipe_to_dismiss_wrap.dart'; import 'package:fluffychat/widgets/twake_components/twake_header.dart'; import 'package:flutter/material.dart'; @@ -95,6 +96,7 @@ class ChatListHeader extends StatelessWidget { builder: (context, value, _) { return TextField( textInputAction: TextInputAction.search, + contextMenuBuilder: mobileTwakeContextMenuBuilder, enabled: false, decoration: ChatListHeaderStyle.searchInputDecoration( context, diff --git a/lib/pages/chat_list/receive_sharing_intent_mixin.dart b/lib/pages/chat_list/receive_sharing_intent_mixin.dart index 70e085750f..f7977fe5fb 100644 --- a/lib/pages/chat_list/receive_sharing_intent_mixin.dart +++ b/lib/pages/chat_list/receive_sharing_intent_mixin.dart @@ -96,38 +96,24 @@ mixin ReceiveSharingIntentMixin on State { if (!PlatformInfos.isMobile) return; // For sharing images coming from outside the app while the app is in the memory - intentFileStreamSubscription = ReceiveSharingIntent.instance - .getMediaStream() + intentFileStreamSubscription = ReceiveSharingIntent.getMediaStream() .listen(_processIncomingSharedFiles, onError: print); // For sharing images coming from outside the app while the app is closed - ReceiveSharingIntent.instance - .getInitialMedia() - .then(_processIncomingSharedFiles); - - ReceiveSharingIntent.instance.getMediaStream().listen((value) { - // TODO: Handle mutliple text sharing - if (value.isNotEmpty) { - if (value.first is String) { - _processIncomingSharedText(value.first as String); - } - } - }); + ReceiveSharingIntent.getInitialMedia().then(_processIncomingSharedFiles); + + // For sharing or opening urls/text coming from outside the app while the app is in the memory + intentDataStreamSubscription = ReceiveSharingIntent.getTextStream() + .listen(_processIncomingSharedText, onError: print); // For sharing or opening urls/text coming from outside the app while the app is closed - ReceiveSharingIntent.instance.getInitialMedia().then((value) { - // TODO: Handle mutliple text sharing - if (value.isNotEmpty) { - if (value.first is String) { - _processIncomingSharedText(value.first as String); - } - } - }); + ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText); // For receiving shared Uris final appLinks = AppLinks(); intentUriStreamSubscription = appLinks.stringLinkStream.listen(_processIncomingUris); + if (TwakeApp.gotInitialLink == false) { TwakeApp.gotInitialLink = true; appLinks.getInitialLinkString().then(_processIncomingUris); diff --git a/lib/pages/chat_search/chat_search_view.dart b/lib/pages/chat_search/chat_search_view.dart index 0c5dd2f38b..7876b72842 100644 --- a/lib/pages/chat_search/chat_search_view.dart +++ b/lib/pages/chat_search/chat_search_view.dart @@ -19,6 +19,7 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/result_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/string_extension.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/highlight_text.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/search/empty_search_widget.dart'; @@ -343,6 +344,7 @@ class _ChatSearchAppBar extends StatelessWidget { padding: ChatSearchStyle.inputPadding, child: TextField( controller: controller.textEditingController, + contextMenuBuilder: mobileTwakeContextMenuBuilder, focusNode: controller.inputFocus, textInputAction: TextInputAction.search, autofocus: true, diff --git a/lib/pages/homeserver_picker/homeserver_app_bar.dart b/lib/pages/homeserver_picker/homeserver_app_bar.dart index 81faa0dd8f..1b45a14f78 100644 --- a/lib/pages/homeserver_picker/homeserver_app_bar.dart +++ b/lib/pages/homeserver_picker/homeserver_app_bar.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -15,6 +16,7 @@ class HomeserverAppBar extends StatelessWidget { return TextField( focusNode: controller.homeserverFocusNode, controller: controller.homeserverController, + contextMenuBuilder: mobileTwakeContextMenuBuilder, onChanged: controller.onChanged, decoration: InputDecoration( prefixIcon: Navigator.of(context).canPop() diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index 0496152e0d..bda3625001 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -1,6 +1,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_state.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; @@ -155,6 +156,7 @@ class HomeserverTextField extends StatelessWidget { autocorrect: false, enabled: true, controller: controller.homeserverController, + contextMenuBuilder: mobileTwakeContextMenuBuilder, decoration: InputDecoration( border: OutlineInputBorder( borderSide: diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index 34993de54a..9ccaf416b5 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -40,6 +41,7 @@ class LoginView extends StatelessWidget { autocorrect: false, autofocus: true, onChanged: controller.checkWellKnownWithCoolDown, + contextMenuBuilder: mobileTwakeContextMenuBuilder, controller: controller.usernameController, textInputAction: TextInputAction.next, keyboardType: TextInputType.emailAddress, @@ -60,6 +62,7 @@ class LoginView extends StatelessWidget { autocorrect: false, autofillHints: controller.loading ? null : [AutofillHints.password], + contextMenuBuilder: mobileTwakeContextMenuBuilder, controller: controller.passwordController, textInputAction: TextInputAction.go, obscureText: !controller.showPassword, diff --git a/lib/pages/new_group/new_group_chat_info_view.dart b/lib/pages/new_group/new_group_chat_info_view.dart index 36a63e2a23..7ce1eae429 100644 --- a/lib/pages/new_group/new_group_chat_info_view.dart +++ b/lib/pages/new_group/new_group_chat_info_view.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/pages/new_group/new_group_chat_info_style.dart'; import 'package:fluffychat/pages/new_group/new_group_info_controller.dart'; import 'package:fluffychat/pages/new_group/widget/expansion_participants_list.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/int_extension.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/twake_components/twake_fab.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; @@ -228,6 +229,7 @@ class NewGroupChatInfoView extends StatelessWidget { ), contentPadding: NewGroupChatInfoStyle.contentPadding, ), + contextMenuBuilder: mobileTwakeContextMenuBuilder, ); }, ), diff --git a/lib/pages/search/search_text_field.dart b/lib/pages/search/search_text_field.dart index 9c358316b1..cb4ed3ed9e 100644 --- a/lib/pages/search/search_text_field.dart +++ b/lib/pages/search/search_text_field.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/pages/search/search_view_style.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; import 'package:fluffychat/pages/dialer/pip/dismiss_keyboard.dart'; @@ -28,6 +29,7 @@ class SearchTextField extends StatelessWidget { }, controller: textEditingController, textInputAction: TextInputAction.search, + contextMenuBuilder: mobileTwakeContextMenuBuilder, enabled: true, focusNode: focusNode, autofocus: autofocus, diff --git a/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart index 26929dfc54..6abdcf5658 100644 --- a/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/pages/settings_dashboard/settings/settings_app_bar.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -52,6 +53,7 @@ class EmotesSettingsView extends StatelessWidget { ), child: TextField( controller: controller.newImageCodeController, + contextMenuBuilder: mobileTwakeContextMenuBuilder, autocorrect: false, minLines: 1, maxLines: 1, @@ -158,6 +160,8 @@ class EmotesSettingsView extends StatelessWidget { child: TextField( readOnly: controller.readonly, controller: textEditingController, + contextMenuBuilder: + mobileTwakeContextMenuBuilder, autocorrect: false, minLines: 1, maxLines: 1, diff --git a/lib/pages/settings_dashboard/settings_ignore_list/settings_ignore_list_view.dart b/lib/pages/settings_dashboard/settings_ignore_list/settings_ignore_list_view.dart index c86b8da69d..7e83739f90 100644 --- a/lib/pages/settings_dashboard/settings_ignore_list/settings_ignore_list_view.dart +++ b/lib/pages/settings_dashboard/settings_ignore_list/settings_ignore_list_view.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/pages/settings_dashboard/settings/settings_app_bar.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -36,6 +37,7 @@ class SettingsIgnoreListView extends StatelessWidget { children: [ TextField( controller: controller.controller, + contextMenuBuilder: mobileTwakeContextMenuBuilder, autocorrect: false, textInputAction: TextInputAction.done, onSubmitted: (_) => controller.ignoreUser(context), diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart index a3054c9c05..53235e8ce4 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/app_state/success.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_item_style.dart'; import 'package:fluffychat/presentation/enum/settings/settings_profile_enum.dart'; import 'package:fluffychat/presentation/model/settings/settings_profile_presentation.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; @@ -70,6 +71,7 @@ class SettingsProfileItemBuilder extends StatelessWidget { onChange!(value, settingsProfileEnum), readOnly: !settingsProfilePresentation.isEditable, autofocus: false, + contextMenuBuilder: mobileTwakeContextMenuBuilder, focusNode: focusNode, controller: textEditingController, decoration: InputDecoration( diff --git a/lib/pages/share/share.dart b/lib/pages/share/share.dart index 70de9b41d2..1c3a40a461 100644 --- a/lib/pages/share/share.dart +++ b/lib/pages/share/share.dart @@ -74,7 +74,8 @@ class ShareController extends State ); final shareContentList = Matrix.of(context).shareContentList; final shareContent = Matrix.of(context).shareContent; - + Logs().d('ShareController::shareTo() shareContent: $shareContent'); + Logs().d('ShareController::shareTo() shareContentList: $shareContentList'); if (shareContentList.isNotEmpty) { _handleShareFilesContent( room: room, diff --git a/lib/pages/story/story_view.dart b/lib/pages/story/story_view.dart index 131ff35faa..3a2dcc0b9e 100644 --- a/lib/pages/story/story_view.dart +++ b/lib/pages/story/story_view.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -355,6 +356,7 @@ class StoryView extends StatelessWidget { onSubmitted: controller.replyAction, textInputAction: TextInputAction.send, readOnly: controller.replyLoading, + contextMenuBuilder: mobileTwakeContextMenuBuilder, decoration: InputDecoration( contentPadding: const EdgeInsets.fromLTRB(0, 16, 0, 16), diff --git a/lib/presentation/extensions/shared_media_file_extension.dart b/lib/presentation/extensions/shared_media_file_extension.dart index 5dcf70b706..3f064277be 100644 --- a/lib/presentation/extensions/shared_media_file_extension.dart +++ b/lib/presentation/extensions/shared_media_file_extension.dart @@ -6,14 +6,14 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart'; extension SharedMediaFileExtension on SharedMediaFile { MatrixFile toMatrixFile() { - if (type == SharedMediaType.image) { + if (type == SharedMediaType.IMAGE) { return MatrixImageFile( bytes: null, name: path.split("/").last, filePath: path, ); } - if (type == SharedMediaType.video) { + if (type == SharedMediaType.VIDEO) { Uint8List? thumbnailBytes; if (thumbnail != null) { thumbnailBytes = File(thumbnail!).readAsBytesSync(); diff --git a/lib/utils/voip/callkeep_manager.dart b/lib/utils/voip/callkeep_manager.dart index 696f86705a..479769c364 100644 --- a/lib/utils/voip/callkeep_manager.dart +++ b/lib/utils/voip/callkeep_manager.dart @@ -111,8 +111,7 @@ class CallKeepManager { Future showCallkitIncoming(CallSession call) async { if (!setupDone) { await _callKeep.setup( - null, - { + options: { 'ios': { 'appName': appName, }, @@ -201,12 +200,12 @@ class CallKeepManager { } Future setOnHold(String callUUID, bool held) async { - await _callKeep.setOnHold(callUUID, held); + await _callKeep.setOnHold(uuid: callUUID, shouldHold: held); setCallHeld(callUUID, held); } Future setMutedCall(String callUUID, bool muted) async { - await _callKeep.setMutedCall(callUUID, muted); + await _callKeep.setMutedCall(uuid: callUUID, shouldMute: muted); setCallMuted(callUUID, muted); } @@ -214,13 +213,13 @@ class CallKeepManager { // Workaround because Android doesn't display well displayName, se we have to switch ... if (isIOS) { await _callKeep.updateDisplay( - callUUID, + uuid: callUUID, callerName: 'New Name', handle: callUUID, ); } else { await _callKeep.updateDisplay( - callUUID, + uuid: callUUID, callerName: callUUID, handle: 'New Name', ); @@ -231,8 +230,8 @@ class CallKeepManager { final callKeeper = CallKeeper(this, call); addCall(call.callId, callKeeper); await _callKeep.displayIncomingCall( - call.callId, - '${call.room.getLocalizedDisplayname()} (Twake Chat)', + uuid: call.callId, + handle: '${call.room.getLocalizedDisplayname()} (Twake Chat)', callerName: '${call.room.getLocalizedDisplayname()} (Twake Chat)', handleType: 'number', hasVideo: call.type == CallType.kVideo, @@ -278,16 +277,18 @@ class CallKeepManager { } void openCallingAccountsPage(BuildContext context) async { - await _callKeep.setup(context, { - 'ios': { - 'appName': appName, + await _callKeep.setup( + options: { + 'ios': { + 'appName': appName, + }, + 'android': alertOptions, }, - 'android': alertOptions, - }); + ); final hasPhoneAccount = await _callKeep.hasPhoneAccount(); Logs().e(hasPhoneAccount.toString()); if (!hasPhoneAccount) { - await _callKeep.hasDefaultPhoneAccount(context, alertOptions); + await _callKeep.hasDefaultPhoneAccount(alertOptions); } else { await _callKeep.openPhoneAccounts(); } @@ -333,9 +334,9 @@ class CallKeepManager { addCall(callUUID, CallKeeper(this, call)); } await _callKeep.startCall( - callUUID, - event.callData.handle!, - event.callData.handle!, + uuid: callUUID, + handle: event.callData.handle!, + callerName: event.callData.handle!, ); Timer(const Duration(seconds: 1), () { _callKeep.setCurrentCallActive(callUUID); diff --git a/lib/widgets/app_bars/searchable_app_bar.dart b/lib/widgets/app_bars/searchable_app_bar.dart index 865bd56469..e4674a87d9 100644 --- a/lib/widgets/app_bars/searchable_app_bar.dart +++ b/lib/widgets/app_bars/searchable_app_bar.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/config/first_column_inner_routes.dart'; import 'package:fluffychat/pages/dialer/pip/dismiss_keyboard.dart'; +import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:fluffychat/widgets/app_bars/searchable_app_bar_style.dart'; import 'package:flutter/material.dart'; @@ -185,6 +186,7 @@ class SearchableAppBar extends StatelessWidget { focusNode: focusNode, autofocus: true, maxLines: SearchableAppBarStyle.textFieldMaxLines, + contextMenuBuilder: mobileTwakeContextMenuBuilder, buildCounter: ( BuildContext context, { required int currentLength, diff --git a/lib/widgets/context_menu_builder_ios_paste_without_permission.dart b/lib/widgets/context_menu_builder_ios_paste_without_permission.dart new file mode 100644 index 0000000000..d0cdf4e840 --- /dev/null +++ b/lib/widgets/context_menu_builder_ios_paste_without_permission.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +Widget mobileTwakeContextMenuBuilder( + BuildContext context, + EditableTextState editableTextState, +) { + if (SystemContextMenu.isSupported(context)) { + return SystemContextMenu.editableText( + editableTextState: editableTextState, + ); + } + return AdaptiveTextSelectionToolbar.editableText( + editableTextState: editableTextState, + ); +} diff --git a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart index 122a873283..2d5de0824d 100644 --- a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart +++ b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart @@ -61,6 +61,7 @@ class AppAdaptiveScaffoldBodyView extends StatelessWidget { builder: (context, activeNavigationBar, __) { return PopScope( canPop: activeNavigationBar == AdaptiveDestinationEnum.rooms, + // ignore: deprecated_member_use onPopInvoked: onPopInvoked, child: Row( children: [ diff --git a/pubspec.lock b/pubspec.lock index 1167d5d6f9..752e55431f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -205,10 +205,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "1414d6d733a85d8ad2f1dfcb3ea7945759e35a123cb99ccfac75d0758f75edfa" + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.4.12" build_runner_core: dependency: transitive description: @@ -262,10 +262,10 @@ packages: description: path: "." ref: master - resolved-ref: "7d8a0a6f1d40c926e2c287d8a1bdf089dd499cf6" - url: "https://github.com/flutter-webrtc/callkeep.git" + resolved-ref: a3c46737b4467898321c51affbbc03a153169d40 + url: "https://github.com/flutter-webrtc/callkeep" source: git - version: "0.3.3" + version: "0.4.0" camera: dependency: transitive description: @@ -406,8 +406,8 @@ packages: dependency: "direct main" description: path: "." - ref: upgrade-targetAndCompileSdkVersion - resolved-ref: a0862ae22bf07ab3cfed8b31724b990df84bcd29 + ref: master + resolved-ref: a26353c9d8ce23321f11ed9d3386844dc0b0df65 url: "git@github.com:linagora/flutter_contacts.git" source: git version: "0.6.3" @@ -648,8 +648,8 @@ packages: description: path: "." ref: main - resolved-ref: "908336895e7bbd3c0c6823366699b2417462fc0e" - url: "https://gitlab.com/sherlockvn/fcm_shared_isolate.git" + resolved-ref: dd834a577372b74df6d95b9a08d30e38a2d64bbc + url: "https://github.com/famedly/fcm_shared_isolate.git" source: git version: "0.1.0" ffi: @@ -2031,8 +2031,8 @@ packages: description: path: "." ref: main - resolved-ref: "4f7dd0b060686536003c5312a7634d695e772d35" - url: "https://gitlab.com/sherlockvn/native_imaging.git" + resolved-ref: d76335e2039c041585df8103f5d4f5924e9e2add + url: "https://github.com/famedly/dart_native_imaging" source: git version: "0.1.1" nested: @@ -2407,10 +2407,10 @@ packages: dependency: transitive description: name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" random_string: dependency: transitive description: @@ -2422,11 +2422,12 @@ packages: receive_sharing_intent: dependency: "direct main" description: - name: receive_sharing_intent - sha256: f127989f8662ea15e193bd1e10605e5a0ab6bb92dffd51f3ce002feb0ce24c93 - url: "https://pub.dev" - source: hosted - version: "1.8.0" + path: "." + ref: master + resolved-ref: f23f7fb0fad25ae80a88350ad8654851f1ee682f + url: "https://github.com/linagora/receive_sharing_intent.git" + source: git + version: "1.4.5" record: dependency: "direct main" description: @@ -3383,5 +3384,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.5.0-259.0.dev <4.0.0" flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1025701822..a9e5ddc7c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,10 @@ dependencies: url: git@github.com:linagora/matrix-dart-sdk.git ref: twake-supported-0.22.6 - receive_sharing_intent: 1.8.0 + receive_sharing_intent: + git: + url: https://github.com/linagora/receive_sharing_intent.git + ref: master # TODO: Android native lib build error: https://github.com/jonataslaw/VideoCompress/issues/240 # video_compress: ^3.1.1 @@ -35,7 +38,7 @@ dependencies: contacts_service: git: url: git@github.com:linagora/flutter_contacts.git - ref: upgrade-targetAndCompileSdkVersion + ref: master # TODO: Remove it after this PR merged # https://github.com/justsoft/video_thumbnail/pull/135 @@ -61,10 +64,10 @@ dependencies: flutter_adaptive_scaffold: ^0.1.4 animations: ^2.0.7 blurhash_dart: ^1.1.0 - # TODO: remove it after it release a new version + # TODO: remove it after it release a new version, the PR is merged to master callkeep: git: - url: https://github.com/flutter-webrtc/callkeep.git + url: https://github.com/flutter-webrtc/callkeep ref: master chewie: ^1.3.6 collection: ^1.16.0 @@ -78,9 +81,10 @@ dependencies: emoji_picker_flutter: ^1.5.1 emoji_proposal: ^0.0.1 emojis: ^0.9.9 + # TODO: remove this after this PR is merged https://gitlab.com/famedly/company/frontend/libraries/native_imaging/-/merge_requests/22 fcm_shared_isolate: git: - url: https://gitlab.com/sherlockvn/fcm_shared_isolate.git + url: https://github.com/famedly/fcm_shared_isolate.git ref: main file_picker: ^8.0.5 flutter: @@ -122,9 +126,10 @@ dependencies: latlong2: ^0.8.1 matrix_homeserver_recommendations: ^0.3.0 matrix_link_text: ^2.0.0 + # TODO: remove this after this PR is merged https://gitlab.com/famedly/company/frontend/libraries/native_imaging/-/merge_requests/22 native_imaging: git: - url: https://gitlab.com/sherlockvn/native_imaging.git + url: https://github.com/famedly/dart_native_imaging ref: main package_info_plus: ^8.0.0 path_provider: ^2.0.15 @@ -174,6 +179,7 @@ dependencies: async: ^2.11.0 cached_network_image: ^3.2.3 flutter_image_compress: ^2.0.4 + # TODO: remove when https://github.com/hui-z/image_gallery_saver/pull/310 is merged image_gallery_saver: git: url: https://github.com/FlutterStudioIst/image_gallery_saver.git @@ -202,7 +208,7 @@ dependencies: heif_converter: 1.0.0 dev_dependencies: - build_runner: ^2.3.3 + build_runner: 2.4.12 flutter_launcher_icons: ^0.13.1 flutter_lints: ^3.0.2 flutter_native_splash: ^2.0.3+1 From 2c905f8d950ed9a96814b59dfdc587c8b9694001 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA <31937920+Te-Z@users.noreply.github.com> Date: Fri, 13 Sep 2024 08:51:01 +0200 Subject: [PATCH 30/40] TW-1963: update search keyword matching (#1983) --- .../model/room/room_list_extension.dart | 22 +++++-- .../search_contacts_and_chats_controller.dart | 62 ++++++++++++------- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/lib/domain/model/room/room_list_extension.dart b/lib/domain/model/room/room_list_extension.dart index db1d5134b7..ce127227ca 100644 --- a/lib/domain/model/room/room_list_extension.dart +++ b/lib/domain/model/room/room_list_extension.dart @@ -3,6 +3,22 @@ import 'package:fluffychat/domain/model/search/recent_chat_model.dart'; import 'package:matrix/matrix.dart'; extension RoomListExtension on List { + bool _matchedMatrixId(RecentChatSearchModel model, String keyword) { + return model.directChatMatrixID + ?.toLowerCase() + .contains(keyword.toLowerCase()) ?? + false; + } + + bool _matchedName(RecentChatSearchModel model, String keyword) { + return model.displayName?.toLowerCase().contains(keyword.toLowerCase()) ?? + false; + } + + bool _matchedNameOrMatrixId(RecentChatSearchModel model, String keyword) { + return _matchedName(model, keyword) || _matchedMatrixId(model, keyword); + } + List searchRecentChat({ required MatrixLocalizations matrixLocalizations, required String keyword, @@ -13,11 +29,7 @@ extension RoomListExtension on List { ) .map((room) => room.toRecentChatSearchModel(matrixLocalizations)) .where( - (model) => - model.displayName != null && - model.displayName!.toLowerCase().contains( - keyword.toLowerCase(), - ), + (model) => _matchedNameOrMatrixId(model, keyword), ) .take(limit ?? length) .toList(); diff --git a/lib/pages/search/search_contacts_and_chats_controller.dart b/lib/pages/search/search_contacts_and_chats_controller.dart index 3b58fd1f41..a12e50664c 100644 --- a/lib/pages/search/search_contacts_and_chats_controller.dart +++ b/lib/pages/search/search_contacts_and_chats_controller.dart @@ -83,27 +83,8 @@ class SearchContactsAndChatsController with SearchDebouncerMixin, SearchMixin { .toList(); final tomContactPresentationSearchMatched = tomPresentationSearchContacts .expand((contact) => contact.toPresentationSearch()) - .where((contact) { - if (contact is! ContactPresentationSearch) { - return false; - } - - if (contact.displayName == null) { - return false; - } - - if (contact.email == null) { - return false; - } - - final matchedName = - contact.displayName!.toLowerCase().contains(keyword.toLowerCase()); - - final matchedEmail = - contact.email!.toLowerCase().contains(keyword.toLowerCase()); - - return matchedName || matchedEmail; - }).toList(); + .where((contact) => _doesMatchKeyword(contact, keyword)) + .toList(); _searchRecentChatInteractor .execute( keyword: keyword, @@ -126,6 +107,45 @@ class SearchContactsAndChatsController with SearchDebouncerMixin, SearchMixin { ); } + bool _matchedMatrixId(PresentationSearch contact, String keyword) { + return contact.directChatMatrixID + ?.toLowerCase() + .contains(keyword.toLowerCase()) ?? + false; + } + + bool _matchedName(PresentationSearch contact, String keyword) { + return contact.displayName?.toLowerCase().contains(keyword.toLowerCase()) ?? + false; + } + + bool _matchedEmail(PresentationSearch contact, String keyword) { + return contact.email?.toLowerCase().contains(keyword.toLowerCase()) ?? + false; + } + + bool _matchedContactInfo(PresentationSearch contact, String keyword) { + return _matchedName(contact, keyword) || + _matchedEmail(contact, keyword) || + _matchedMatrixId(contact, keyword); + } + + bool _doesMatchKeyword(PresentationSearch contact, String keyword) { + if (contact is! ContactPresentationSearch) { + return false; + } + + if (contact.displayName == null) { + return false; + } + + if (contact.email == null) { + return false; + } + + return _matchedContactInfo(contact, keyword); + } + void onSearchBarChanged(String keyword) { setDebouncerValue(keyword); } From 5558fd50e4d0809ce86f2dbeee503739af6a1b4e Mon Sep 17 00:00:00 2001 From: "quanghnguyen@linagora.com" Date: Tue, 10 Sep 2024 23:53:54 +0700 Subject: [PATCH 31/40] TW-1878: Fix can't open notification when open Media in chat details --- .../receive_sharing_intent_mixin.dart | 22 ++++++------- lib/utils/background_push.dart | 32 ++++++++++++++----- .../app_adaptive_scaffold_body.dart | 13 ++++++++ .../app_adaptive_scaffold_body_args.dart | 2 +- .../agruments/receive_content_args.dart | 17 ++++++++++ lib/widgets/twake_app.dart | 6 ++++ 6 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 lib/widgets/layouts/agruments/receive_content_args.dart diff --git a/lib/pages/chat_list/receive_sharing_intent_mixin.dart b/lib/pages/chat_list/receive_sharing_intent_mixin.dart index f7977fe5fb..a559dc1154 100644 --- a/lib/pages/chat_list/receive_sharing_intent_mixin.dart +++ b/lib/pages/chat_list/receive_sharing_intent_mixin.dart @@ -4,6 +4,8 @@ import 'package:fluffychat/pages/share/share.dart'; import 'package:fluffychat/presentation/extensions/shared_media_file_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/url_launcher.dart'; +import 'package:fluffychat/widgets/layouts/agruments/receive_content_args.dart'; +import 'package:fluffychat/widgets/layouts/enum/adaptive_destinations_enum.dart'; import 'package:fluffychat/widgets/twake_app.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -40,11 +42,17 @@ mixin ReceiveSharingIntentMixin on State { } void openSharePage() { - if (isCurrentPageIsNotRooms()) { + if (TwakeApp.isCurrentPageIsNotRooms()) { return; } - if (isCurrentPageIsInRooms()) { - TwakeApp.router.go('/rooms'); + if (TwakeApp.isCurrentPageIsInRooms()) { + TwakeApp.router.go( + '/rooms', + extra: ReceiveContentArgs( + newActiveClient: matrixState.client, + activeDestination: AdaptiveDestinationEnum.rooms, + ), + ); } Navigator.of(TwakeApp.routerKey.currentContext!).push( @@ -54,14 +62,6 @@ mixin ReceiveSharingIntentMixin on State { ); } - bool isCurrentPageIsInRooms() => - TwakeApp.router.routeInformationProvider.value.uri.path - .startsWith('/rooms/'); - - bool isCurrentPageIsNotRooms() => - !TwakeApp.router.routeInformationProvider.value.uri.path - .startsWith('/rooms'); - void _processIncomingSharedText(String? text) { if (text == null) return; if (_intentOpenApp(text)) { diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index e91a0e454c..1387ba1631 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -27,6 +27,8 @@ import 'package:fluffychat/presentation/extensions/client_extension.dart'; import 'package:fluffychat/presentation/extensions/go_router_extensions.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; import 'package:fluffychat/utils/push_helper.dart'; +import 'package:fluffychat/widgets/layouts/agruments/receive_content_args.dart'; +import 'package:fluffychat/widgets/layouts/enum/adaptive_destinations_enum.dart'; import 'package:fluffychat/widgets/twake_app.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -387,7 +389,7 @@ class BackgroundPush { }) async { try { Logs().v('[Push] Attempting to go to room $roomId...'); - _clearAllNavigatorAvailable(roomId: roomId); + await _clearAllNavigatorAvailable(roomId: roomId); if (_matrixState == null || roomId == null) { return; } @@ -626,9 +628,27 @@ class BackgroundPush { ); } - void _clearAllNavigatorAvailable({ + Future _handleInnerNavigation() async { + if (TwakeApp.isCurrentPageIsNotRooms()) { + return; + } + + if (TwakeApp.isCurrentPageIsInRooms()) { + Logs().d("BackgroundPush::_handleInnerNavigation(): CurrentRoomActive"); + TwakeApp.router.go( + '/rooms', + extra: ReceiveContentArgs( + newActiveClient: client, + activeDestination: AdaptiveDestinationEnum.rooms, + ), + ); + await Future.delayed(const Duration(milliseconds: 500)); + } + } + + Future _clearAllNavigatorAvailable({ String? roomId, - }) { + }) async { Logs().d( "BackgroundPush:: - Current active room id ${TwakeApp.router.activeRoomId}", ); @@ -636,11 +656,7 @@ class BackgroundPush { return; } - final canPopNavigation = TwakeApp.router.routerDelegate.canPop(); - Logs().d("BackgroundPush:: - Can pop other Navigation $canPopNavigation"); - if (canPopNavigation) { - TwakeApp.router.routerDelegate.pop(); - } + await _handleInnerNavigation(); } void _handleRedirectRoom( diff --git a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart index 5ed048d058..b9520d3de3 100644 --- a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart +++ b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/widgets/layouts/adaptive_layout/app_adaptive_scaffold import 'package:fluffychat/widgets/layouts/agruments/app_adaptive_scaffold_body_args.dart'; import 'package:fluffychat/widgets/layouts/agruments/logged_in_other_account_body_args.dart'; import 'package:fluffychat/widgets/layouts/agruments/logout_body_args.dart'; +import 'package:fluffychat/widgets/layouts/agruments/receive_content_args.dart'; import 'package:fluffychat/widgets/layouts/agruments/switch_active_account_body_args.dart'; import 'package:fluffychat/widgets/layouts/enum/adaptive_destinations_enum.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -151,6 +152,14 @@ class AppAdaptiveScaffoldBodyController extends State }); } + void _handleReceiveContent(ReceiveContentArgs args) { + if (args.activeDestination == null) return; + if (args.activeDestination != AdaptiveDestinationEnum.rooms) { + activeNavigationBarNotifier.value = AdaptiveDestinationEnum.rooms; + pageController.jumpToPage(AdaptiveDestinationEnum.rooms.index); + } + } + MatrixState get matrix => Matrix.of(context); @override @@ -185,6 +194,10 @@ class AppAdaptiveScaffoldBodyController extends State widget.args is SwitchActiveAccountBodyArgs) { _handleSwitchAccount(oldWidget); } + + if (widget.args is ReceiveContentArgs) { + _handleReceiveContent(widget.args as ReceiveContentArgs); + } super.didUpdateWidget(oldWidget); } diff --git a/lib/widgets/layouts/agruments/app_adaptive_scaffold_body_args.dart b/lib/widgets/layouts/agruments/app_adaptive_scaffold_body_args.dart index c35cdc65d7..62f2bd2796 100644 --- a/lib/widgets/layouts/agruments/app_adaptive_scaffold_body_args.dart +++ b/lib/widgets/layouts/agruments/app_adaptive_scaffold_body_args.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:matrix/matrix.dart'; -abstract class AbsAppAdaptiveScaffoldBodyArgs extends Equatable { +abstract class AbsAppAdaptiveScaffoldBodyArgs with EquatableMixin { final Client? newActiveClient; const AbsAppAdaptiveScaffoldBodyArgs({ diff --git a/lib/widgets/layouts/agruments/receive_content_args.dart b/lib/widgets/layouts/agruments/receive_content_args.dart new file mode 100644 index 0000000000..c61cc5121b --- /dev/null +++ b/lib/widgets/layouts/agruments/receive_content_args.dart @@ -0,0 +1,17 @@ +import 'package:fluffychat/widgets/layouts/agruments/app_adaptive_scaffold_body_args.dart'; +import 'package:fluffychat/widgets/layouts/enum/adaptive_destinations_enum.dart'; + +class ReceiveContentArgs extends AbsAppAdaptiveScaffoldBodyArgs { + const ReceiveContentArgs({ + required super.newActiveClient, + this.activeDestination, + }); + + final AdaptiveDestinationEnum? activeDestination; + + @override + List get props => [ + newActiveClient, + activeDestination, + ]; +} diff --git a/lib/widgets/twake_app.dart b/lib/widgets/twake_app.dart index f998ad1de1..b3e3a9e175 100644 --- a/lib/widgets/twake_app.dart +++ b/lib/widgets/twake_app.dart @@ -44,6 +44,12 @@ class TwakeApp extends StatefulWidget { }, ); + static bool isCurrentPageIsInRooms() => + router.routeInformationProvider.value.uri.path.startsWith('/rooms/'); + + static bool isCurrentPageIsNotRooms() => + !router.routeInformationProvider.value.uri.path.startsWith('/rooms'); + @override TwakeAppState createState() => TwakeAppState(); } From 4799eb0ef012007be66a7392f0552a3de73e9ac4 Mon Sep 17 00:00:00 2001 From: "quanghnguyen@linagora.com" Date: Fri, 13 Sep 2024 10:15:23 +0700 Subject: [PATCH 32/40] TW-2022: Change explain text in permission dialog --- assets/l10n/intl_en.arb | 10 +++--- lib/pages/chat/chat_app_bar_title.dart | 2 +- .../mixins/common_media_picker_mixin.dart | 4 +-- .../contacts_view_controller_mixin.dart | 1 + ..._file_to_twake_downloads_folder_mixin.dart | 1 + .../save_media_to_gallery_android_mixin.dart | 1 + lib/utils/permission_dialog.dart | 36 +++++++++---------- lib/utils/permission_service.dart | 3 ++ .../contacts_warning_banner_view.dart | 2 +- ...andle_download_and_preview_file_mixin.dart | 12 ++++--- 10 files changed, 41 insertions(+), 31 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index d6d711d698..d0c58690bd 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1180,7 +1180,7 @@ "type": "text", "placeholders": {} }, - "loading": "Loading status...", + "loadingStatus": "Loading status...", "loadMore": "Load more…", "@loadMore": { "type": "text", @@ -3041,10 +3041,10 @@ } } }, - "explainPermissionToAccessContacts": "Twake chat collects your contacts only when you are on a contact search screen to find out whether your friends are on the Matrix server, enabling connection with them. Your contacts are not synchronized with our server.", - "explainPermissionToAccessMedias": "Twake chat collects your photos, videos, music, and other documents to enable sending and saving selected files. Please go to Settings > Permissions and activate Storage permission: Photos and Videos.", - "explainPermissionToAccessPhotos": "Twake chat collects your photos to enable sending and saving selected photos only when the app is in use. Press Settings > Permissions, then enable Storage permission: Photos.", - "explainPermissionToAccessVideos": "Twake chat collects your videos to enable sending and saving selected videos only when the app is in use. Press Settings > Permissions, then enable Storage permission: Videos.", + "explainPermissionToAccessContacts": "Twake Chat DOES NOT collect your contacts. Twake Chat sends only contact hashes to the Twake Chat servers to understand who from your friends already joined Twake Chat, enabling connection with them. Your contacts ARE NOT synchronized with our server.", + "explainPermissionToAccessMedias": "Twake Chat does not synchronize data between your device and our servers. We only store media that you have sent to the chat room. All media files sent to chat are encrypted and stored securely. Go to Settings > Permissions and activate the Storage: Photos and Videos permission. You can also deny access to your media library at any time.", + "explainPermissionToAccessPhotos": "Twake Chat does not synchronize data between your device and our servers. We only store media that you have sent to the chat room. All media files sent to chat are encrypted and stored securely. Go to Settings > Permissions and activate the Storage: Photos permission. You can also deny access to your media library at any time.", + "explainPermissionToAccessVideos": "Twake Chat does not synchronize data between your device and our servers. We only store media that you have sent to the chat room. All media files sent to chat are encrypted and stored securely. Go to Settings > Permissions and activate the Storage: Videos permission. You can also deny access to your media library at any time.", "downloading": "Downloading", "settingUpYourTwake": "Setting up your Twake\nIt could take a while", "performingAutomaticalLogin": "Performing automatical login via SSO", diff --git a/lib/pages/chat/chat_app_bar_title.dart b/lib/pages/chat/chat_app_bar_title.dart index cb47435d7c..8125ec8505 100644 --- a/lib/pages/chat/chat_app_bar_title.dart +++ b/lib/pages/chat/chat_app_bar_title.dart @@ -196,7 +196,7 @@ class _DirectChatAppBarStatusContent extends StatelessWidget { } if (directChatPresence == null) { return ChatAppBarTitleText( - text: L10n.of(context)!.loading, + text: L10n.of(context)!.loadingStatus, ); } final typingText = room.getLocalizedTypingText(context); diff --git a/lib/presentation/mixins/common_media_picker_mixin.dart b/lib/presentation/mixins/common_media_picker_mixin.dart index 19f43c81d9..a5e626381b 100644 --- a/lib/presentation/mixins/common_media_picker_mixin.dart +++ b/lib/presentation/mixins/common_media_picker_mixin.dart @@ -40,11 +40,11 @@ mixin CommonMediaPickerMixin { text: isMicrophone ? L10n.of(context)!.tapToAllowAccessToYourMicrophone : L10n.of(context)!.tapToAllowAccessToYourCamera, - style: Theme.of(context).textTheme.titleSmall, + style: Theme.of(context).textTheme.bodyMedium, children: [ TextSpan( text: ' ${L10n.of(context)!.twake}.', - style: Theme.of(context).textTheme.titleSmall!.copyWith( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontWeight: FontWeight.bold, ), ), diff --git a/lib/presentation/mixins/contacts_view_controller_mixin.dart b/lib/presentation/mixins/contacts_view_controller_mixin.dart index c5528fadea..224471545c 100644 --- a/lib/presentation/mixins/contacts_view_controller_mixin.dart +++ b/lib/presentation/mixins/contacts_view_controller_mixin.dart @@ -87,6 +87,7 @@ mixin class ContactsViewControllerMixin { permission: Permission.contacts, explainTextRequestPermission: Text( L10n.of(context)!.explainPermissionToAccessContacts, + style: Theme.of(context).textTheme.bodyMedium, ), onRefuseTap: _handleDenyPermissionDialog, onAcceptButton: () async { diff --git a/lib/presentation/mixins/save_file_to_twake_downloads_folder_mixin.dart b/lib/presentation/mixins/save_file_to_twake_downloads_folder_mixin.dart index e2b5cd7299..0d8d310abc 100644 --- a/lib/presentation/mixins/save_file_to_twake_downloads_folder_mixin.dart +++ b/lib/presentation/mixins/save_file_to_twake_downloads_folder_mixin.dart @@ -244,6 +244,7 @@ mixin SaveFileToTwakeAndroidDownloadsFolderMixin { L10n.of(context)!.explainPermissionToDownloadFiles( AppConfig.applicationName, ), + style: Theme.of(context).textTheme.bodyMedium, ), onAcceptButton: () => PermissionHandlerService().goToSettingsForPermissionActions(), diff --git a/lib/presentation/mixins/save_media_to_gallery_android_mixin.dart b/lib/presentation/mixins/save_media_to_gallery_android_mixin.dart index cee82bc2e5..4e8405688b 100644 --- a/lib/presentation/mixins/save_media_to_gallery_android_mixin.dart +++ b/lib/presentation/mixins/save_media_to_gallery_android_mixin.dart @@ -137,6 +137,7 @@ mixin SaveMediaToGalleryAndroidMixin L10n.of(context)!.explainPermissionToGallery( AppConfig.applicationName, ), + style: Theme.of(context).textTheme.bodyMedium, ), onAcceptButton: () => permissionHandlerService.goToSettingsForPermissionActions(), diff --git a/lib/utils/permission_dialog.dart b/lib/utils/permission_dialog.dart index c935dac392..0afc55a7f7 100644 --- a/lib/utils/permission_dialog.dart +++ b/lib/utils/permission_dialog.dart @@ -53,25 +53,24 @@ class _PermissionDialogState extends State borderRadius: BorderRadius.circular(28.0), color: Theme.of(context).colorScheme.surface, ), - width: 312, - height: 280, + width: MediaQuery.sizeOf(context).width * 0.85, padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (widget.icon != null) ...[ + child: IntrinsicHeight( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.icon != null) ...[ + const SizedBox( + height: 24.0, + ), + widget.icon!, + ], const SizedBox( - height: 24.0, + height: 16.0, ), - widget.icon!, - ], - const SizedBox( - height: 16.0, - ), - widget.explainTextRequestPermission, - const SizedBox(height: 24.0), - Expanded( - child: Row( + widget.explainTextRequestPermission, + const SizedBox(height: 24.0), + Row( mainAxisAlignment: MainAxisAlignment.end, children: [ _PermissionTextButton( @@ -97,8 +96,9 @@ class _PermissionDialogState extends State ), ], ), - ), - ], + const SizedBox(height: 24.0), + ], + ), ), ), ), diff --git a/lib/utils/permission_service.dart b/lib/utils/permission_service.dart index 81ff632b36..6a44251159 100644 --- a/lib/utils/permission_service.dart +++ b/lib/utils/permission_service.dart @@ -94,6 +94,7 @@ class PermissionHandlerService { permission: Permission.photos, explainTextRequestPermission: Text( L10n.of(context)!.explainPermissionToAccessPhotos, + style: Theme.of(context).textTheme.bodyMedium, ), onAcceptButton: () async { Navigator.of(dialogContext).pop(true); @@ -119,6 +120,7 @@ class PermissionHandlerService { permission: Permission.videos, explainTextRequestPermission: Text( L10n.of(context)!.explainPermissionToAccessVideos, + style: Theme.of(context).textTheme.bodyMedium, ), onAcceptButton: () async { Navigator.of(dialogContext).pop(true); @@ -161,6 +163,7 @@ class PermissionHandlerService { Platform.isIOS ? Permission.photos : Permission.storage, explainTextRequestPermission: Text( L10n.of(context)!.explainPermissionToAccessMedias, + style: Theme.of(context).textTheme.bodyMedium, ), onAcceptButton: () async { Navigator.of(dialogContext).pop(true); diff --git a/lib/widgets/contacts_warning_banner/contacts_warning_banner_view.dart b/lib/widgets/contacts_warning_banner/contacts_warning_banner_view.dart index 51cb19d477..85f01850ee 100644 --- a/lib/widgets/contacts_warning_banner/contacts_warning_banner_view.dart +++ b/lib/widgets/contacts_warning_banner/contacts_warning_banner_view.dart @@ -42,7 +42,7 @@ class ContactsWarningBannerView extends StatelessWidget { Padding( padding: ContactsWarningBannerStyle.paddingForContentBanner, child: Text( - L10n.of(context)!.contactsWarningBannerTitle, + L10n.of(context)!.explainPermissionToAccessContacts, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface, ), diff --git a/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart b/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart index d1ab88c892..a48d80df0a 100644 --- a/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart +++ b/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart @@ -62,8 +62,10 @@ mixin HandleDownloadAndPreviewFileMixin { builder: (context) { return PermissionDialog( permission: Permission.storage, - explainTextRequestPermission: - Text(L10n.of(context)!.explainStoragePermission), + explainTextRequestPermission: Text( + L10n.of(context)!.explainStoragePermission, + style: Theme.of(context).textTheme.bodyMedium, + ), icon: const Icon(Icons.preview_outlined), ); }, @@ -89,8 +91,10 @@ mixin HandleDownloadAndPreviewFileMixin { builder: (context) { return PermissionDialog( permission: Permission.storage, - explainTextRequestPermission: - Text(L10n.of(context)!.explainGoToStorageSetting), + explainTextRequestPermission: Text( + L10n.of(context)!.explainGoToStorageSetting, + style: Theme.of(context).textTheme.bodyMedium, + ), icon: const Icon(Icons.preview_outlined), ); }, From 6047fd2fdd402a50ff55beb814d7a94252279e7d Mon Sep 17 00:00:00 2001 From: "quanghnguyen@linagora.com" Date: Fri, 13 Sep 2024 14:42:29 +0700 Subject: [PATCH 33/40] fixup! TW-2022: Change explain text in permission dialog --- assets/l10n/intl_fr.arb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index 7081bb49dd..809de49711 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -2714,10 +2714,10 @@ "count": {} } }, - "explainPermissionToAccessContacts": "Twake chat collecte vos contacts uniquement lorsque vous êtes sur un écran de recherche de contacts pour vérifier si vos amis sont sur le serveur Matrix, ce qui permet de se connecter avec eux. Vos contacts ne sont pas synchronisés avec notre serveur.", - "explainPermissionToAccessMedias": "Twake chat collecte vos photos, vidéos, musiques et autres documents pour permettre l'envoi et la sauvegarde des fichiers sélectionnés. Veuillez aller dans Paramètres > Autorisations et activer l'autorisation de stockage : Photos et vidéos.", - "explainPermissionToAccessPhotos": "Twake chat collecte vos photos pour permettre l'envoi et la sauvegarde des photos sélectionnées uniquement lorsque l'application est en cours d'utilisation. Appuyez sur Paramètres > Autorisations, puis activez l'autorisation de stockage : Photos.", - "explainPermissionToAccessVideos": "Twake chat collecte vos vidéos pour permettre l'envoi et la sauvegarde des vidéos sélectionnées uniquement lorsque l'application est en cours d'utilisation. Appuyez sur Paramètres > Autorisations, puis activez l'autorisation de stockage : Vidéos.", + "explainPermissionToAccessContacts": "", + "explainPermissionToAccessMedias": "", + "explainPermissionToAccessPhotos": "", + "explainPermissionToAccessVideos": "", "recentChat": "DISCUSSION RÉCENTE", "@recentChat": {}, "muteThisMessage": "Couper le son de ce salon", From 4e02e8aa58945545f9383d8458b5bc25f1d3c8e9 Mon Sep 17 00:00:00 2001 From: Dat PHAM HOANG Date: Fri, 13 Sep 2024 17:53:04 +0700 Subject: [PATCH 34/40] Bump version to v2.6.3 to handle the reject from Play Store --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a9e5ddc7c1..69e389d97c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fluffychat description: A convenient Matrix-based tool for personal and corporate communication. publish_to: none -version: 2.6.0+2330 +version: 2.6.3+2330 environment: sdk: ">=3.1.3 <4.0.0" From 5d9d5dbde5ce8733861c57f9acdb738cd9209f61 Mon Sep 17 00:00:00 2001 From: "khaled.njim" Date: Thu, 5 Sep 2024 00:21:15 +0100 Subject: [PATCH 35/40] TW-1817 added french and russian translation in camera screen --- assets/l10n/intl_en.arb | 18 ++++- assets/l10n/intl_fr.arb | 19 ++++- assets/l10n/intl_ru.arb | 19 ++++- .../mixins/common_media_picker_mixin.dart | 26 +++++- ...localized_camera_picker_text_delegate.dart | 80 +++++++++++++++++++ 5 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 lib/utils/localized_camera_picker_text_delegate.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index d0c58690bd..fb992e94f3 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3086,5 +3086,21 @@ "byContinuingYourAgreeingToOur": "By continuing, you're agreeing to our", "youDontHaveAnyContactsYet": "You dont have any contacts yet.", "loading": "Loading...", - "errorDialogTitle": "Oops, something went wrong" + "errorDialogTitle": "Oops, something went wrong", + "shootingTips": "Tap to take photo.", + "shootingWithRecordingTips": "Tap to take photo. Long press to record video.", + "shootingOnlyRecordingTips": "Long press to record video.", + "shootingTapRecordingTips": "Tap to record video.", + "loadFailed": "Load failed", + "saving": "Saving...", + "sActionManuallyFocusHint": "manually focus", + "sActionPreviewHint": "preview", + "sActionRecordHint": "record", + "sActionShootHint": "take picture", + "sActionShootingButtonTooltip": "shooting button", + "sActionStopRecordingHint": "stop recording", + "sCameraLensDirectionLabel": "Camera lens direction: {value}", + "sCameraPreviewLabel": "Camera preview: {value}", + "sFlashModeLabel": "Flash mode: {mode}", + "sSwitchCameraLensDirectionLabel": "Switch to the {value} camera" } diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index 809de49711..0c0675d197 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -3085,5 +3085,22 @@ "@searchContacts": {}, "phone": "Téléphone", "viewProfile": "Voir le profil", - "profileInfo": "Informations du profil" + "profileInfo": "Informations du profil", + "shootingTips": "Appuyez pour prendre une photo.", + "shootingWithRecordingTips": "Appuyez pour prendre une photo. Appuyez longuement pour enregistrer une vidéo.", + "shootingOnlyRecordingTips": "Appuyez longuement pour enregistrer une vidéo.", + "shootingTapRecordingTips": "Appuyez pour enregistrer une vidéo.", + "loadFailed": "Échec du chargement", + "loading": "Chargement...", + "saving": "Enregistrement...", + "sActionManuallyFocusHint": "mettre au point manuellement", + "sActionPreviewHint": "aperçu", + "sActionRecordHint": "enregistrer", + "sActionShootHint": "prendre une photo", + "sActionShootingButtonTooltip": "bouton de prise de vue", + "sActionStopRecordingHint": "arrêter l'enregistrement", + "sCameraLensDirectionLabel": "Direction de la lentille de la caméra : {value}", + "sCameraPreviewLabel": "Aperçu de la caméra : {value}", + "sFlashModeLabel": "Mode flash : {mode}", + "sSwitchCameraLensDirectionLabel": "Passer à la caméra {value}" } diff --git a/assets/l10n/intl_ru.arb b/assets/l10n/intl_ru.arb index 68e98d9b70..1ffd3ef016 100644 --- a/assets/l10n/intl_ru.arb +++ b/assets/l10n/intl_ru.arb @@ -3080,5 +3080,22 @@ "unselect": "Отменить выбор", "@unselect": {}, "searchContacts": "Поиск контактов", - "@searchContacts": {} + "@searchContacts": {}, + "shootingTips": "Нажмите, чтобы сделать фото.", + "shootingWithRecordingTips": "Нажмите, чтобы сделать фото. Удерживайте для записи видео.", + "shootingOnlyRecordingTips": "Удерживайте для записи видео.", + "shootingTapRecordingTips": "Нажмите, чтобы записать видео.", + "loadFailed": "Не удалось загрузить", + "loading": "Загрузка...", + "saving": "Сохранение...", + "sActionManuallyFocusHint": "ручная фокусировка", + "sActionPreviewHint": "предпросмотр", + "sActionRecordHint": "запись", + "sActionShootHint": "сделать фото", + "sActionShootingButtonTooltip": "кнопка съемки", + "sActionStopRecordingHint": "остановить запись", + "sCameraLensDirectionLabel": "Направление объектива камеры: {value}", + "sCameraPreviewLabel": "Предпросмотр камеры: {value}", + "sFlashModeLabel": "Режим вспышки: {mode}", + "sSwitchCameraLensDirectionLabel": "Переключиться на камеру {value}" } diff --git a/lib/presentation/mixins/common_media_picker_mixin.dart b/lib/presentation/mixins/common_media_picker_mixin.dart index a5e626381b..283f74746b 100644 --- a/lib/presentation/mixins/common_media_picker_mixin.dart +++ b/lib/presentation/mixins/common_media_picker_mixin.dart @@ -1,3 +1,5 @@ +import 'package:fluffychat/config/localizations/localization_service.dart'; +import 'package:fluffychat/utils/localized_camera_picker_text_delegate.dart'; import 'package:fluffychat/utils/permission_dialog.dart'; import 'package:fluffychat/utils/permission_service.dart'; import 'package:flutter/material.dart'; @@ -80,14 +82,36 @@ mixin CommonMediaPickerMixin { context, pickerConfig: onlyImage ? CameraPickerConfig( + textDelegate: getTextDelegateForLocale( + context, + ), enableAudio: false, onError: (e, a) => _onError(context: context, error: e), ) : CameraPickerConfig( + textDelegate: getTextDelegateForLocale( + context, + ), enableRecording: true, onError: (e, a) => _onError(context: context, error: e), ), - locale: View.of(context).platformDispatcher.locale, ); } + + CameraPickerTextDelegate getTextDelegateForLocale( + BuildContext context, + ) { + switch (LocalizationService.currentLocale.value.languageCode) { + case 'ru': + case 'fr': + return LocalizedCameraPickerTextDelegate( + context, + LocalizationService.currentLocale.value.languageCode, + ); + default: + return cameraPickerTextDelegateFromLocale( + LocalizationService.currentLocale.value, + ); + } + } } diff --git a/lib/utils/localized_camera_picker_text_delegate.dart b/lib/utils/localized_camera_picker_text_delegate.dart new file mode 100644 index 0000000000..03c86854a8 --- /dev/null +++ b/lib/utils/localized_camera_picker_text_delegate.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:wechat_camera_picker/wechat_camera_picker.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class LocalizedCameraPickerTextDelegate extends CameraPickerTextDelegate { + final BuildContext context; + final String language; + const LocalizedCameraPickerTextDelegate(this.context, this.language); + + @override + String get languageCode => language; + + @override + String get confirm => L10n.of(context)!.confirm; + + @override + String get shootingTips => L10n.of(context)!.shootingTips; + + @override + String get shootingWithRecordingTips => + L10n.of(context)!.shootingWithRecordingTips; + + @override + String get shootingOnlyRecordingTips => + L10n.of(context)!.shootingOnlyRecordingTips; + + @override + String get shootingTapRecordingTips => + L10n.of(context)!.shootingTapRecordingTips; + + @override + String get loadFailed => L10n.of(context)!.loadFailed; + + @override + String get loading => L10n.of(context)!.loading; + + @override + String get saving => L10n.of(context)!.saving; + + @override + String get sActionManuallyFocusHint => + L10n.of(context)!.sActionManuallyFocusHint; + + @override + String get sActionPreviewHint => L10n.of(context)!.sActionPreviewHint; + + @override + String get sActionRecordHint => L10n.of(context)!.sActionRecordHint; + + @override + String get sActionShootHint => L10n.of(context)!.sActionShootHint; + + @override + String get sActionShootingButtonTooltip => + L10n.of(context)!.sActionShootingButtonTooltip; + + @override + String get sActionStopRecordingHint => + L10n.of(context)!.sActionStopRecordingHint; + + @override + String sCameraLensDirectionLabel(CameraLensDirection value) => + L10n.of(context)!.sCameraLensDirectionLabel(value.name); + + @override + String? sCameraPreviewLabel(CameraLensDirection? value) { + if (value == null) { + return null; + } + return L10n.of(context)!.sCameraPreviewLabel(value.name); + } + + @override + String sFlashModeLabel(FlashMode mode) => + L10n.of(context)!.sFlashModeLabel(mode.name); + + @override + String sSwitchCameraLensDirectionLabel(CameraLensDirection value) => + L10n.of(context)!.sSwitchCameraLensDirectionLabel(value.name); +} From 2b787a7188d992c52496bd1e4204e787bb81d920 Mon Sep 17 00:00:00 2001 From: KhaledNjim <160496984+KhaledNjim@users.noreply.github.com> Date: Tue, 10 Sep 2024 01:22:54 +0100 Subject: [PATCH 36/40] TW-1817 update english strings Co-authored-by: Huy Quang Nguyen --- assets/l10n/intl_en.arb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index fb992e94f3..06009fd63a 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3093,12 +3093,12 @@ "shootingTapRecordingTips": "Tap to record video.", "loadFailed": "Load failed", "saving": "Saving...", - "sActionManuallyFocusHint": "manually focus", - "sActionPreviewHint": "preview", - "sActionRecordHint": "record", - "sActionShootHint": "take picture", - "sActionShootingButtonTooltip": "shooting button", - "sActionStopRecordingHint": "stop recording", + "sActionManuallyFocusHint": "Manually focus", + "sActionPreviewHint": "Preview", + "sActionRecordHint": "Record", + "sActionShootHint": "Take picture", + "sActionShootingButtonTooltip": "Shooting button", + "sActionStopRecordingHint": "Stop recording", "sCameraLensDirectionLabel": "Camera lens direction: {value}", "sCameraPreviewLabel": "Camera preview: {value}", "sFlashModeLabel": "Flash mode: {mode}", From 1bd768236de72a9eef4a724e038d898ae1bc70e3 Mon Sep 17 00:00:00 2001 From: KhaledNjim <160496984+KhaledNjim@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:38:03 +0200 Subject: [PATCH 37/40] TW-1996-removed-activated-status-in-accounts-tab (#2005) --- .../widget/contact_status_widget.dart | 38 +++++------ .../widget/expansion_contact_list_tile.dart | 3 +- .../widget/contact_status_widget_test.dart | 64 +++++++++++++++++++ 3 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 test/pages/new_private_chat/widget/contact_status_widget_test.dart diff --git a/lib/pages/new_private_chat/widget/contact_status_widget.dart b/lib/pages/new_private_chat/widget/contact_status_widget.dart index e2acf0cfa4..8b959143c1 100644 --- a/lib/pages/new_private_chat/widget/contact_status_widget.dart +++ b/lib/pages/new_private_chat/widget/contact_status_widget.dart @@ -13,36 +13,30 @@ class ContactStatusWidget extends StatelessWidget { required this.status, }); - final Color? activeColor = LinagoraRefColors.material().secondary[40]; final Color? inactiveColor = LinagoraRefColors.material().neutral[60]; @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - ImagePaths.icStatus, - // ignore: deprecated_member_use - color: status == ContactStatus.active ? activeColor : inactiveColor, - ), - status == ContactStatus.active - ? Text( - " ${L10n.of(context)!.online}", - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: activeColor, - ), - ) - : Text( + return status == ContactStatus.inactive + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + ImagePaths.icStatus, + colorFilter: + ColorFilter.mode(inactiveColor!, BlendMode.srcIn), + ), + Text( " ${L10n.of(context)!.inactive}", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: inactiveColor, ), ), - ], - ), - ); + ], + ), + ) + : const SizedBox.shrink(); } } diff --git a/lib/pages/new_private_chat/widget/expansion_contact_list_tile.dart b/lib/pages/new_private_chat/widget/expansion_contact_list_tile.dart index 54efe2d7d0..e855238afb 100644 --- a/lib/pages/new_private_chat/widget/expansion_contact_list_tile.dart +++ b/lib/pages/new_private_chat/widget/expansion_contact_list_tile.dart @@ -85,7 +85,8 @@ class ExpansionContactListTile extends StatelessWidget { ), ], const SizedBox(width: 8.0), - if (contact.status != null) + if (contact.status != null && + contact.status == ContactStatus.inactive) ContactStatusWidget( status: contact.status!, ), diff --git a/test/pages/new_private_chat/widget/contact_status_widget_test.dart b/test/pages/new_private_chat/widget/contact_status_widget_test.dart new file mode 100644 index 0000000000..f7ea163077 --- /dev/null +++ b/test/pages/new_private_chat/widget/contact_status_widget_test.dart @@ -0,0 +1,64 @@ +import 'package:fluffychat/pages/new_private_chat/widget/contact_status_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:fluffychat/domain/model/contact/contact_status.dart'; +import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; + +void main() { + group('ContactStatusWidget', () { + testWidgets('renders correctly for inactive status', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: L10n.localizationsDelegates, + supportedLocales: L10n.supportedLocales, + home: Scaffold( + body: ContactStatusWidget(status: ContactStatus.inactive), + ), + ), + ); + + expect(find.byType(SvgPicture), findsOneWidget); + + expect(find.byType(Text), findsOneWidget); + + final svgPicture = tester.widget(find.byType(SvgPicture)); + expect( + svgPicture.colorFilter, + ColorFilter.mode( + LinagoraRefColors.material().neutral[60]!, + BlendMode.srcIn, + ), + ); + + final text = tester.widget(find.byType(Text)); + expect(text.style?.color, LinagoraRefColors.material().neutral[60]); + expect( + text.style?.fontSize, + Theme.of(tester.element(find.byType(ContactStatusWidget))) + .textTheme + .bodySmall + ?.fontSize, + ); + }); + + testWidgets('renders correctly for active status', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: L10n.localizationsDelegates, + supportedLocales: L10n.supportedLocales, + home: Scaffold( + body: ContactStatusWidget(status: ContactStatus.active), + ), + ), + ); + + expect(find.byType(SvgPicture), findsNothing); + expect(find.byType(Text), findsNothing); + expect(find.byType(SizedBox), findsOneWidget); + }); + }); +} From 7db65518fdda2cfbbe6170d3128c201e1167eaa5 Mon Sep 17 00:00:00 2001 From: Nguyen Thai Date: Mon, 16 Sep 2024 11:32:17 +0700 Subject: [PATCH 38/40] Set uploaded appbundles status to completed --- android/fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index 4484f8e15d..6d9f2d881c 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -37,7 +37,7 @@ platform :android do track = "internal" upload_to_play_store(track: track, aab: '../build/app/outputs/bundle/release/app-release.aab', - release_status: "draft") + release_status: "completed") end lane :deploy_candidate do From cf9f7fb135384c9e631f0205d2ae4ae8e9a67d60 Mon Sep 17 00:00:00 2001 From: "khaled.njim" Date: Mon, 15 Jul 2024 03:31:43 +0100 Subject: [PATCH 39/40] TW-1902 update chat list item ui TW-1902 update icons, text style TW-1920 add delivery state for chatlist items TW-1902 added image preview in chat_list TW-1902 added video preview for chat list items TW-1902 fixed bottom navigation avatar sync --- assets/images/ic_encrypted.svg | 8 +- assets/l10n/intl_en.arb | 18 +-- assets/l10n/intl_fr.arb | 35 ------ assets/l10n/intl_ru.arb | 35 ------ lib/pages/chat/chat_app_bar_title.dart | 5 +- lib/pages/chat_list/chat_list.dart | 1 - lib/pages/chat_list/chat_list_body_view.dart | 47 ++----- .../chat_list/chat_list_header_style.dart | 2 +- lib/pages/chat_list/chat_list_item.dart | 119 ++++++++++-------- lib/pages/chat_list/chat_list_item_style.dart | 23 +++- .../chat_list/chat_list_item_subtitle.dart | 108 ++++++++++------ lib/pages/chat_list/chat_list_item_title.dart | 39 +++--- .../chat_list/chat_list_item_title_style.dart | 6 + .../chat_list/chat_list_view_builder.dart | 3 + lib/pages/chat_list/chat_list_view_style.dart | 4 + lib/pages/search/search_text_field.dart | 3 +- lib/pages/search/server_search_view.dart | 2 +- .../subtitle_image_preview_style.dart | 9 ++ .../subtitle_text_style_component.dart | 2 +- .../subtitle_text_style_decorator.dart | 24 ++-- .../title_text_style_decorator.dart | 7 +- .../mixins/chat_list_item_mixin.dart | 103 +++++++++++++-- .../app_adaptive_scaffold_body.dart | 11 +- .../enum/adaptive_destinations_enum.dart | 6 +- lib/widgets/mxc_image.dart | 4 +- 25 files changed, 345 insertions(+), 279 deletions(-) create mode 100644 lib/presentation/decorators/chat_list/subtitle_image_preview_style.dart diff --git a/assets/images/ic_encrypted.svg b/assets/images/ic_encrypted.svg index f082a53915..4a461b2b26 100644 --- a/assets/images/ic_encrypted.svg +++ b/assets/images/ic_encrypted.svg @@ -1,7 +1,3 @@ - - - - - - + + diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 06009fd63a..d8b2473239 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -899,7 +899,7 @@ "type": "text", "placeholders": {} }, - "friday": "Friday", + "friday": "Fri", "@friday": { "type": "text", "placeholders": {} @@ -1247,7 +1247,7 @@ "type": "text", "placeholders": {} }, - "monday": "Monday", + "monday": "Mon", "@monday": { "type": "text", "placeholders": {} @@ -1692,7 +1692,7 @@ "type": "text", "placeholders": {} }, - "saturday": "Saturday", + "saturday": "Sat", "@saturday": { "type": "text", "placeholders": {} @@ -1951,7 +1951,7 @@ "type": "text", "placeholders": {} }, - "sunday": "Sunday", + "sunday": "Sun", "@sunday": { "type": "text", "placeholders": {} @@ -1981,7 +1981,7 @@ "type": "text", "placeholders": {} }, - "thursday": "Thursday", + "thursday": "Thu", "@thursday": { "type": "text", "placeholders": {} @@ -2022,7 +2022,7 @@ "type": "text", "placeholders": {} }, - "tuesday": "Tuesday", + "tuesday": "Tue", "@tuesday": { "type": "text", "placeholders": {} @@ -2199,7 +2199,7 @@ "type": "text", "placeholders": {} }, - "wednesday": "Wednesday", + "wednesday": "Wed", "@wednesday": { "type": "text", "placeholders": {} @@ -3102,5 +3102,7 @@ "sCameraLensDirectionLabel": "Camera lens direction: {value}", "sCameraPreviewLabel": "Camera preview: {value}", "sFlashModeLabel": "Flash mode: {mode}", - "sSwitchCameraLensDirectionLabel": "Switch to the {value} camera" + "sSwitchCameraLensDirectionLabel": "Switch to the {value} camera", + "photo": "Photo", + "video": "Video" } diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index 0c0675d197..918796e827 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -809,11 +809,6 @@ "type": "text", "placeholders": {} }, - "friday": "Vendredi", - "@friday": { - "type": "text", - "placeholders": {} - }, "fromJoining": "À partir de l'entrée dans le salon", "@fromJoining": { "type": "text", @@ -1143,11 +1138,6 @@ "type": "text", "placeholders": {} }, - "monday": "Lundi", - "@monday": { - "type": "text", - "placeholders": {} - }, "muteChat": "Mettre la discussion en sourdine", "@muteChat": { "type": "text", @@ -1541,11 +1531,6 @@ "type": "text", "placeholders": {} }, - "saturday": "Samedi", - "@saturday": { - "type": "text", - "placeholders": {} - }, "saveFile": "Enregistrer le fichier", "@saveFile": { "type": "text", @@ -1785,11 +1770,6 @@ "type": "text", "placeholders": {} }, - "sunday": "Dimanche", - "@sunday": { - "type": "text", - "placeholders": {} - }, "synchronizingPleaseWait": "Synchronisation... Veuillez patienter.", "@synchronizingPleaseWait": { "type": "text", @@ -1815,11 +1795,6 @@ "type": "text", "placeholders": {} }, - "thursday": "Jeudi", - "@thursday": { - "type": "text", - "placeholders": {} - }, "title": "FluffyChat", "@title": { "description": "Title for the application", @@ -1856,11 +1831,6 @@ "type": "text", "placeholders": {} }, - "tuesday": "Mardi", - "@tuesday": { - "type": "text", - "placeholders": {} - }, "unavailable": "Indisponible", "@unavailable": { "type": "text", @@ -2031,11 +2001,6 @@ "type": "text", "placeholders": {} }, - "wednesday": "Mercredi", - "@wednesday": { - "type": "text", - "placeholders": {} - }, "weSentYouAnEmail": "Nous vous avons envoyé un message", "@weSentYouAnEmail": { "type": "text", diff --git a/assets/l10n/intl_ru.arb b/assets/l10n/intl_ru.arb index 1ffd3ef016..0947457f92 100644 --- a/assets/l10n/intl_ru.arb +++ b/assets/l10n/intl_ru.arb @@ -800,11 +800,6 @@ "type": "text", "placeholders": {} }, - "friday": "Пятница", - "@friday": { - "type": "text", - "placeholders": {} - }, "fromJoining": "С момента присоединения", "@fromJoining": { "type": "text", @@ -1134,11 +1129,6 @@ "type": "text", "placeholders": {} }, - "monday": "Понедельник", - "@monday": { - "type": "text", - "placeholders": {} - }, "muteChat": "Отключить уведомления", "@muteChat": { "type": "text", @@ -1527,11 +1517,6 @@ "type": "text", "placeholders": {} }, - "saturday": "Суббота", - "@saturday": { - "type": "text", - "placeholders": {} - }, "saveFile": "Сохранить файл", "@saveFile": { "type": "text", @@ -1770,11 +1755,6 @@ "type": "text", "placeholders": {} }, - "sunday": "Воскресенье", - "@sunday": { - "type": "text", - "placeholders": {} - }, "synchronizingPleaseWait": "Синхронизация… Пожалуйста, подождите.", "@synchronizingPleaseWait": { "type": "text", @@ -1800,11 +1780,6 @@ "type": "text", "placeholders": {} }, - "thursday": "Четверг", - "@thursday": { - "type": "text", - "placeholders": {} - }, "title": "FluffyChat", "@title": { "description": "Title for the application", @@ -1841,11 +1816,6 @@ "type": "text", "placeholders": {} }, - "tuesday": "Вторник", - "@tuesday": { - "type": "text", - "placeholders": {} - }, "unavailable": "Недоступен", "@unavailable": { "type": "text", @@ -2016,11 +1986,6 @@ "type": "text", "placeholders": {} }, - "wednesday": "Среда", - "@wednesday": { - "type": "text", - "placeholders": {} - }, "weSentYouAnEmail": "Мы отправили вам электронное письмо", "@weSentYouAnEmail": { "type": "text", diff --git a/lib/pages/chat/chat_app_bar_title.dart b/lib/pages/chat/chat_app_bar_title.dart index 8125ec8505..0e61b020ed 100644 --- a/lib/pages/chat/chat_app_bar_title.dart +++ b/lib/pages/chat/chat_app_bar_title.dart @@ -91,10 +91,11 @@ class ChatAppBarTitle extends StatelessWidget { padding: const EdgeInsets.all(2.0), child: SvgPicture.asset( ImagePaths.icEncrypted, - width: 20, - height: 20, + width: 16, + height: 16, ), ), + const SizedBox(width: 4), Flexible( child: Text( roomName ?? diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index b3511394dc..1d222ed917 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -788,7 +788,6 @@ class ChatListController extends State WidgetsBinding.instance.addPostFrameCallback((_) async { if (mounted) { Matrix.of(context).backgroundPush?.setupPush(); - await matrixState.retrievePersistedActiveClient(); } }); _checkTorBrowser(); diff --git a/lib/pages/chat_list/chat_list_body_view.dart b/lib/pages/chat_list/chat_list_body_view.dart index 86c8767d37..d946d4c341 100644 --- a/lib/pages/chat_list/chat_list_body_view.dart +++ b/lib/pages/chat_list/chat_list_body_view.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pages/chat_list/chat_list_view_builder.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/presentation/enum/chat_list/chat_list_enum.dart'; import 'package:fluffychat/resource/image_paths.dart'; -import 'package:fluffychat/utils/extension/value_notifier_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; import 'package:flutter/material.dart'; @@ -148,15 +147,7 @@ class ChatListBodyView extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - ExpandableTitleBuilder( - title: L10n.of(context)!.countPinChat( - controller.filteredRoomsForPin.length, - ), - isExpanded: isExpanded, - onTap: controller - .expandRoomsForPinNotifier.toggle, - ), - if (isExpanded) child!, + child!, ], ); }, @@ -166,33 +157,15 @@ class ChatListBodyView extends StatelessWidget { ), ), if (!controller.filteredRoomsForAllIsEmpty) - ValueListenableBuilder( - valueListenable: controller.expandRoomsForAllNotifier, - builder: (context, isExpanded, child) { - return Padding( - padding: ChatListBodyViewStyle - .paddingTopExpandableTitleBuilder, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ExpandableTitleBuilder( - title: L10n.of(context)!.countAllChat( - controller.filteredRoomsForAll.length, - ), - isExpanded: isExpanded, - onTap: controller - .expandRoomsForAllNotifier.toggle, - ), - if (isExpanded) child!, - ], - ), - ); - }, - child: ChatListViewBuilder( - controller: controller, - rooms: controller.filteredRoomsForAll, - ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ChatListViewBuilder( + controller: controller, + rooms: controller.filteredRoomsForAll, + ), + ], ), ], ), diff --git a/lib/pages/chat_list/chat_list_header_style.dart b/lib/pages/chat_list/chat_list_header_style.dart index 255f7d0fa4..749002c447 100644 --- a/lib/pages/chat_list/chat_list_header_style.dart +++ b/lib/pages/chat_list/chat_list_header_style.dart @@ -44,7 +44,7 @@ class ChatListHeaderStyle { prefixIcon: Icon( Icons.search, size: ChatListHeaderStyle.searchIconSize, - color: prefixIconColor ?? Theme.of(context).colorScheme.onSurface, + color: prefixIconColor ?? LinagoraRefColors.material().neutral[60], ), suffixIcon: const SizedBox.shrink(), ); diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index e1c78d5e74..15ef3afa99 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -1,5 +1,4 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/presentation/mixins/chat_list_item_mixin.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item_style.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item_subtitle.dart'; @@ -91,66 +90,86 @@ class ChatListItem extends StatelessWidget with ChatListItemMixin { MatrixLocals(L10n.of(context)!), ); return Padding( - padding: ChatListItemStyle.paddingConversation, + padding: ChatListItemStyle.padding, child: Material( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), + borderRadius: ChatListItemStyle.chatlistItemBorderRadius, clipBehavior: Clip.hardEdge, color: isSelectedItem ? Theme.of(context).colorScheme.primaryContainer : activeChat ? Theme.of(context).colorScheme.secondaryContainer : Colors.transparent, - child: InkWell( - onTap: () => clickAction(context), - onSecondaryTapDown: onSecondaryTapDown, - onLongPress: onLongPress, - child: Container( - height: ChatListItemStyle.chatItemHeight, - padding: ChatListItemStyle.paddingBody, - child: Row( - children: [ - if (isEnableSelectMode) checkBoxWidget ?? const SizedBox(), - Padding( - padding: ChatListItemStyle.paddingAvatar, - child: Stack( - children: [ - Avatar( - mxContent: room.avatar, - name: displayName, - onTap: onTapAvatar, - ), - if (_isGroupChat) - Positioned( - bottom: 0, - right: 0, - child: Container( - padding: ChatListItemStyle.paddingIconGroup, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.onPrimary, - ), - child: Icon( - Icons.group, - size: ChatListItemStyle.groupIconSize, - color: room.isUnreadOrInvited - ? LinagoraSysColors.material() - .onSurfaceVariant - : LinagoraRefColors.material().tertiary[30], + child: Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: + LinagoraStateLayer(LinagoraSysColors.material().surfaceTint) + .opacityLayer3, + width: ChatListItemStyle.chatListBottomBorderWidht, + ), + ), + ), + child: InkWell( + onTap: () => clickAction(context), + onSecondaryTapDown: onSecondaryTapDown, + onLongPress: onLongPress, + borderRadius: ChatListItemStyle.chatlistItemBorderRadius, + child: Container( + height: ChatListItemStyle.chatItemHeight, + padding: ChatListItemStyle.paddingBody, + decoration: BoxDecoration( + borderRadius: ChatListItemStyle.chatlistItemBorderRadius, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (isEnableSelectMode) checkBoxWidget ?? const SizedBox(), + Padding( + padding: ChatListItemStyle.paddingAvatar, + child: Stack( + children: [ + Avatar( + mxContent: room.avatar, + name: displayName, + onTap: onTapAvatar, + ), + if (_isGroupChat) + Positioned( + bottom: 0, + right: 0, + child: Container( + padding: ChatListItemStyle.paddingIconGroup, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).colorScheme.onPrimary, + ), + child: Icon( + Icons.group, + size: ChatListItemStyle.groupIconSize, + color: room.isUnreadOrInvited + ? LinagoraSysColors.material() + .onSurfaceVariant + : LinagoraRefColors.material().tertiary[30], + ), ), ), - ), - ], + ], + ), ), - ), - Expanded( - child: Column( - children: [ - ChatListItemTitle(room: room), - ChatListItemSubtitle(room: room), - ], + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ChatListItemTitle( + room: room, + ), + ChatListItemSubtitle(room: room), + ], + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pages/chat_list/chat_list_item_style.dart b/lib/pages/chat_list/chat_list_item_style.dart index 56111fe7ca..953b14a99f 100644 --- a/lib/pages/chat_list/chat_list_item_style.dart +++ b/lib/pages/chat_list/chat_list_item_style.dart @@ -5,7 +5,7 @@ class ChatListItemStyle { static Color? get readIconColor => LinagoraRefColors.material().tertiary[20]; static Color? get pinnedIconColor => - LinagoraRefColors.material().tertiary[30]; + LinagoraRefColors.material().tertiary[40]; static const double readIconSize = 20; @@ -13,7 +13,7 @@ class ChatListItemStyle { static const double mentionIconWidth = 20; - static const double chatItemHeight = 85; + static const double chatItemHeight = 80; static double unreadBadgeSize( bool unread, @@ -27,14 +27,19 @@ class ChatListItemStyle { : 0.0; } - static const EdgeInsetsDirectional paddingConversation = - EdgeInsetsDirectional.symmetric( + static const EdgeInsets paddingConversation = EdgeInsets.fromLTRB( + 8, + 8, + 8, + 8, + ); + + static const EdgeInsets padding = EdgeInsets.symmetric( horizontal: 8, - vertical: 2, ); static const EdgeInsetsDirectional paddingAvatar = - EdgeInsetsDirectional.only(end: 8); + EdgeInsetsDirectional.only(start: 8, end: 8); static const EdgeInsetsDirectional paddingIconGroup = EdgeInsetsDirectional.all(4); @@ -57,4 +62,10 @@ class ChatListItemStyle { } static const double letterSpaceDisplayName = 0.15; + + static final chatlistItemBorderRadius = BorderRadius.circular(4); + + static const paddingIcon = EdgeInsets.only(bottom: 4); + + static const chatListBottomBorderWidht = 1.0; } diff --git a/lib/pages/chat_list/chat_list_item_subtitle.dart b/lib/pages/chat_list/chat_list_item_subtitle.dart index af3b8afe30..e392b86739 100644 --- a/lib/pages/chat_list/chat_list_item_subtitle.dart +++ b/lib/pages/chat_list/chat_list_item_subtitle.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/domain/model/room/room_extension.dart'; import 'package:fluffychat/presentation/mixins/chat_list_item_mixin.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item_style.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -9,6 +10,7 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; import 'package:matrix/matrix.dart'; class ChatListItemSubtitle extends StatelessWidget with ChatListItemMixin { @@ -25,6 +27,15 @@ class ChatListItemSubtitle extends StatelessWidget with ChatListItemMixin { room.hasNewMessages, room.notificationCount > 0, ); + final isMediaEvent = room.lastEvent?.messageType == MessageTypes.Image || + room.lastEvent?.messageType == MessageTypes.Video; + + final haveNotificationsAndMuted = + room.notificationCount > 0 && room.isMuted; + + final haveNotificationsOrUnread = + room.notificationCount > 0 || room.markedUnread; + return Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -32,17 +43,22 @@ class ChatListItemSubtitle extends StatelessWidget with ChatListItemMixin { Expanded( child: typingText.isNotEmpty ? typingTextWidget(typingText, context) - : (isGroup + : isGroup ? chatListItemSubtitleForGroup( context: context, room: room, ) - : textContentWidget( - room, - context, - isGroup, - room.isUnreadOrInvited, - )), + : isMediaEvent + ? chatlistItemMediaPreviewSubTitle( + context, + room, + ) + : textContentWidget( + room, + context, + isGroup, + room.isUnreadOrInvited, + ), ), const SizedBox(width: 8), FutureBuilder( @@ -60,39 +76,49 @@ class ChatListItemSubtitle extends StatelessWidget with ChatListItemMixin { room.lastEvent == null) { return const SizedBox.shrink(); } - final isMentionned = snapshot.data! .getAllMentionedUserIdsFromMessage(room) .contains(Matrix.of(context).client.userID); - return AnimatedContainer( - duration: TwakeThemes.animationDuration, - curve: TwakeThemes.animationCurve, - padding: const EdgeInsets.only(bottom: 4), - height: ChatListItemStyle.mentionIconWidth, - width: isMentionned && room.isUnreadOrInvited - ? ChatListItemStyle.mentionIconWidth - : 0, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - ), - child: Center( - child: isMentionned && room.isUnreadOrInvited - ? Text( - '@', - style: TextStyle( - color: isMentionned - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context) - .colorScheme - .onPrimaryContainer, - fontSize: - Theme.of(context).textTheme.labelMedium?.fontSize, - ), - ) - : Container(), - ), - ); + return room.lastEvent?.senderId == Matrix.of(context).client.userID + ? Icon( + Icons.done_all, + color: room.lastEvent!.receipts.isEmpty + ? LinagoraRefColors.material().tertiary[30] + : LinagoraSysColors.material().secondary, + size: 20, + ) + : AnimatedContainer( + duration: TwakeThemes.animationDuration, + curve: TwakeThemes.animationCurve, + padding: const EdgeInsets.only(bottom: 4), + height: ChatListItemStyle.mentionIconWidth, + width: isMentionned && room.isUnreadOrInvited + ? ChatListItemStyle.mentionIconWidth + : 0, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + ), + child: Center( + child: isMentionned && room.isUnreadOrInvited + ? Text( + '@', + style: TextStyle( + color: isMentionned + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context) + .colorScheme + .onPrimaryContainer, + fontSize: Theme.of(context) + .textTheme + .labelMedium + ?.fontSize, + ), + ) + : Container(), + ), + ); }, ), const SizedBox(width: 4), @@ -110,9 +136,11 @@ class ChatListItemSubtitle extends StatelessWidget with ChatListItemMixin { color: room.highlightCount > 0 || room.membership == Membership.invite ? Theme.of(context).colorScheme.primary - : room.notificationCount > 0 || room.markedUnread - ? Theme.of(context).colorScheme.primary - : LinagoraRefColors.material().tertiary[30], + : haveNotificationsAndMuted + ? LinagoraRefColors.material().tertiary[30] + : haveNotificationsOrUnread + ? Theme.of(context).colorScheme.primary + : LinagoraRefColors.material().tertiary[30], borderRadius: BorderRadius.circular(AppConfig.borderRadius), ), child: Center( diff --git a/lib/pages/chat_list/chat_list_item_title.dart b/lib/pages/chat_list/chat_list_item_title.dart index 0fa59464df..caa5636adc 100644 --- a/lib/pages/chat_list/chat_list_item_title.dart +++ b/lib/pages/chat_list/chat_list_item_title.dart @@ -16,14 +16,11 @@ import 'package:matrix/matrix.dart'; class ChatListItemTitle extends StatelessWidget with ChatListItemMixin { final Room room; - final TextStyle? textStyle; - final DateTime? originServerTs; const ChatListItemTitle({ super.key, required this.room, - this.textStyle, this.originServerTs, }); @@ -40,26 +37,28 @@ class ChatListItemTitle extends StatelessWidget with ChatListItemMixin { children: [ Row( children: [ + Flexible( + child: Padding( + padding: ChatListItemTitleStyle.paddingRightTitle, + child: Text( + displayName, + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + style: + ChatLitTitleTextStyleView.textStyle.textStyle(room), + ), + ), + ), if (room.encrypted) Padding( - padding: - const EdgeInsets.only(right: 4, top: 2, bottom: 2), + padding: ChatListItemTitleStyle.paddingLeftIcon, child: SvgPicture.asset( ImagePaths.icEncrypted, - width: 20, - height: 20, + width: ChatListItemTitleStyle.encryptedInconWidth, + height: ChatListItemTitleStyle.encryptedInconHeight, ), ), - Flexible( - child: Text( - displayName, - overflow: TextOverflow.ellipsis, - maxLines: 1, - softWrap: false, - style: textStyle ?? - ChatLitTitleTextStyleView.textStyle.textStyle(room), - ), - ), if (room.isFavourite) Padding( padding: ChatListItemTitleStyle.paddingLeftIcon, @@ -90,7 +89,7 @@ class ChatListItemTitle extends StatelessWidget with ChatListItemMixin { if (room.isTypingText(context)) ...[ Icon( Icons.schedule, - color: LinagoraRefColors.material().neutral[50], + color: LinagoraRefColors.material().tertiary[30], size: ChatListItemTitleStyle.iconScheduleSize, ), ], @@ -100,9 +99,7 @@ class ChatListItemTitle extends StatelessWidget with ChatListItemMixin { (originServerTs ?? room.timeCreated) .localizedTimeShort(context), style: Theme.of(context).textTheme.labelMedium?.copyWith( - color: room.isUnreadOrInvited - ? Theme.of(context).colorScheme.onSurface - : LinagoraRefColors.material().neutral[50], + color: LinagoraRefColors.material().tertiary[30], ), ), ), diff --git a/lib/pages/chat_list/chat_list_item_title_style.dart b/lib/pages/chat_list/chat_list_item_title_style.dart index f6513101e2..d802d61c4c 100644 --- a/lib/pages/chat_list/chat_list_item_title_style.dart +++ b/lib/pages/chat_list/chat_list_item_title_style.dart @@ -7,4 +7,10 @@ class ChatListItemTitleStyle { EdgeInsetsDirectional.only( start: 4, ); + static const EdgeInsetsDirectional paddingRightTitle = + EdgeInsetsDirectional.only( + end: 3, + ); + static const encryptedInconHeight = 16.0; + static const encryptedInconWidth = 14.0; } diff --git a/lib/pages/chat_list/chat_list_view_builder.dart b/lib/pages/chat_list/chat_list_view_builder.dart index eee9f8b109..cbc8eb277f 100644 --- a/lib/pages/chat_list/chat_list_view_builder.dart +++ b/lib/pages/chat_list/chat_list_view_builder.dart @@ -23,6 +23,9 @@ class ChatListViewBuilder extends StatelessWidget { physics: const NeverScrollableScrollPhysics(), itemCount: rooms.length, itemBuilder: (BuildContext context, int index) { + if (index == rooms.length) { + return const SizedBox.shrink(); + } return ValueListenableBuilder( valueListenable: controller.selectModeNotifier, builder: (context, selectMode, _) { diff --git a/lib/pages/chat_list/chat_list_view_style.dart b/lib/pages/chat_list/chat_list_view_style.dart index 6289134c67..54ae368360 100644 --- a/lib/pages/chat_list/chat_list_view_style.dart +++ b/lib/pages/chat_list/chat_list_view_style.dart @@ -38,4 +38,8 @@ class ChatListViewStyle { ? LinagoraRefColors.material().primary[50] : LinagoraRefColors.material().primary[40]; } + + static double dividerHeight = 1.0; + static double dividerIndent = 8.0; + static double dividerThickness = 1.0; } diff --git a/lib/pages/search/search_text_field.dart b/lib/pages/search/search_text_field.dart index cb4ed3ed9e..408859555a 100644 --- a/lib/pages/search/search_text_field.dart +++ b/lib/pages/search/search_text_field.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; import 'package:fluffychat/pages/dialer/pip/dismiss_keyboard.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; class SearchTextField extends StatelessWidget { final TextEditingController textEditingController; @@ -46,7 +47,7 @@ class SearchTextField extends StatelessWidget { prefixIcon: Icon( Icons.search_outlined, size: SearchViewStyle.searchIconSize, - color: Theme.of(context).colorScheme.onSurface, + color: LinagoraRefColors.material().neutral[60], ), suffixIcon: ValueListenableBuilder( valueListenable: textEditingController, diff --git a/lib/pages/search/server_search_view.dart b/lib/pages/search/server_search_view.dart index 8be0ae054a..07a1512202 100644 --- a/lib/pages/search/server_search_view.dart +++ b/lib/pages/search/server_search_view.dart @@ -89,7 +89,7 @@ class ServerSearchMessagesList extends StatelessWidget { maxLines: 2, style: ChatLitSubSubtitleTextStyleView .textStyle - .textStyle(room), + .textStyle(room, context), ), ], ), diff --git a/lib/presentation/decorators/chat_list/subtitle_image_preview_style.dart b/lib/presentation/decorators/chat_list/subtitle_image_preview_style.dart new file mode 100644 index 0000000000..b63c4b30bc --- /dev/null +++ b/lib/presentation/decorators/chat_list/subtitle_image_preview_style.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +class SubtitleImagePreviewStyle { + static const double width = 20; + static const double height = 20; + static const double borderRadius = 4; + static const BoxFit fit = BoxFit.fill; + static const EdgeInsets labelPadding = EdgeInsets.only(left: 5); +} diff --git a/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_component.dart b/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_component.dart index ba8b925fcd..f2cddf4801 100644 --- a/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_component.dart +++ b/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_component.dart @@ -2,5 +2,5 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; abstract class ChatListSubtitleTextStyleComponent { - TextStyle textStyle(Room room); + TextStyle textStyle(Room room, BuildContext context); } diff --git a/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_decorator.dart b/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_decorator.dart index c8ffb4fd67..7e1d03c7b0 100644 --- a/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_decorator.dart +++ b/lib/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_decorator.dart @@ -18,8 +18,8 @@ class ChatListSubtitleTextStyle implements ChatListSubtitleTextStyleDecorator { ChatListSubtitleTextStyle(this._interfaceTextStyleComponent); @override - TextStyle textStyle(Room room) { - return _interfaceTextStyleComponent.textStyle(room); + TextStyle textStyle(Room room, BuildContext context) { + return _interfaceTextStyleComponent.textStyle(room, context); } @override @@ -30,8 +30,8 @@ class ChatListSubtitleTextStyle implements ChatListSubtitleTextStyleDecorator { class ReadChatListSubtitleTextStyleDecorator implements ChatListSubtitleTextStyleComponent { @override - TextStyle textStyle(Room room) { - return LinagoraTextStyle.material().bodyMedium3.copyWith( + TextStyle textStyle(Room room, BuildContext context) { + return Theme.of(context).textTheme.bodyMedium!.copyWith( color: LinagoraSysColors.material().onSurface, fontFamily: GoogleFonts.inter().fontFamily, ); @@ -45,15 +45,15 @@ class UnreadChatListSubtitleTextStyleDecorator UnreadChatListSubtitleTextStyleDecorator(this._interfaceTextStyleComponent); @override - TextStyle textStyle(Room room) { + TextStyle textStyle(Room room, BuildContext context) { if (room.isUnreadOrInvited) { - return _interfaceTextStyleComponent.textStyle(room).merge( - LinagoraTextStyle.material().bodyMedium2.copyWith( + return _interfaceTextStyleComponent.textStyle(room, context).merge( + Theme.of(context).textTheme.bodyMedium!.copyWith( color: LinagoraSysColors.material().onSurface, ), ); } else { - return _interfaceTextStyleComponent.textStyle(room); + return _interfaceTextStyleComponent.textStyle(room, context); } } @@ -69,13 +69,13 @@ class MuteChatListSubtitleTextStyleDecorator MuteChatListSubtitleTextStyleDecorator(this._interfaceTextStyleComponent); @override - TextStyle textStyle(Room room) { + TextStyle textStyle(Room room, BuildContext context) { if (room.isMuted) { - return _interfaceTextStyleComponent.textStyle(room).copyWith( - color: LinagoraRefColors.material().tertiary[20], + return _interfaceTextStyleComponent.textStyle(room, context).copyWith( + color: LinagoraSysColors.material().onSurface, ); } else { - return _interfaceTextStyleComponent.textStyle(room); + return _interfaceTextStyleComponent.textStyle(room, context); } } diff --git a/lib/presentation/decorators/chat_list/title_text_style_decorator/title_text_style_decorator.dart b/lib/presentation/decorators/chat_list/title_text_style_decorator/title_text_style_decorator.dart index bb25c10c6b..15435932f2 100644 --- a/lib/presentation/decorators/chat_list/title_text_style_decorator/title_text_style_decorator.dart +++ b/lib/presentation/decorators/chat_list/title_text_style_decorator/title_text_style_decorator.dart @@ -30,7 +30,7 @@ class ReadChatListTitleTextStyleDecorator implements ChatListTitleTextStyleComponent { @override TextStyle textStyle(Room room) { - return LinagoraTextStyle.material().bodyLarge2.copyWith( + return LinagoraTextStyle.material().bodyMedium2.copyWith( color: LinagoraSysColors.material().onSurface, fontFamily: GoogleFonts.inter().fontFamily, ); @@ -47,8 +47,9 @@ class UnreadChatListTitleTextStyleDecorator TextStyle textStyle(Room room) { if (room.isUnreadOrInvited) { return _interfaceTextStyleComponent.textStyle(room).merge( - LinagoraTextStyle.material().bodyLarge1.copyWith( + LinagoraTextStyle.material().bodyMedium2.copyWith( color: LinagoraSysColors.material().onSurface, + fontFamily: GoogleFonts.inter().fontFamily, ), ); } else { @@ -72,7 +73,7 @@ class MuteChatListTitleTextStyleDecorator final isMuted = room.pushRuleState != PushRuleState.notify; if (isMuted) { return _interfaceTextStyleComponent.textStyle(room).copyWith( - color: LinagoraRefColors.material().tertiary[20], + color: LinagoraSysColors.material().onSurface, ); } else { return _interfaceTextStyleComponent.textStyle(room); diff --git a/lib/presentation/mixins/chat_list_item_mixin.dart b/lib/presentation/mixins/chat_list_item_mixin.dart index b7c346d875..1b2edf55ed 100644 --- a/lib/presentation/mixins/chat_list_item_mixin.dart +++ b/lib/presentation/mixins/chat_list_item_mixin.dart @@ -1,9 +1,14 @@ +import 'package:fluffychat/pages/chat/events/images_builder/image_placeholder.dart'; +import 'package:fluffychat/presentation/decorators/chat_list/subtitle_image_preview_style.dart'; import 'package:fluffychat/presentation/decorators/chat_list/subtitle_text_style_decorator/subtitle_text_style_view.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/widgets/mxc_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; +import 'package:linagora_design_flutter/style/linagora_text_style.dart'; import 'package:matrix/matrix.dart'; mixin ChatListItemMixin { @@ -39,20 +44,22 @@ mixin ChatListItemMixin { softWrap: false, maxLines: isGroup ? 1 : 2, overflow: TextOverflow.ellipsis, - style: ChatLitSubSubtitleTextStyleView.textStyle.textStyle(room), + style: LinagoraTextStyle.material().bodyMedium3.copyWith( + color: LinagoraRefColors.material().tertiary[30], + ), ); }, ); } Widget typingTextWidget(String typingText, BuildContext context) { - final displayedTypingText = "~ $typingText…"; + final displayedTypingText = "$typingText…"; return Text( displayedTypingText, - style: Theme.of(context).textTheme.labelLarge?.merge( + style: LinagoraTextStyle.material().bodyMedium2.merge( TextStyle( overflow: TextOverflow.ellipsis, - color: LinagoraRefColors.material().secondary, + color: LinagoraRefColors.material().tertiary[30], ), ), maxLines: 2, @@ -75,7 +82,7 @@ mixin ChatListItemMixin { maxLines: 1, softWrap: false, style: ChatLitSubSubtitleTextStyleView.textStyle - .textStyle(room), + .textStyle(room, context), ); }, ), @@ -96,7 +103,8 @@ mixin ChatListItemMixin { softWrap: false, maxLines: 2, overflow: TextOverflow.ellipsis, - style: ChatLitSubSubtitleTextStyleView.textStyle.textStyle(room), + style: + ChatLitSubSubtitleTextStyleView.textStyle.textStyle(room, context), ); } @@ -119,18 +127,87 @@ mixin ChatListItemMixin { softWrap: false, maxLines: 2, overflow: TextOverflow.ellipsis, - style: ChatLitSubSubtitleTextStyleView.textStyle.textStyle(room), + style: ChatLitSubSubtitleTextStyleView.textStyle + .textStyle(room, context), ); } - return Text( - "${snapshot.data!.calcDisplayname()}: $subscriptions", - softWrap: false, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: ChatLitSubSubtitleTextStyleView.textStyle.textStyle(room), + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + snapshot.data!.calcDisplayname(), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: LinagoraSysColors.material().onSurface, + ), + ), + room.lastEvent?.messageType == MessageTypes.Image || + room.lastEvent?.messageType == MessageTypes.Video + ? chatlistItemMediaPreviewSubTitle( + context, + room, + ) + : Text( + subscriptions, + softWrap: false, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: LinagoraTextStyle.material().bodyMedium3.copyWith( + color: LinagoraRefColors.material().tertiary[30], + ), + ), + ], ); }, ); } + + Widget chatlistItemMediaPreviewSubTitle( + BuildContext context, + Room room, + ) { + return Row( + children: [ + if (room.lastEvent?.status != EventStatus.synced) + const SizedBox.shrink() + else + SizedBox( + height: SubtitleImagePreviewStyle.height, + width: SubtitleImagePreviewStyle.width, + child: ClipRRect( + borderRadius: + BorderRadius.circular(SubtitleImagePreviewStyle.borderRadius), + child: MxcImage( + key: ValueKey(room.lastEvent!.eventId), + cacheKey: room.lastEvent!.eventId, + event: room.lastEvent!, + placeholder: (context) => ImagePlaceholder( + event: room.lastEvent!, + width: SubtitleImagePreviewStyle.width, + height: SubtitleImagePreviewStyle.height, + fit: SubtitleImagePreviewStyle.fit, + ), + fit: SubtitleImagePreviewStyle.fit, + enableHeroAnimation: false, + ), + ), + ), + Padding( + padding: SubtitleImagePreviewStyle.labelPadding, + child: Text( + room.lastEvent!.messageType == MessageTypes.Image + ? L10n.of(context)!.photo + : L10n.of(context)!.video, + style: LinagoraTextStyle.material() + .bodyMedium3 + .copyWith(color: LinagoraRefColors.material().tertiary[30]), + ), + ), + ], + ); + } } diff --git a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart index b9520d3de3..b8b037bee9 100644 --- a/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart +++ b/lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body.dart @@ -164,11 +164,16 @@ class AppAdaptiveScaffoldBodyController extends State @override void initState() { + super.initState(); activeRoomIdNotifier.value = widget.activeRoomId; resetLocationPathWithLoginToken(); - getCurrentProfile(); - _handleProfileDataChange(); - super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (mounted) { + await matrix.retrievePersistedActiveClient(); + getCurrentProfile(); + _handleProfileDataChange(); + } + }); } @override diff --git a/lib/widgets/layouts/enum/adaptive_destinations_enum.dart b/lib/widgets/layouts/enum/adaptive_destinations_enum.dart index 02175db127..4cf2ee8137 100644 --- a/lib/widgets/layouts/enum/adaptive_destinations_enum.dart +++ b/lib/widgets/layouts/enum/adaptive_destinations_enum.dart @@ -98,8 +98,10 @@ enum AdaptiveDestinationEnum { profile: profile, isSelected: false, ), - activeIcon: - BottomNavigationAvatar(profile: profile, isSelected: true), + activeIcon: BottomNavigationAvatar( + profile: profile, + isSelected: true, + ), label: L10n.of(context)!.settings, ); default: diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index be84795e4c..0b8428fb50 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -38,6 +38,7 @@ class MxcImage extends StatefulWidget { final void Function()? onTapSelectMode; final ImageData? imageData; final bool isPreview; + final bool enableHeroAnimation; /// Enable it if the image is stretched, and you don't want to resize it final bool noResize; @@ -75,6 +76,7 @@ class MxcImage extends StatefulWidget { this.closeRightColumn, this.cacheWidth, this.cacheHeight, + this.enableHeroAnimation = true, super.key, }); @@ -272,7 +274,7 @@ class _MxcImageState extends State { ) : _buildImageWidget(); - if (widget.event?.eventId != null) { + if (widget.event?.eventId != null && widget.enableHeroAnimation) { imageWidget = Hero( tag: widget.event!.eventId, child: imageWidget, From 5fae280837139cf4ebb294c38c215a845f596e80 Mon Sep 17 00:00:00 2001 From: "quanghnguyen@linagora.com" Date: Wed, 18 Sep 2024 09:39:06 +0700 Subject: [PATCH 40/40] TW-1902: Update unit test for date time --- test/utils/date_time_extension_test.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/utils/date_time_extension_test.dart b/test/utils/date_time_extension_test.dart index 6c449a2483..017eb1f612 100644 --- a/test/utils/date_time_extension_test.dart +++ b/test/utils/date_time_extension_test.dart @@ -64,7 +64,7 @@ void main() async { testWidgets( 'GIVEN the date time to display is Monday of current week\n' 'THEN should display the Monday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Monday'; + const expectedDisplayText = 'Mon'; final currentTime = DateTime(2022, 1, 1); final timeToTest = DateTime(2021, 12, 27, 12, 5); @@ -105,7 +105,7 @@ void main() async { testWidgets( 'GIVEN the date time to display is Tuesday of current week\n' 'THEN should display Tuesday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Tuesday'; + const expectedDisplayText = 'Tue'; final currentTime = DateTime(2022, 1, 1); final timeToTest = DateTime(2021, 12, 28, 12, 5); @@ -146,7 +146,7 @@ void main() async { testWidgets( 'GIVEN the date time to display is Wednesday of current week\n' 'THEN should display Wednesday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Wednesday'; + const expectedDisplayText = 'Wed'; final currentTime = DateTime(2022, 1, 1); final timeToTest = DateTime(2021, 12, 29, 12, 5); @@ -187,7 +187,7 @@ void main() async { testWidgets( 'GIVEN the date time to display is Thursday of current week\n' 'THEN should display Thursday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Thursday'; + const expectedDisplayText = 'Thu'; final currentTime = DateTime(2022, 1, 1); final timeToTest = DateTime(2021, 12, 30, 12, 5); @@ -228,7 +228,7 @@ void main() async { testWidgets( 'GIVEN the date time to display is Friday of current week\n' 'THEN should display Friday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Friday'; + const expectedDisplayText = 'Fri'; final currentTime = DateTime(2022, 1, 1); final timeToTest = DateTime(2021, 12, 31, 12, 5); @@ -269,7 +269,7 @@ void main() async { testWidgets( 'GIVEN the date time to display is Saturday of current week\n' 'THEN should display Saturday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Saturday'; + const expectedDisplayText = 'Sat'; final currentTime = DateTime(2024, 2, 25); final timeToTest = DateTime(2024, 2, 24, 12, 5); @@ -310,7 +310,7 @@ void main() async { testWidgets( 'GIVEN the date time to display is Sunday of current week\n' 'THEN should display Sunday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Sunday'; + const expectedDisplayText = 'Sun'; final currentTime = DateTime(2022, 1, 1); final timeToTest = DateTime(2022, 1, 2, 12, 5); @@ -352,7 +352,7 @@ void main() async { 'GIVEN the current time is Sunday\n' 'AND the date time to display is Friday of current week\n' 'THEN should display Friday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Friday'; + const expectedDisplayText = 'Fri'; final currentTime = DateTime(2024, 3, 3); final timeToTest = DateTime(2024, 3, 1, 12, 5); @@ -394,7 +394,7 @@ void main() async { 'GIVEN the current time is Sunday\n' 'AND the date time to display is Saturday of current week\n' 'THEN should display Saturday\n', (WidgetTester tester) async { - const expectedDisplayText = 'Saturday'; + const expectedDisplayText = 'Sat'; final currentTime = DateTime(2024, 3, 3); final timeToTest = DateTime(2024, 3, 2, 12, 5);