From 90aada42343b35a94d52da97f761614c1600ca54 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 10 Jul 2023 00:22:29 +0700 Subject: [PATCH 1/6] TW-281: Mapping data from `List` to `List` --- .../extensions/room_extension.dart | 34 +++++++++++++++++++ .../extensions/room_list_extension.dart | 10 ++++++ 2 files changed, 44 insertions(+) create mode 100644 lib/presentation/extensions/room_list_extension.dart diff --git a/lib/presentation/extensions/room_extension.dart b/lib/presentation/extensions/room_extension.dart index 43ea1c481f..20cf6ec6b2 100644 --- a/lib/presentation/extensions/room_extension.dart +++ b/lib/presentation/extensions/room_extension.dart @@ -4,7 +4,11 @@ import 'package:collection/collection.dart'; import 'package:dartz/dartz.dart' hide id; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/presentation/extensions/asset_entity_extension.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:flutter/cupertino.dart'; import 'package:matrix/matrix.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/src/utils/file_send_request_credentials.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -275,4 +279,34 @@ extension SendImage on Room { User? getUser(mxId) { return getParticipants().firstWhereOrNull((user) => user.id == mxId); } + + bool isShowInChatList() { + return _isDirectChatHaveMessage() || _isGroupChat(); + } + + bool _isGroupChat() { + return !isDirectChat; + } + + bool _isDirectChatHaveMessage() { + return isDirectChat && _isLastEventInRoomIsMessage(); + } + + bool _isLastEventInRoomIsMessage() { + return [ + EventTypes.Message, + EventTypes.Sticker, + EventTypes.Encrypted, + ].contains(lastEvent?.type); + } + + PresentationSearch toPresentationSearch(BuildContext context) { + return PresentationSearch( + displayName: getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), + roomSummary: summary, + directChatMatrixID: directChatMatrixID, + matrixId: id, + searchTypeEnum: SearchTypeEnum.recentChat + ); + } } \ No newline at end of file diff --git a/lib/presentation/extensions/room_list_extension.dart b/lib/presentation/extensions/room_list_extension.dart new file mode 100644 index 0000000000..337314284b --- /dev/null +++ b/lib/presentation/extensions/room_list_extension.dart @@ -0,0 +1,10 @@ +import 'package:fluffychat/presentation/extensions/room_extension.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:matrix/matrix.dart'; + +extension RoomListExtension on List { + List toPresentationSearchList(BuildContext context) { + return map((room) => room.toPresentationSearch(context)).toList(); + } +} \ No newline at end of file From a486afccdea175dc16d279f288405465029b8a86 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 10 Jul 2023 00:23:06 +0700 Subject: [PATCH 2/6] TW-281: Mapping data from `List` to `List` --- .../presentation_contact_extension.dart | 11 ++++- .../presentation_contact_list_extension.dart | 9 ++++ .../model/presentation_search.dart | 43 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 lib/domain/model/extensions/contact/presentation_contact_list_extension.dart create mode 100644 lib/presentation/model/presentation_search.dart diff --git a/lib/domain/model/extensions/contact/presentation_contact_extension.dart b/lib/domain/model/extensions/contact/presentation_contact_extension.dart index 3dcad07780..6cb77528a5 100644 --- a/lib/domain/model/extensions/contact/presentation_contact_extension.dart +++ b/lib/domain/model/extensions/contact/presentation_contact_extension.dart @@ -1,5 +1,5 @@ -import 'package:fluffychat/domain/model/contact/contact.dart'; import 'package:fluffychat/presentation/model/presentation_contact.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; extension PresentaionContactExtension on PresentationContact { @@ -22,4 +22,13 @@ extension PresentaionContactExtension on PresentationContact { return false; } + + PresentationSearch toPresentationSearch() { + return PresentationSearch( + email: email, + displayName: displayName, + directChatMatrixID: matrixId, + searchTypeEnum: SearchTypeEnum.contact, + ); + } } \ No newline at end of file diff --git a/lib/domain/model/extensions/contact/presentation_contact_list_extension.dart b/lib/domain/model/extensions/contact/presentation_contact_list_extension.dart new file mode 100644 index 0000000000..1f52805d39 --- /dev/null +++ b/lib/domain/model/extensions/contact/presentation_contact_list_extension.dart @@ -0,0 +1,9 @@ +import 'package:fluffychat/domain/model/extensions/contact/presentation_contact_extension.dart'; +import 'package:fluffychat/presentation/model/presentation_contact.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; + +extension PresentaionContactListExtension on List { + List toPresentationSearchList() { + return map((contact) => contact.toPresentationSearch()).toList(); + } +} \ No newline at end of file diff --git a/lib/presentation/model/presentation_search.dart b/lib/presentation/model/presentation_search.dart new file mode 100644 index 0000000000..68cd56ccc1 --- /dev/null +++ b/lib/presentation/model/presentation_search.dart @@ -0,0 +1,43 @@ +import 'package:equatable/equatable.dart'; +import 'package:matrix/matrix.dart'; + +enum SearchTypeEnum { + contact, + recentChat, +} + +class PresentationSearch extends Equatable { + + final String? email; + + final String? displayName; + + final String? matrixId; + + final SearchTypeEnum? searchTypeEnum; + + final RoomSummary? roomSummary; + + final String? directChatMatrixID; + + const PresentationSearch({ + this.email, + this.displayName, + this.matrixId, + this.searchTypeEnum, + this.roomSummary, + this.directChatMatrixID + }); + + bool get isContact => searchTypeEnum == SearchTypeEnum.contact; + + @override + List get props => [ + email, + displayName, + matrixId, + searchTypeEnum, + roomSummary, + directChatMatrixID + ]; +} \ No newline at end of file From 4af06ce1bff93dd1835fa3bc987ad8d1a807008b Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 10 Jul 2023 00:23:50 +0700 Subject: [PATCH 3/6] TW-281: Create `SearchInteractor` for hanlde get contacts list and recent chat list --- .../search/search_interactor_state.dart | 23 ++++++ .../usecase/search/search_interactor.dart | 79 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 lib/domain/app_state/search/search_interactor_state.dart create mode 100644 lib/domain/usecase/search/search_interactor.dart diff --git a/lib/domain/app_state/search/search_interactor_state.dart b/lib/domain/app_state/search/search_interactor_state.dart new file mode 100644 index 0000000000..82301a838f --- /dev/null +++ b/lib/domain/app_state/search/search_interactor_state.dart @@ -0,0 +1,23 @@ +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; + +class GetContactAndRecentChatSuccess extends Success { + final List presentationSearches; + + const GetContactAndRecentChatSuccess({required this.presentationSearches}); + + @override + List get props => [presentationSearches]; +} + +class GetContactAndRecentChatFailed extends Failure { + + final dynamic exception; + + const GetContactAndRecentChatFailed({required this.exception}); + + @override + List get props => [exception]; + +} \ No newline at end of file diff --git a/lib/domain/usecase/search/search_interactor.dart b/lib/domain/usecase/search/search_interactor.dart new file mode 100644 index 0000000000..f2881c506b --- /dev/null +++ b/lib/domain/usecase/search/search_interactor.dart @@ -0,0 +1,79 @@ +import 'package:dartz/dartz.dart'; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/domain/app_state/search/search_interactor_state.dart'; +import 'package:fluffychat/domain/model/contact/contact_query.dart'; +import 'package:fluffychat/domain/model/extensions/contact/contact_extension.dart'; +import 'package:fluffychat/domain/model/extensions/contact/presentation_contact_list_extension.dart'; +import 'package:fluffychat/domain/repository/contact_repository.dart'; +import 'package:fluffychat/presentation/extensions/room_extension.dart'; +import 'package:fluffychat/presentation/extensions/room_list_extension.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:matrix/matrix.dart'; + +class SearchContactsAndRecentChatInteractor { + final ContactRepository contactRepository = getIt.get(); + + SearchContactsAndRecentChatInteractor(); + + Stream> execute({ + required BuildContext context, + required String keyword, + int? limitContacts, + int? limitRecentChats, + bool enableSearch = false + }) async* { + try { + if (enableSearch) { + final recentChat = await _searchRecentChat(context, keyword); + final contacts = await contactRepository.searchContact(query: ContactQuery(keyword: keyword), limit: limitContacts); + + final presentationSearches = _comparePresentationSearches( + recentChat.toPresentationSearchList(context), + contacts.expand((contact) => contact.toPresentationContacts()).toList().toPresentationSearchList() + ); + yield Right(GetContactAndRecentChatSuccess(presentationSearches: presentationSearches)); + } else { + final rooms = await _getRecentChat(context, limitRecentChats: limitRecentChats); + yield Right(GetContactAndRecentChatSuccess(presentationSearches: rooms.toPresentationSearchList(context))); + } + } catch (e) { + yield Left(GetContactAndRecentChatFailed(exception: e)); + } + } + + Future> _getRecentChat(BuildContext context, {int? limitRecentChats}) async { + return Matrix.of(context).client.rooms.where((room) => !room.isSpace && !room.isStoryRoom) + .where((room) => room.isShowInChatList()) + .take(limitRecentChats ?? 0) + .toList(); + } + + Future> _searchRecentChat(BuildContext context, String keyword) async { + return Matrix.of(context).client.rooms.where((room) => !room.isSpace && !room.isStoryRoom) + .where((room) => room.isShowInChatList()) + .where((room) => + room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)) + .toLowerCase() + .contains(keyword.toLowerCase()) + ).toList(); + } + + List _comparePresentationSearches(List recentChat, List contacts) { + final isDuplicateElement = contacts.where((contact) { + return recentChat.any((recentChat) => recentChat.directChatMatrixID == contact.directChatMatrixID); + }).toList(); + if (isDuplicateElement.isNotEmpty) { + final presentationSearches = recentChat + contacts; + presentationSearches.removeWhere((contact) => isDuplicateElement.contains(contact)); + return presentationSearches; + } else { + return recentChat + contacts; + } + } +} \ No newline at end of file From d4cb402d9e1b8c534f340c345317f283fecc6515 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 10 Jul 2023 00:24:36 +0700 Subject: [PATCH 4/6] TW-281: Create `SearchDI` and `SearchController` for hanlde UI and logic search --- lib/di/search/search_di.dart | 39 ++++++ lib/pages/search/search.dart | 151 +++++++++++++++++++----- lib/pages/search/search_controller.dart | 89 ++++++++++++++ 3 files changed, 250 insertions(+), 29 deletions(-) create mode 100644 lib/di/search/search_di.dart create mode 100644 lib/pages/search/search_controller.dart diff --git a/lib/di/search/search_di.dart b/lib/di/search/search_di.dart new file mode 100644 index 0000000000..5c6e3db9df --- /dev/null +++ b/lib/di/search/search_di.dart @@ -0,0 +1,39 @@ +import 'package:fluffychat/data/datasource/tom_contacts_datasource.dart'; +import 'package:fluffychat/data/datasource_impl/contact/tom_contacts_datasource_impl.dart'; +import 'package:fluffychat/data/network/contact/tom_contact_api.dart'; +import 'package:fluffychat/data/repository/contact/tom_contact_repository_impl.dart'; +import 'package:fluffychat/di/base_di.dart'; +import 'package:fluffychat/domain/repository/contact_repository.dart'; +import 'package:fluffychat/domain/usecase/fetch_contacts_interactor.dart'; +import 'package:fluffychat/domain/usecase/load_more_internal_contacts.dart'; +import 'package:fluffychat/domain/usecase/lookup_contacts_interactor.dart'; +import 'package:fluffychat/domain/usecase/search/search_interactor.dart'; +import 'package:get_it/get_it.dart'; +import 'package:matrix/matrix.dart'; + +class SearchDI extends BaseDI { + + @override + String get scopeName => 'Search'; + + @override + void setUp(GetIt get) { + Logs().d('SearchDI::setUp()'); + + get.registerFactory(() => TomContactAPI()); + + get.registerFactory(() => TomContactsDatasourceImpl()); + + get.registerFactory(() => TomContactRepositoryImpl()); + + get.registerFactory(() => LookupContactsInteractor()); + + get.registerFactory(() => FetchContactsInteractor()); + + get.registerFactory(() => LoadMoreInternalContacts()); + + get.registerFactory(() => SearchContactsAndRecentChatInteractor()); + + Logs().d('SearchDI::setUp() - done'); + } +} \ No newline at end of file diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 0c0d85a779..8b5bf0609d 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -1,16 +1,26 @@ +import 'dart:async'; + import 'package:dartz/dartz.dart' hide State; import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/contact/get_contacts_success.dart'; -import 'package:fluffychat/mixin/comparable_presentation_contact_mixin.dart'; +import 'package:fluffychat/domain/app_state/search/search_interactor_state.dart'; +import 'package:fluffychat/domain/model/extensions/contact/contact_extension.dart'; +import 'package:fluffychat/domain/model/extensions/contact/presentation_contact_list_extension.dart'; +import 'package:fluffychat/domain/usecase/fetch_contacts_interactor.dart'; +import 'package:fluffychat/domain/usecase/search/search_interactor.dart'; +import 'package:fluffychat/mixin/comparable_presentation_search_mixin.dart'; +import 'package:fluffychat/pages/search/search_controller.dart'; import 'package:fluffychat/pages/search/search_view.dart'; -import 'package:fluffychat/pages/new_private_chat/fetch_contacts_controller.dart'; -import 'package:fluffychat/presentation/model/presentation_contact.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; +import 'package:fluffychat/presentation/mixin/load_more_search_mixin.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; import 'package:rxdart/rxdart.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:vrouter/vrouter.dart'; class Search extends StatefulWidget { const Search({super.key}); @@ -19,35 +29,27 @@ class Search extends StatefulWidget { State createState() => SearchController(); } -class SearchController extends State with ComparablePresentationContactMixin { +class SearchController extends State with ComparablePresentationSearchMixin, LoadMoreSearchMixin { static const int limitRecentChats = 3; + static const int limitContacts = 30; static const int limitRecentContacts = 7; - AutoScrollController recentChatsController = AutoScrollController(); - ScrollController customScrollController = ScrollController(); - final fetchContactsController = FetchContactsController(); - final contactsStreamController = BehaviorSubject>(); - - isFilteredRecentChat(Room room) { - return !room.isSpace && room.isDirectChat && !room.isStoryRoom; - } - - List get filteredRoomsForAll => Matrix.of(context) - .client - .rooms - .where((room) => !room.isSpace && !room.isStoryRoom) - .take(limitRecentChats) - .toList(); + bool isSearching = false; + bool isSearchMode = false; + ValueNotifier? isSearchModeNotifier; - void listenContactsStartList() { - fetchContactsController.streamController.stream.listen((event) { - Logs().d('SearchController::fetchContacts() - event: $event'); - contactsStreamController.add(event); - }); - } + SearchContactAndRecentChatController? searchContactAndRecentChatController; + final AutoScrollController recentChatsController = AutoScrollController(); + final ScrollController customScrollController = ScrollController(); + final _fetchContactsInteractor = getIt.get(); + final _searchContactsAndRecentChatInteractor = getIt.get(); + final getContactStream = StreamController>(); + final contactsStreamController = BehaviorSubject>(); + final getContactAndRecentChatStream = StreamController>(); + final contactsAndRecentChatStreamController = BehaviorSubject>(); - Future getProfile(BuildContext context, PresentationContact contact) async { + Future getProfile(BuildContext context, PresentationSearch contact) async { final client = Matrix.of(context).client; if (contact.matrixId == null) { return Future.error(Exception("MatrixId is null")); @@ -61,19 +63,110 @@ class SearchController extends State with ComparablePresentationContact } } + + void listenContactsStartList() { + getContactStream.stream.listen((event) { + Logs().d('SearchController::getContactStream() - event: $event'); + contactsStreamController.add(event); + }); + + getContactAndRecentChatStream.stream.listen((event) { + Logs().d('SearchController::getContactAndRecentChatStream() - event: $event'); + contactsAndRecentChatStreamController.add(event); + }); + } + + void _fetchCurrentTomContacts({ + int? limit, + int? offset, + }) { + _fetchContactsInteractor.execute(limit: limit, offset: offset).listen((event) { + getContactStream.add(event); + }); + } + + void _getContactAndRecentChat() { + _searchContactsAndRecentChatInteractor.execute( + context: context, + keyword: '', + limitRecentChats: limitRecentChats, + limitContacts: limitContacts, + ).listen((event) { + getContactAndRecentChatStream.add(event); + }); + } + + List getContactsFromFetchStream(Either event) { + return event.fold>( + (failure) => [], + (success) { + final currentContacts = success.contacts.expand((contact) => contact.toPresentationContacts()); + updateLastContactIndex(oldContactList.length); + oldContactList = currentContacts.toList().toPresentationSearchList(); + lastContactIndexNotifier.value = oldContactList.length; + return oldContactList; + }, + ).toList(); + } + + List getContactsAndRecentChatStream(Either event) { + return event.fold>( + (failure) => [], + (success) { + final contactsAndRecentChat = success.presentationSearches; + updateLastContactAndRecentChatIndex(contactsAndRecentChat.length); + return contactsAndRecentChat; + }, + ).toList(); + } + + void listenSearchContactAndRecentChat() { + searchContactAndRecentChatController?.getContactAndRecentChatStream.stream.listen((event) { + Logs().d('NewPrivateChatController::_fetchRemoteContacts() - event: $event'); + getContactAndRecentChatStream.add(event); + }); + } + + void goToChatScreen(PresentationSearch presentationSearch) { + if (presentationSearch.isContact) { + showFutureLoadingDialog( + context: context, + future: () async { + if (presentationSearch.directChatMatrixID != null && presentationSearch.directChatMatrixID!.isNotEmpty) { + final roomId = await Matrix.of(context).client.startDirectChat(presentationSearch.directChatMatrixID!); + VRouter.of(context).toSegments(['rooms', roomId]); + } + }, + ); + } else { + if (presentationSearch.matrixId != null) { + VRouter.of(context).toSegments(['rooms', presentationSearch.matrixId!]); + } + } + + } + @override void initState() { + searchContactAndRecentChatController = SearchContactAndRecentChatController(context); + searchContactAndRecentChatController?.init(); + isSearchModeNotifier = ValueNotifier(false); listenContactsStartList(); + listenSearchContactAndRecentChat(); super.initState(); - fetchContactsController.fetchCurrentTomContacts(limit: limitRecentContacts); + _fetchCurrentTomContacts(limit: limitRecentContacts); + _getContactAndRecentChat(); } @override void dispose() { recentChatsController.dispose(); customScrollController.dispose(); - fetchContactsController.dispose(); + getContactStream.close(); contactsStreamController.close(); + getContactAndRecentChatStream.close(); + contactsAndRecentChatStreamController.close(); + searchContactAndRecentChatController?.dispose(); super.dispose(); } diff --git a/lib/pages/search/search_controller.dart b/lib/pages/search/search_controller.dart new file mode 100644 index 0000000000..ad63880795 --- /dev/null +++ b/lib/pages/search/search_controller.dart @@ -0,0 +1,89 @@ +import 'dart:async'; + +import 'package:dartz/dartz.dart'; +import 'package:debounce_throttle/debounce_throttle.dart'; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/domain/app_state/search/search_interactor_state.dart'; +import 'package:fluffychat/domain/usecase/search/search_interactor.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; + +class SearchContactAndRecentChatController { + final BuildContext context; + SearchContactAndRecentChatController(this.context); + + static const debouncerIntervalInMilliseconds = 300; + String searchKeyword = ""; + + final SearchContactsAndRecentChatInteractor _searchContactsAndRecentChatInteractor = getIt.get(); + Debouncer? _debouncer; + final TextEditingController textEditingController = TextEditingController(); + StreamController> getContactAndRecentChatStream = StreamController(); + void Function(String)? onSearchKeywordChanged; + late final isSearchModeNotifier = ValueNotifier(false); + + + + void init() { + _initializeDebouncer(); + textEditingController.addListener(() { + if (textEditingController.text.isNotEmpty) { + isSearchModeNotifier.value = true; + } else { + isSearchModeNotifier.value = false; + } + onSearchBarChanged(textEditingController.text); + }); + } + + void _initializeDebouncer() { + _debouncer = Debouncer( + const Duration(milliseconds: debouncerIntervalInMilliseconds), + initialValue: '', + ); + + _debouncer?.values.listen((keyword) async { + Logs().d("SearchContactAndRecentChatController::_initializeDebouncer: searchKeyword: $searchKeyword"); + searchKeyword = keyword; + if (onSearchKeywordChanged != null) { + onSearchKeywordChanged!(textEditingController.text); + } + fetchLookupContacts(); + }); + } + + void fetchLookupContacts({ + int? limitContacts, + int? limitRecentChats, + }) { + _searchContactsAndRecentChatInteractor.execute( + keyword: searchKeyword, + context: context, + limitContacts: limitContacts, + limitRecentChats: limitRecentChats, + enableSearch: true, + ).listen((event) { + getContactAndRecentChatStream.add(event); + }); + } + + void onSearchBarChanged(String keyword) { + _debouncer?.setValue(keyword); + searchKeyword = keyword; + } + + void onCloseSearchTapped() { + textEditingController.clear(); + } + + void clearSearchBar() { + textEditingController.clear(); + } + + void dispose() { + _debouncer?.cancel(); + textEditingController.dispose(); + getContactAndRecentChatStream.close(); + } +} \ No newline at end of file From 93bc9ece38fad03812c6a1cf7273f546f10911a1 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 10 Jul 2023 00:24:51 +0700 Subject: [PATCH 5/6] TW-281: Handle UI when searching --- lib/config/routes.dart | 22 +-- lib/di/search/search_di.dart | 14 +- .../presentation_contact_extension.dart | 2 +- .../usecase/search/search_interactor.dart | 40 +++-- .../comparable_presentation_search_mixin.dart | 24 +++ lib/pages/chat_details/chat_details_view.dart | 4 +- lib/pages/chat_list/chat_list.dart | 23 +-- .../search/recent_contacts_banner_widget.dart | 18 +-- lib/pages/search/recent_item_widget.dart | 103 +++++++----- lib/pages/search/search.dart | 28 ++-- lib/pages/search/search_controller.dart | 23 +-- lib/pages/search/search_view.dart | 65 +++++--- .../extensions/room_extension.dart | 9 +- .../extensions/room_list_extension.dart | 5 +- .../extensions/room_summary_extension.dart | 6 + .../mixin/load_more_search_mixin.dart | 30 ++++ .../model/presentation_search.dart | 10 +- lib/widgets/vwidget_with_dependencies.dart | 153 ++++++++++++++++++ 18 files changed, 417 insertions(+), 162 deletions(-) create mode 100644 lib/mixin/comparable_presentation_search_mixin.dart create mode 100644 lib/presentation/extensions/room_summary_extension.dart create mode 100644 lib/presentation/mixin/load_more_search_mixin.dart create mode 100644 lib/widgets/vwidget_with_dependencies.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 7aeb9fa4dc..cce35b990b 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/di/chat/chat_di.dart'; import 'package:fluffychat/di/contact/contact_di.dart'; +import 'package:fluffychat/di/search/search_di.dart'; import 'package:fluffychat/pages/add_story/add_story.dart'; import 'package:fluffychat/pages/archive/archive.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -34,6 +35,7 @@ import 'package:fluffychat/widgets/layouts/loading_view.dart'; import 'package:fluffychat/widgets/layouts/side_view_layout.dart'; import 'package:fluffychat/widgets/layouts/two_column_layout.dart'; import 'package:fluffychat/widgets/log_view.dart'; +import 'package:fluffychat/widgets/vwidget_with_dependencies.dart'; import 'package:fluffychat/widgets/vwidget_with_dependency.dart'; import 'package:flutter/material.dart'; import 'package:vrouter/vrouter.dart'; @@ -173,11 +175,11 @@ class AppRoutes { ), ] ), - VWidgetWithDependency( - di: ContactDI(), - path: '/search', - widget: const Search(), - buildTransition: rightToLeftTransition, + VWidgetWithDependencies( + dies: [SearchDI()], + path: '/search', + widget: const Search(), + buildTransition: rightToLeftTransition, ), ], ) @@ -264,11 +266,11 @@ class AppRoutes { ), ], ), - VWidgetWithDependency( - di: ContactDI(), - path: '/search', - widget: const Search(), - buildTransition: rightToLeftTransition + VWidgetWithDependencies( + dies: [SearchDI()], + path: '/search', + widget: const Search(), + buildTransition: rightToLeftTransition ), ], ), diff --git a/lib/di/search/search_di.dart b/lib/di/search/search_di.dart index 5c6e3db9df..8eb1f32331 100644 --- a/lib/di/search/search_di.dart +++ b/lib/di/search/search_di.dart @@ -20,19 +20,19 @@ class SearchDI extends BaseDI { void setUp(GetIt get) { Logs().d('SearchDI::setUp()'); - get.registerFactory(() => TomContactAPI()); + get.registerSingleton(TomContactAPI()); - get.registerFactory(() => TomContactsDatasourceImpl()); + get.registerSingleton(TomContactsDatasourceImpl()); - get.registerFactory(() => TomContactRepositoryImpl()); + get.registerSingleton(TomContactRepositoryImpl()); - get.registerFactory(() => LookupContactsInteractor()); + get.registerSingleton(LookupContactsInteractor()); - get.registerFactory(() => FetchContactsInteractor()); + get.registerSingleton(FetchContactsInteractor()); - get.registerFactory(() => LoadMoreInternalContacts()); + get.registerSingleton(LoadMoreInternalContacts()); - get.registerFactory(() => SearchContactsAndRecentChatInteractor()); + get.registerSingleton(SearchContactsAndRecentChatInteractor()); Logs().d('SearchDI::setUp() - done'); } diff --git a/lib/domain/model/extensions/contact/presentation_contact_extension.dart b/lib/domain/model/extensions/contact/presentation_contact_extension.dart index 6cb77528a5..c3bddeba6f 100644 --- a/lib/domain/model/extensions/contact/presentation_contact_extension.dart +++ b/lib/domain/model/extensions/contact/presentation_contact_extension.dart @@ -28,7 +28,7 @@ extension PresentaionContactExtension on PresentationContact { email: email, displayName: displayName, directChatMatrixID: matrixId, - searchTypeEnum: SearchTypeEnum.contact, + searchElementTypeEnum: SearchElementTypeEnum.contact, ); } } \ No newline at end of file diff --git a/lib/domain/usecase/search/search_interactor.dart b/lib/domain/usecase/search/search_interactor.dart index f2881c506b..9ae04b2d96 100644 --- a/lib/domain/usecase/search/search_interactor.dart +++ b/lib/domain/usecase/search/search_interactor.dart @@ -10,10 +10,6 @@ import 'package:fluffychat/presentation/extensions/room_extension.dart'; import 'package:fluffychat/presentation/extensions/room_list_extension.dart'; import 'package:fluffychat/presentation/model/presentation_search.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter/cupertino.dart'; import 'package:matrix/matrix.dart'; class SearchContactsAndRecentChatInteractor { @@ -22,7 +18,8 @@ class SearchContactsAndRecentChatInteractor { SearchContactsAndRecentChatInteractor(); Stream> execute({ - required BuildContext context, + required List rooms, + required MatrixLocalizations matrixLocalizations, required String keyword, int? limitContacts, int? limitRecentChats, @@ -30,38 +27,47 @@ class SearchContactsAndRecentChatInteractor { }) async* { try { if (enableSearch) { - final recentChat = await _searchRecentChat(context, keyword); + final recentChat = await _searchRecentChat(rooms: rooms, matrixLocalizations: matrixLocalizations, keyword: keyword); final contacts = await contactRepository.searchContact(query: ContactQuery(keyword: keyword), limit: limitContacts); final presentationSearches = _comparePresentationSearches( - recentChat.toPresentationSearchList(context), - contacts.expand((contact) => contact.toPresentationContacts()).toList().toPresentationSearchList() + recentChat.toPresentationSearchList(matrixLocalizations), + contacts.expand((contact) => contact.toPresentationContacts()) + .toList() + .toPresentationSearchList() ); yield Right(GetContactAndRecentChatSuccess(presentationSearches: presentationSearches)); } else { - final rooms = await _getRecentChat(context, limitRecentChats: limitRecentChats); - yield Right(GetContactAndRecentChatSuccess(presentationSearches: rooms.toPresentationSearchList(context))); + final recentChat = await _getRecentChat(rooms: rooms, limitRecentChats: limitRecentChats); + yield Right(GetContactAndRecentChatSuccess(presentationSearches: recentChat.toPresentationSearchList(matrixLocalizations))); } } catch (e) { yield Left(GetContactAndRecentChatFailed(exception: e)); } } - Future> _getRecentChat(BuildContext context, {int? limitRecentChats}) async { - return Matrix.of(context).client.rooms.where((room) => !room.isSpace && !room.isStoryRoom) + Future> _getRecentChat({ + required List rooms, + int? limitRecentChats + }) async { + return rooms.where((room) => !room.isSpace && !room.isStoryRoom) .where((room) => room.isShowInChatList()) .take(limitRecentChats ?? 0) .toList(); } - Future> _searchRecentChat(BuildContext context, String keyword) async { - return Matrix.of(context).client.rooms.where((room) => !room.isSpace && !room.isStoryRoom) - .where((room) => room.isShowInChatList()) + Future> _searchRecentChat({ + required List rooms, + required MatrixLocalizations matrixLocalizations, + required String keyword, + }) async { + return rooms.where((room) => !room.isSpace && !room.isStoryRoom) .where((room) => - room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)) + room.getLocalizedDisplayname(matrixLocalizations) .toLowerCase() .contains(keyword.toLowerCase()) - ).toList(); + && room.isShowInChatList() + ).toList(); } List _comparePresentationSearches(List recentChat, List contacts) { diff --git a/lib/mixin/comparable_presentation_search_mixin.dart b/lib/mixin/comparable_presentation_search_mixin.dart new file mode 100644 index 0000000000..5a58813b9f --- /dev/null +++ b/lib/mixin/comparable_presentation_search_mixin.dart @@ -0,0 +1,24 @@ +import 'package:fluffychat/presentation/model/presentation_search.dart'; + +class ComparablePresentationSearchMixin { + int comparePresentationSearch(PresentationSearch searchResultOne, PresentationSearch searchResultTwo) { + final bufferOne = StringBuffer(); + final bufferTwo = StringBuffer(); + + bufferOne.writeAll([ + searchResultOne.displayName ?? "", + searchResultOne.matrixId ?? "", + searchResultOne.email ?? "", + searchResultTwo.searchElementTypeEnum ?? SearchElementTypeEnum.contact + ]); + + bufferTwo.writeAll([ + searchResultTwo.displayName ?? "", + searchResultTwo.matrixId ?? "", + searchResultTwo.email ?? "", + searchResultTwo.searchElementTypeEnum ?? SearchElementTypeEnum.contact + ]); + + return bufferOne.toString().compareTo(bufferTwo.toString()); + } +} \ No newline at end of file diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 988e8de9c5..43784fb4d5 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/presentation/extensions/room_summary_extension.dart'; import 'package:fluffychat/widgets/avatar/avatar_style.dart'; import 'package:flutter/material.dart'; @@ -37,8 +38,7 @@ class ChatDetailsView extends StatelessWidget { } controller.members!.removeWhere((u) => u.membership == Membership.leave); - final actualMembersCount = (room.summary.mInvitedMemberCount ?? 0) + - (room.summary.mJoinedMemberCount ?? 0); + final actualMembersCount = room.summary.actualMembersCount; final canRequestMoreMembers = controller.members!.length < actualMembersCount; final iconColor = Theme.of(context).textTheme.bodyLarge!.color; diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index f278a76228..6ca59cad25 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/domain/usecases/get_recovery_words_interactor.dart'; import 'package:fluffychat/pages/bootstrap/tom_bootstrap_dialog.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/pages/settings_security/settings_security.dart'; +import 'package:fluffychat/presentation/extensions/room_extension.dart'; import 'package:fluffychat/utils/famedlysdk_store.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; @@ -135,7 +136,7 @@ class ChatListController extends State .client .rooms .where(getRoomFilterByActiveFilter(_activeFilterAllChats)) - .where(isShowInChatList) + .where((room) => room.isShowInChatList()) .toList(); bool isSearchMode = false; @@ -148,26 +149,6 @@ class ChatListController extends State bool isSearching = false; static const String _serverStoreNamespace = 'im.fluffychat.search.server'; - bool isShowInChatList(Room room) { - return isDirectChatHaveMessage(room) || isGroupChat(room); - } - - bool isGroupChat(Room room) { - return !room.isDirectChat; - } - - bool isDirectChatHaveMessage(Room room) { - return room.isDirectChat && isLastEventInRoomIsMessage(room); - } - - bool isLastEventInRoomIsMessage(Room room) { - return [ - EventTypes.Message, - EventTypes.Sticker, - EventTypes.Encrypted, - ].contains(room.lastEvent?.type); - } - void setServer() async { final newServer = await showTextInputDialog( useRootNavigator: false, diff --git a/lib/pages/search/recent_contacts_banner_widget.dart b/lib/pages/search/recent_contacts_banner_widget.dart index 7f53809d01..37d701b6d1 100644 --- a/lib/pages/search/recent_contacts_banner_widget.dart +++ b/lib/pages/search/recent_contacts_banner_widget.dart @@ -4,7 +4,7 @@ import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/domain/app_state/contact/get_contacts_success.dart'; import 'package:fluffychat/pages/search/recent_contacts_banner_widget_style.dart'; import 'package:fluffychat/pages/search/search.dart'; -import 'package:fluffychat/presentation/model/presentation_contact.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; import 'package:fluffychat/utils/display_name_widget.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:flutter/material.dart'; @@ -27,9 +27,9 @@ class RecentContactsBannerWidget extends StatelessWidget { return const SizedBox(); } - final contactsList = searchController.fetchContactsController.getContactsFromFetchStream(snapshot.data!); + final contactsList = searchController.getContactsFromFetchStream(snapshot.data!); - final contactsListSorted = contactsList.sorted((pre, next) => searchController.comparePresentationContacts(pre, next)); + final contactsListSorted = contactsList.sorted((pre, next) => searchController.comparePresentationSearch(pre, next)); if (contactsListSorted.isEmpty) { return const SizedBox(); @@ -42,7 +42,7 @@ class RecentContactsBannerWidget extends StatelessWidget { itemCount: contactsListSorted.length, itemBuilder: (context, index) { return ChatRecentContactItemWidget( - contact: contactsListSorted[index], + presentationSearch: contactsListSorted[index], searchController: searchController, ); }, @@ -54,13 +54,13 @@ class RecentContactsBannerWidget extends StatelessWidget { class ChatRecentContactItemWidget extends StatelessWidget { final SearchController searchController; - final PresentationContact contact; - const ChatRecentContactItemWidget({super.key, required this.contact, required this.searchController}); + final PresentationSearch presentationSearch; + const ChatRecentContactItemWidget({super.key, required this.presentationSearch, required this.searchController}); @override Widget build(BuildContext context) { return FutureBuilder( - future: searchController.getProfile(context, contact), + future: searchController.getProfile(context, presentationSearch), builder: (context, snapshot) { return SizedBox( width: RecentContactsBannerWidgetStyle.chatRecentContactItemWidth, @@ -71,14 +71,14 @@ class ChatRecentContactItemWidget extends StatelessWidget { width: RecentContactsBannerWidgetStyle.avatarWidthSize, child: Avatar( mxContent: snapshot.data?.avatarUrl, - name: contact.displayName, + name: presentationSearch.displayName, ), ), Padding( padding: RecentContactsBannerWidgetStyle.chatRecentContactItemPadding, child: BuildDisplayName( profileDisplayName: snapshot.data?.displayname, - contactDisplayName: contact.displayName + contactDisplayName: presentationSearch.displayName ), ), ], diff --git a/lib/pages/search/recent_item_widget.dart b/lib/pages/search/recent_item_widget.dart index b5b8a4fc91..0f1174e3c4 100644 --- a/lib/pages/search/recent_item_widget.dart +++ b/lib/pages/search/recent_item_widget.dart @@ -1,30 +1,28 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/search/recent_item_widget_style.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/pages/search/search.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; +import 'package:fluffychat/presentation/extensions/room_summary_extension.dart'; import 'package:matrix/matrix.dart'; class RecentItemWidget extends StatelessWidget { - final Room room; + final PresentationSearch presentationSearch; + final SearchController searchController; final void Function()? onTap; - const RecentItemWidget( - this.room, - { - this.onTap, - Key? key, - } - ) : super(key: key); + const RecentItemWidget({ + required this.presentationSearch, + required this.searchController, + this.onTap, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { - final displayName = room.getLocalizedDisplayname( - MatrixLocals(L10n.of(context)!), - ); - final directChatMatrixID = room.directChatMatrixID; return Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius), clipBehavior: Clip.hardEdge, @@ -39,15 +37,20 @@ class RecentItemWidget extends StatelessWidget { child: ListTile( contentPadding: EdgeInsets.zero, title: Row( - crossAxisAlignment: directChatMatrixID != null + crossAxisAlignment: presentationSearch.isContact ? CrossAxisAlignment.start : CrossAxisAlignment.center, children: [ SizedBox( width: RecentItemStyle.avatarSize, - child: Avatar( - mxContent: room.avatar, - name: displayName, + child: FutureBuilder( + future: searchController.getProfile(context, presentationSearch), + builder: (context, snapshot) { + return Avatar( + mxContent: snapshot.data?.avatarUrl, + name: presentationSearch.displayName, + ); + } ), ), const SizedBox(width: 8), @@ -56,7 +59,7 @@ class RecentItemWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - displayName, + presentationSearch.displayName ?? "", overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, @@ -68,7 +71,7 @@ class RecentItemWidget extends StatelessWidget { ), ), ), - _buildInformationWidget(context, directChatMatrixID: directChatMatrixID) + _buildInformationWidget(context), ], ), ), @@ -81,28 +84,26 @@ class RecentItemWidget extends StatelessWidget { ); } - Widget _buildInformationWidget( - BuildContext context, - { - String? directChatMatrixID, - } - ) { - if (directChatMatrixID == null) { - return _GroupChatInformation(room: room); + Widget _buildInformationWidget(BuildContext context) { + if (presentationSearch.isContact) { + return _ContactInformation(presentationSearch: presentationSearch); } else { - return _DirectChatInformation(room: room); + if (presentationSearch.directChatMatrixID == null) { + return _GroupChatInformation(presentationSearch: presentationSearch); + } else { + return _DirectChatInformation(presentationSearch: presentationSearch); + } } } } class _GroupChatInformation extends StatelessWidget { - final Room room; - const _GroupChatInformation({required this.room}); + final PresentationSearch presentationSearch; + const _GroupChatInformation({required this.presentationSearch}); @override Widget build(BuildContext context) { - final actualMembersCount = (room.summary.mInvitedMemberCount ?? 0) + - (room.summary.mJoinedMemberCount ?? 0); + final actualMembersCount = presentationSearch.roomSummary?.actualMembersCount ?? 0; return Text( L10n.of(context)!.membersCount(actualMembersCount), overflow: TextOverflow.ellipsis, @@ -121,16 +122,39 @@ class _GroupChatInformation extends StatelessWidget { class _DirectChatInformation extends StatelessWidget { - final Room room; - const _DirectChatInformation({required this.room}); + final PresentationSearch presentationSearch; + const _DirectChatInformation({required this.presentationSearch}); + + @override + Widget build(BuildContext context) { + return Text( + presentationSearch.roomSummary?.mHeroes?.first ?? "", + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + style: Theme.of(context).textTheme.bodyMedium?.merge( + TextStyle( + overflow: TextOverflow.ellipsis, + letterSpacing: 0.15, + color: LinagoraRefColors.material().tertiary[30], + ), + ), + ); + } +} + +class _ContactInformation extends StatelessWidget { + final PresentationSearch presentationSearch; + const _ContactInformation({required this.presentationSearch}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - room.summary.mHeroes?.first ?? "", + if (presentationSearch.email != null) + Text( + presentationSearch.email ?? "", overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, @@ -142,8 +166,9 @@ class _DirectChatInformation extends StatelessWidget { ), ), ), - Text( - room.name, + if (presentationSearch.directChatMatrixID != null) + Text( + presentationSearch.directChatMatrixID ?? "", overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, @@ -154,7 +179,7 @@ class _DirectChatInformation extends StatelessWidget { color: LinagoraRefColors.material().tertiary[30], ), ), - ) + ), ], ); } diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 8b5bf0609d..fa6aaaa610 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -14,9 +14,12 @@ import 'package:fluffychat/pages/search/search_controller.dart'; import 'package:fluffychat/pages/search/search_view.dart'; import 'package:fluffychat/presentation/mixin/load_more_search_mixin.dart'; import 'package:fluffychat/presentation/model/presentation_search.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:future_loading_dialog/future_loading_dialog.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:rxdart/rxdart.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; @@ -31,9 +34,9 @@ class Search extends StatefulWidget { class SearchController extends State with ComparablePresentationSearchMixin, LoadMoreSearchMixin { - static const int limitRecentChats = 3; - static const int limitContacts = 30; - static const int limitRecentContacts = 7; + static const int limitPrefetchedRecentChats = 3; + static const int limitSearchingPrefetchedRecentContacts = 30; + static const int limitPrefetchedRecentContacts = 7; bool isSearching = false; bool isSearchMode = false; @@ -87,10 +90,11 @@ class SearchController extends State with ComparablePresentationSearchM void _getContactAndRecentChat() { _searchContactsAndRecentChatInteractor.execute( - context: context, + rooms: Matrix.of(context).client.rooms, + matrixLocalizations: MatrixLocals(L10n.of(context)!), keyword: '', - limitRecentChats: limitRecentChats, - limitContacts: limitContacts, + limitRecentChats: limitPrefetchedRecentChats, + limitContacts: limitSearchingPrefetchedRecentContacts, ).listen((event) { getContactAndRecentChatStream.add(event); }); @@ -122,7 +126,7 @@ class SearchController extends State with ComparablePresentationSearchM void listenSearchContactAndRecentChat() { searchContactAndRecentChatController?.getContactAndRecentChatStream.stream.listen((event) { - Logs().d('NewPrivateChatController::_fetchRemoteContacts() - event: $event'); + Logs().d('SearchController::getContactAndRecentChatStream() - event: $event'); getContactAndRecentChatStream.add(event); }); } @@ -149,13 +153,17 @@ class SearchController extends State with ComparablePresentationSearchM @override void initState() { searchContactAndRecentChatController = SearchContactAndRecentChatController(context); - searchContactAndRecentChatController?.init(); isSearchModeNotifier = ValueNotifier(false); listenContactsStartList(); listenSearchContactAndRecentChat(); super.initState(); - _fetchCurrentTomContacts(limit: limitRecentContacts); - _getContactAndRecentChat(); + SchedulerBinding.instance.addPostFrameCallback((_) async { + if (mounted) { + searchContactAndRecentChatController?.init(); + _fetchCurrentTomContacts(limit: limitPrefetchedRecentContacts); + _getContactAndRecentChat(); + } + }); } @override diff --git a/lib/pages/search/search_controller.dart b/lib/pages/search/search_controller.dart index ad63880795..5ef570d121 100644 --- a/lib/pages/search/search_controller.dart +++ b/lib/pages/search/search_controller.dart @@ -6,6 +6,9 @@ import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/search/search_interactor_state.dart'; import 'package:fluffychat/domain/usecase/search/search_interactor.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; @@ -23,16 +26,10 @@ class SearchContactAndRecentChatController { void Function(String)? onSearchKeywordChanged; late final isSearchModeNotifier = ValueNotifier(false); - - void init() { _initializeDebouncer(); textEditingController.addListener(() { - if (textEditingController.text.isNotEmpty) { - isSearchModeNotifier.value = true; - } else { - isSearchModeNotifier.value = false; - } + isSearchModeNotifier.value = textEditingController.text.isNotEmpty; onSearchBarChanged(textEditingController.text); }); } @@ -49,20 +46,26 @@ class SearchContactAndRecentChatController { if (onSearchKeywordChanged != null) { onSearchKeywordChanged!(textEditingController.text); } - fetchLookupContacts(); + final enableSearch = searchKeyword.isNotEmpty && searchKeyword != ''; + fetchLookupContacts( + enableSearch: enableSearch, + limitRecentChats: !enableSearch ? 3 : null + ); }); } void fetchLookupContacts({ int? limitContacts, int? limitRecentChats, + bool enableSearch = false, }) { _searchContactsAndRecentChatInteractor.execute( keyword: searchKeyword, - context: context, + matrixLocalizations: MatrixLocals(L10n.of(context)!), + rooms: Matrix.of(context).client.rooms, limitContacts: limitContacts, limitRecentChats: limitRecentChats, - enableSearch: true, + enableSearch: enableSearch, ).listen((event) { getContactAndRecentChatStream.add(event); }); diff --git a/lib/pages/search/search_view.dart b/lib/pages/search/search_view.dart index 2c1c35690b..7037b01386 100644 --- a/lib/pages/search/search_view.dart +++ b/lib/pages/search/search_view.dart @@ -1,3 +1,6 @@ +import 'package:dartz/dartz.dart' hide State; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/domain/app_state/search/search_interactor_state.dart'; import 'package:fluffychat/pages/search/recent_contacts_banner_widget.dart'; import 'package:fluffychat/pages/search/recent_item_widget.dart'; import 'package:fluffychat/pages/search/search.dart'; @@ -6,6 +9,7 @@ import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; +import 'package:matrix/matrix.dart'; import 'package:vrouter/vrouter.dart'; class SearchView extends StatefulWidget { @@ -70,26 +74,44 @@ class _SearchViewState extends State { } Widget _recentChatsWidget() { - final rooms = widget.searchController.filteredRoomsForAll; - if (rooms.isEmpty) { - const SizedBox(); - } else { - return ListView.builder( - padding: SearchViewStyle.paddingRecentChats, - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - controller: widget.searchController.recentChatsController, - itemCount: rooms.length, - itemBuilder: (BuildContext context, int i) { - return RecentItemWidget( - rooms[i], - key: Key('chat_recent_${rooms[i].id}'), - onTap: () {}, + return StreamBuilder>( + stream: widget.searchController.contactsAndRecentChatStreamController.stream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator(strokeWidth: 2)); + } + + if (snapshot.hasError || snapshot.data!.isLeft()) { + return const SizedBox(); + } + + final contactsList = widget.searchController.getContactsAndRecentChatStream(snapshot.data!); + + if (widget.searchController.isSearchMode) { + contactsList.sort((pre, cur) => widget.searchController.comparePresentationSearch(pre, cur)); + } + + Logs().d("SearchView:_recentChatsWidget(): --- contactsListSorted $contactsList"); + + return ListView.builder( + padding: SearchViewStyle.paddingRecentChats, + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + controller: widget.searchController.recentChatsController, + itemCount: contactsList.length, + itemBuilder: (BuildContext context, int i) { + return RecentItemWidget( + searchController: widget.searchController, + presentationSearch: contactsList[i], + key: Key('chat_recent_${contactsList[i].matrixId}'), + onTap: () { + widget.searchController.goToChatScreen(contactsList[i]); + }, + ); + }, ); - }, - ); - } - return const SizedBox(); + } + ); } @@ -114,10 +136,9 @@ class _SearchViewState extends State { const SizedBox(width: 4.0), Expanded( child: TextField( - // controller: controller.searchChatController, + controller: widget.searchController.searchContactAndRecentChatController?.textEditingController, textInputAction: TextInputAction.search, - onChanged: (value) {}, - enabled: false, + enabled: true, decoration: InputDecoration( filled: true, contentPadding: SearchViewStyle.contentPaddingAppBar, diff --git a/lib/presentation/extensions/room_extension.dart b/lib/presentation/extensions/room_extension.dart index 20cf6ec6b2..19e020ace8 100644 --- a/lib/presentation/extensions/room_extension.dart +++ b/lib/presentation/extensions/room_extension.dart @@ -5,10 +5,7 @@ import 'package:dartz/dartz.dart' hide id; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/presentation/extensions/asset_entity_extension.dart'; import 'package:fluffychat/presentation/model/presentation_search.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; -import 'package:flutter/cupertino.dart'; import 'package:matrix/matrix.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/src/utils/file_send_request_credentials.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -300,13 +297,13 @@ extension SendImage on Room { ].contains(lastEvent?.type); } - PresentationSearch toPresentationSearch(BuildContext context) { + PresentationSearch toPresentationSearch(MatrixLocalizations matrixLocalizations) { return PresentationSearch( - displayName: getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), + displayName: getLocalizedDisplayname(matrixLocalizations), roomSummary: summary, directChatMatrixID: directChatMatrixID, matrixId: id, - searchTypeEnum: SearchTypeEnum.recentChat + searchElementTypeEnum: SearchElementTypeEnum.recentChat ); } } \ No newline at end of file diff --git a/lib/presentation/extensions/room_list_extension.dart b/lib/presentation/extensions/room_list_extension.dart index 337314284b..b46d68f999 100644 --- a/lib/presentation/extensions/room_list_extension.dart +++ b/lib/presentation/extensions/room_list_extension.dart @@ -1,10 +1,9 @@ import 'package:fluffychat/presentation/extensions/room_extension.dart'; import 'package:fluffychat/presentation/model/presentation_search.dart'; -import 'package:flutter/cupertino.dart'; import 'package:matrix/matrix.dart'; extension RoomListExtension on List { - List toPresentationSearchList(BuildContext context) { - return map((room) => room.toPresentationSearch(context)).toList(); + List toPresentationSearchList(MatrixLocalizations matrixLocalizations) { + return map((room) => room.toPresentationSearch(matrixLocalizations)).toList(); } } \ No newline at end of file diff --git a/lib/presentation/extensions/room_summary_extension.dart b/lib/presentation/extensions/room_summary_extension.dart new file mode 100644 index 0000000000..7aa026c210 --- /dev/null +++ b/lib/presentation/extensions/room_summary_extension.dart @@ -0,0 +1,6 @@ + +import 'package:matrix/matrix.dart'; + +extension RoomSummaryExtension on RoomSummary { + int get actualMembersCount => (mInvitedMemberCount ?? 0) + (mJoinedMemberCount ?? 0); +} \ No newline at end of file diff --git a/lib/presentation/mixin/load_more_search_mixin.dart b/lib/presentation/mixin/load_more_search_mixin.dart new file mode 100644 index 0000000000..fec84661b1 --- /dev/null +++ b/lib/presentation/mixin/load_more_search_mixin.dart @@ -0,0 +1,30 @@ +import 'package:fluffychat/pages/new_private_chat/fetch_contacts_controller.dart'; +import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:flutter/material.dart'; + +mixin LoadMoreSearchMixin { + final scrollController = ScrollController(); + + final haveMoreCountactsNotifier = ValueNotifier(true); + + final lastContactIndexNotifier = ValueNotifier(0); + + List oldContactList = []; + List oldContactAndRecentChatList = []; + + void listenForScrollChanged({required FetchContactsController fetchContactsController}) { + scrollController.addListener(() {}); + } + + void updateLastContactIndex(int value) { + lastContactIndexNotifier.value = value; + } + + void updateLastContactAndRecentChatIndex(int value) { + lastContactIndexNotifier.value = value; + } + + bool get isLoadMoreAction { + return scrollController.offset >= scrollController.position.maxScrollExtent; + } +} \ No newline at end of file diff --git a/lib/presentation/model/presentation_search.dart b/lib/presentation/model/presentation_search.dart index 68cd56ccc1..32d91be294 100644 --- a/lib/presentation/model/presentation_search.dart +++ b/lib/presentation/model/presentation_search.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:matrix/matrix.dart'; -enum SearchTypeEnum { +enum SearchElementTypeEnum { contact, recentChat, } @@ -14,7 +14,7 @@ class PresentationSearch extends Equatable { final String? matrixId; - final SearchTypeEnum? searchTypeEnum; + final SearchElementTypeEnum? searchElementTypeEnum; final RoomSummary? roomSummary; @@ -24,19 +24,19 @@ class PresentationSearch extends Equatable { this.email, this.displayName, this.matrixId, - this.searchTypeEnum, + this.searchElementTypeEnum, this.roomSummary, this.directChatMatrixID }); - bool get isContact => searchTypeEnum == SearchTypeEnum.contact; + bool get isContact => searchElementTypeEnum == SearchElementTypeEnum.contact; @override List get props => [ email, displayName, matrixId, - searchTypeEnum, + searchElementTypeEnum, roomSummary, directChatMatrixID ]; diff --git a/lib/widgets/vwidget_with_dependencies.dart b/lib/widgets/vwidget_with_dependencies.dart new file mode 100644 index 0000000000..f4c5ce7df6 --- /dev/null +++ b/lib/widgets/vwidget_with_dependencies.dart @@ -0,0 +1,153 @@ +import 'package:fluffychat/di/abstract_di.dart'; +import 'package:fluffychat/di/base_di.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; +import 'package:vrouter/vrouter.dart'; + + +class VWidgetWithDependencies extends VGuard { + + final String? path; + + /// A name for the route which will allow you to easily navigate to it + /// using [VRouter.of(context).pushNamed] + /// + /// Note that [name] should be unique w.r.t every [VRouteElement] + final String? name; + + /// Alternative paths that will be matched to this route + /// + /// Note that path is match first, then every aliases in order + final List aliases; + + /// The widget which will be displayed for this [VRouteElement] + final Widget widget; + + /// A LocalKey that will be given to the page which contains the given [widget] + /// + /// This key mostly controls the page animation. If a page remains the same but the key is changes, + /// the page gets animated + /// The key is by default the value of the current [path] (or [aliases]) with + /// the path parameters replaced + /// + /// Do provide a constant [key] if you don't want this page to animate even if [path] or + /// [aliases] path parameters change + final LocalKey? key; + + /// The duration of [VWidgetBase.buildTransition] + final Duration? transitionDuration; + + /// The reverse duration of [VWidgetBase.buildTransition] + final Duration? reverseTransitionDuration; + + /// Create a custom transition effect when coming to and + /// going to this route + /// This has the priority over [VRouter.buildTransition] + /// + /// Also see: + /// * [VRouter.buildTransition] for default transitions for all routes + final Widget Function(Animation animation, + Animation secondaryAnimation, Widget child)? buildTransition; + + /// Whether this page route is a full-screen dialog. + /// + /// In Material and Cupertino, being fullscreen has the effects of making the app bars + /// have a close button instead of a back button. On iOS, dialogs transitions animate + /// differently and are also not closeable with the back swipe gesture. + final bool fullscreenDialog; + + final List dies; + + final OnFinishedBind? onFinishedBind; + + @override + Future beforeEnter(VRedirector vRedirector) async { + super.beforeEnter(vRedirector); + Logs().d('VWidgetWithDependencies::beforeEnter()'); + if (beforeDI != null) { + beforeDI!.call(vRedirector); + } + for (final di in dies) { + Logs().d('VWidgetWithDependencies::di $di'); + di.bind(onFinishedBind: onFinishedBind); + } + } + + final Future Function(VRedirector vRedirector)? beforeDI; + + @override + Future beforeUpdate(VRedirector vRedirector) => + _beforeUpdate(vRedirector); + final Future Function(VRedirector vRedirector) _beforeUpdate; + + @override + Future beforeLeave(VRedirector vRedirector, + void Function(Map state) saveHistoryState) async { + super.beforeLeave(vRedirector, saveHistoryState); + await Future.forEach(dies, (di) async { + await di.unbind(); + }); + } + + @override + void afterEnter(BuildContext context, String? from, String to) => + _afterEnter(context, from, to); + final void Function(BuildContext context, String? from, String to) + _afterEnter; + + @override + void afterUpdate(BuildContext context, String? from, String to) => + _afterUpdate(context, from, to); + final void Function(BuildContext context, String? from, String to) + _afterUpdate; + + + VWidgetWithDependencies({ + required this.path, + required this.widget, + this.beforeDI, + required this.dies, + this.onFinishedBind, + super.beforeEnter, + super.beforeUpdate, + super.afterEnter, + super.afterUpdate, + super.stackedRoutes = const [], + this.key, + this.name, + this.aliases = const [], + this.transitionDuration, + this.reverseTransitionDuration, + this.buildTransition, + this.fullscreenDialog = false, + }): _beforeUpdate = beforeUpdate, + _afterEnter = afterEnter, + _afterUpdate = afterUpdate; + + @override + List buildRoutes() => [ + VPath( + path: path, + aliases: aliases, + mustMatchStackedRoute: mustMatchStackedRoute, + stackedRoutes: [ + VWidgetBase( + widget: widget, + key: key, + name: name, + stackedRoutes: stackedRoutes, + buildTransition: buildTransition, + transitionDuration: transitionDuration, + reverseTransitionDuration: reverseTransitionDuration, + fullscreenDialog: fullscreenDialog, + ), + ], + ), + ]; + + /// A boolean to indicate whether this can be a valid [VRouteElement] of the [VRoute] if no + /// [VRouteElement] in its [stackedRoute] is matched + /// + /// This is mainly useful for [VRouteElement]s which are NOT [VRouteElementWithPage] + bool get mustMatchStackedRoute => false; +} \ No newline at end of file From dcbd5386e29fefa1946535f07d3e8ac5f719a860 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 17 Jul 2023 14:14:32 +0700 Subject: [PATCH 6/6] TW-136: Update recent contact list --- lib/di/search/search_di.dart | 3 - .../search/search_interactor_state.dart | 8 +- .../extensions/contact/contact_extension.dart | 19 ++++ .../presentation_contact_list_extension.dart | 9 -- .../extensions/search/search_extension.dart | 15 +++ .../search/search_list_extension.dart | 9 ++ lib/domain/model/room/room_extension.dart | 40 +++++++ .../model/room/room_list_extension.dart | 9 ++ lib/domain/model/search/search_model.dart | 41 +++++++ .../usecase/search/search_interactor.dart | 40 ++++--- lib/domain/usecase/send_file_interactor.dart | 2 +- lib/domain/usecase/send_image_interactor.dart | 2 +- .../usecase/send_images_interactor.dart | 2 +- .../comparable_presentation_search_mixin.dart | 3 +- lib/pages/chat_list/chat_list.dart | 2 +- lib/pages/new_group/new_group_chat_info.dart | 2 - .../search/recent_contacts_banner_widget.dart | 107 +++++++----------- lib/pages/search/recent_item_widget.dart | 2 +- lib/pages/search/search.dart | 80 +++++++------ lib/pages/search/search_controller.dart | 12 +- .../presentation_contact_extension.dart | 11 +- .../presentation_search_extension.dart | 0 .../extensions/room_list_extension.dart | 9 -- ...tension.dart => send_image_extension.dart} | 14 +-- .../mixin/load_more_search_mixin.dart | 2 +- .../{ => search}/presentation_search.dart | 6 +- lib/widgets/pill.dart | 2 +- 27 files changed, 263 insertions(+), 188 deletions(-) delete mode 100644 lib/domain/model/extensions/contact/presentation_contact_list_extension.dart create mode 100644 lib/domain/model/extensions/search/search_extension.dart create mode 100644 lib/domain/model/extensions/search/search_list_extension.dart create mode 100644 lib/domain/model/room/room_extension.dart create mode 100644 lib/domain/model/room/room_list_extension.dart create mode 100644 lib/domain/model/search/search_model.dart rename lib/{domain/model => presentation}/extensions/contact/presentation_contact_extension.dart (63%) create mode 100644 lib/presentation/extensions/contact/presentation_search_extension.dart delete mode 100644 lib/presentation/extensions/room_list_extension.dart rename lib/presentation/extensions/{room_extension.dart => send_image_extension.dart} (95%) rename lib/presentation/model/{ => search}/presentation_search.dart (91%) diff --git a/lib/di/search/search_di.dart b/lib/di/search/search_di.dart index 8eb1f32331..ac3107113b 100644 --- a/lib/di/search/search_di.dart +++ b/lib/di/search/search_di.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/data/repository/contact/tom_contact_repository_impl.d import 'package:fluffychat/di/base_di.dart'; import 'package:fluffychat/domain/repository/contact_repository.dart'; import 'package:fluffychat/domain/usecase/fetch_contacts_interactor.dart'; -import 'package:fluffychat/domain/usecase/load_more_internal_contacts.dart'; import 'package:fluffychat/domain/usecase/lookup_contacts_interactor.dart'; import 'package:fluffychat/domain/usecase/search/search_interactor.dart'; import 'package:get_it/get_it.dart'; @@ -30,8 +29,6 @@ class SearchDI extends BaseDI { get.registerSingleton(FetchContactsInteractor()); - get.registerSingleton(LoadMoreInternalContacts()); - get.registerSingleton(SearchContactsAndRecentChatInteractor()); Logs().d('SearchDI::setUp() - done'); diff --git a/lib/domain/app_state/search/search_interactor_state.dart b/lib/domain/app_state/search/search_interactor_state.dart index 82301a838f..904abcba8d 100644 --- a/lib/domain/app_state/search/search_interactor_state.dart +++ b/lib/domain/app_state/search/search_interactor_state.dart @@ -1,14 +1,14 @@ import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/domain/model/search/search_model.dart'; class GetContactAndRecentChatSuccess extends Success { - final List presentationSearches; + final List search; - const GetContactAndRecentChatSuccess({required this.presentationSearches}); + const GetContactAndRecentChatSuccess({required this.search}); @override - List get props => [presentationSearches]; + List get props => [search]; } class GetContactAndRecentChatFailed extends Failure { diff --git a/lib/domain/model/extensions/contact/contact_extension.dart b/lib/domain/model/extensions/contact/contact_extension.dart index c63a735204..82c1dd3753 100644 --- a/lib/domain/model/extensions/contact/contact_extension.dart +++ b/lib/domain/model/extensions/contact/contact_extension.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/domain/model/contact/contact.dart'; +import 'package:fluffychat/domain/model/search/search_model.dart'; import 'package:fluffychat/presentation/model/presentation_contact.dart'; extension ContactExtension on Contact { @@ -21,4 +22,22 @@ extension ContactExtension on Contact { return listContacts; } + + Set toSearch() { + final listContacts = emails?.map((email) => SearchModel( + email: email, + displayName: displayName, + matrixId: matrixId, + )).toSet() ?? {}; + + if (emails == null || emails!.isEmpty) { + listContacts.add(SearchModel( + email: null, + displayName: displayName, + matrixId: matrixId, + )); + } + + return listContacts; + } } \ No newline at end of file diff --git a/lib/domain/model/extensions/contact/presentation_contact_list_extension.dart b/lib/domain/model/extensions/contact/presentation_contact_list_extension.dart deleted file mode 100644 index 1f52805d39..0000000000 --- a/lib/domain/model/extensions/contact/presentation_contact_list_extension.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:fluffychat/domain/model/extensions/contact/presentation_contact_extension.dart'; -import 'package:fluffychat/presentation/model/presentation_contact.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; - -extension PresentaionContactListExtension on List { - List toPresentationSearchList() { - return map((contact) => contact.toPresentationSearch()).toList(); - } -} \ No newline at end of file diff --git a/lib/domain/model/extensions/search/search_extension.dart b/lib/domain/model/extensions/search/search_extension.dart new file mode 100644 index 0000000000..c10678f47c --- /dev/null +++ b/lib/domain/model/extensions/search/search_extension.dart @@ -0,0 +1,15 @@ +import 'package:fluffychat/domain/model/search/search_model.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; + +extension SearchExtension on SearchModel { + PresentationSearch toPresentationSearch() { + return PresentationSearch( + email: email, + displayName: displayName, + matrixId: matrixId, + searchElementTypeEnum: searchElementTypeEnum, + roomSummary: roomSummary, + directChatMatrixID: directChatMatrixID + ); + } +} \ No newline at end of file diff --git a/lib/domain/model/extensions/search/search_list_extension.dart b/lib/domain/model/extensions/search/search_list_extension.dart new file mode 100644 index 0000000000..2274af8f28 --- /dev/null +++ b/lib/domain/model/extensions/search/search_list_extension.dart @@ -0,0 +1,9 @@ +import 'package:fluffychat/domain/model/extensions/search/search_extension.dart'; +import 'package:fluffychat/domain/model/search/search_model.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; + +extension SearchListExtenstion on List { + List toPresentationSearch() { + return map((search) => search.toPresentationSearch()).toList(); + } +} \ No newline at end of file diff --git a/lib/domain/model/room/room_extension.dart b/lib/domain/model/room/room_extension.dart new file mode 100644 index 0000000000..2b67854432 --- /dev/null +++ b/lib/domain/model/room/room_extension.dart @@ -0,0 +1,40 @@ + +import 'package:fluffychat/domain/model/search/search_model.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; +import 'package:matrix/matrix.dart'; + +extension RoomExtension on Room { + SearchModel toSearch(MatrixLocalizations matrixLocalizations) { + return SearchModel( + displayName: getLocalizedDisplayname(matrixLocalizations), + roomSummary: summary, + directChatMatrixID: directChatMatrixID, + matrixId: id, + searchElementTypeEnum: SearchElementTypeEnum.recentChat + ); + } + + bool isNotSpaceAndStoryRoom() { + return !isSpace && !isStoryRoom; + } + + bool isShowInChatList() { + return _isDirectChatHaveMessage() || _isGroupChat(); + } + + bool _isGroupChat() { + return !isDirectChat; + } + + bool _isDirectChatHaveMessage() { + return isDirectChat && _isLastEventInRoomIsMessage(); + } + + bool _isLastEventInRoomIsMessage() { + return [ + EventTypes.Message, + EventTypes.Sticker, + EventTypes.Encrypted, + ].contains(lastEvent?.type); + } +} \ No newline at end of file diff --git a/lib/domain/model/room/room_list_extension.dart b/lib/domain/model/room/room_list_extension.dart new file mode 100644 index 0000000000..a545e75e19 --- /dev/null +++ b/lib/domain/model/room/room_list_extension.dart @@ -0,0 +1,9 @@ +import 'package:fluffychat/domain/model/room/room_extension.dart'; +import 'package:fluffychat/domain/model/search/search_model.dart'; +import 'package:matrix/matrix.dart'; + +extension RoomListExtension on List { + List toSearchList(MatrixLocalizations matrixLocalizations) { + return map((room) => room.toSearch(matrixLocalizations)).toList(); + } +} \ No newline at end of file diff --git a/lib/domain/model/search/search_model.dart b/lib/domain/model/search/search_model.dart new file mode 100644 index 0000000000..ceaba3d2b8 --- /dev/null +++ b/lib/domain/model/search/search_model.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; +import 'package:matrix/matrix.dart'; + +enum SearchElementTypeEnum { + contact, + recentChat, +} + +class SearchModel extends Equatable { + + final String? email; + + final String? displayName; + + final String? matrixId; + + final SearchElementTypeEnum? searchElementTypeEnum; + + final RoomSummary? roomSummary; + + final String? directChatMatrixID; + + const SearchModel({ + this.email, + this.displayName, + this.matrixId, + this.searchElementTypeEnum, + this.roomSummary, + this.directChatMatrixID + }); + + @override + List get props => [ + email, + displayName, + matrixId, + searchElementTypeEnum, + roomSummary, + directChatMatrixID + ]; +} \ No newline at end of file diff --git a/lib/domain/usecase/search/search_interactor.dart b/lib/domain/usecase/search/search_interactor.dart index 9ae04b2d96..5a6ccecf50 100644 --- a/lib/domain/usecase/search/search_interactor.dart +++ b/lib/domain/usecase/search/search_interactor.dart @@ -4,12 +4,10 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/search/search_interactor_state.dart'; import 'package:fluffychat/domain/model/contact/contact_query.dart'; import 'package:fluffychat/domain/model/extensions/contact/contact_extension.dart'; -import 'package:fluffychat/domain/model/extensions/contact/presentation_contact_list_extension.dart'; +import 'package:fluffychat/domain/model/room/room_extension.dart'; +import 'package:fluffychat/domain/model/room/room_list_extension.dart'; +import 'package:fluffychat/domain/model/search/search_model.dart'; import 'package:fluffychat/domain/repository/contact_repository.dart'; -import 'package:fluffychat/presentation/extensions/room_extension.dart'; -import 'package:fluffychat/presentation/extensions/room_list_extension.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; import 'package:matrix/matrix.dart'; class SearchContactsAndRecentChatInteractor { @@ -23,34 +21,41 @@ class SearchContactsAndRecentChatInteractor { required String keyword, int? limitContacts, int? limitRecentChats, - bool enableSearch = false }) async* { try { - if (enableSearch) { + if (keyword.isNotEmpty) { final recentChat = await _searchRecentChat(rooms: rooms, matrixLocalizations: matrixLocalizations, keyword: keyword); final contacts = await contactRepository.searchContact(query: ContactQuery(keyword: keyword), limit: limitContacts); final presentationSearches = _comparePresentationSearches( - recentChat.toPresentationSearchList(matrixLocalizations), - contacts.expand((contact) => contact.toPresentationContacts()) + recentChat.toSearchList(matrixLocalizations), + contacts.expand((contact) => contact.toSearch()) + .where((contact) => _compareDisplayNameWithKeyword(contact, keyword)) .toList() - .toPresentationSearchList() ); - yield Right(GetContactAndRecentChatSuccess(presentationSearches: presentationSearches)); + yield Right(GetContactAndRecentChatSuccess(search: presentationSearches)); } else { final recentChat = await _getRecentChat(rooms: rooms, limitRecentChats: limitRecentChats); - yield Right(GetContactAndRecentChatSuccess(presentationSearches: recentChat.toPresentationSearchList(matrixLocalizations))); + yield Right(GetContactAndRecentChatSuccess(search: recentChat.toSearchList(matrixLocalizations))); } } catch (e) { yield Left(GetContactAndRecentChatFailed(exception: e)); } } + bool _compareDisplayNameWithKeyword(SearchModel search, String keyword) { + if (search.displayName != null) { + return search.displayName!.toLowerCase().contains(keyword.toLowerCase()); + } else { + return false; + } + } + Future> _getRecentChat({ required List rooms, int? limitRecentChats }) async { - return rooms.where((room) => !room.isSpace && !room.isStoryRoom) + return rooms.where((room) => room.isNotSpaceAndStoryRoom()) .where((room) => room.isShowInChatList()) .take(limitRecentChats ?? 0) .toList(); @@ -61,16 +66,15 @@ class SearchContactsAndRecentChatInteractor { required MatrixLocalizations matrixLocalizations, required String keyword, }) async { - return rooms.where((room) => !room.isSpace && !room.isStoryRoom) + return rooms.where((room) => room.isNotSpaceAndStoryRoom()) .where((room) => room.getLocalizedDisplayname(matrixLocalizations) - .toLowerCase() - .contains(keyword.toLowerCase()) - && room.isShowInChatList() + .toLowerCase() + .contains(keyword.toLowerCase()) ).toList(); } - List _comparePresentationSearches(List recentChat, List contacts) { + List _comparePresentationSearches(List recentChat, List contacts) { final isDuplicateElement = contacts.where((contact) { return recentChat.any((recentChat) => recentChat.directChatMatrixID == contact.directChatMatrixID); }).toList(); diff --git a/lib/domain/usecase/send_file_interactor.dart b/lib/domain/usecase/send_file_interactor.dart index 5b40cfda32..41f0d195de 100644 --- a/lib/domain/usecase/send_file_interactor.dart +++ b/lib/domain/usecase/send_file_interactor.dart @@ -1,6 +1,6 @@ import 'package:file_picker/file_picker.dart'; -import 'package:fluffychat/presentation/extensions/room_extension.dart'; +import 'package:fluffychat/presentation/extensions/send_image_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart'; import 'package:matrix/matrix.dart'; diff --git a/lib/domain/usecase/send_image_interactor.dart b/lib/domain/usecase/send_image_interactor.dart index e6d738865e..2b195a22c4 100644 --- a/lib/domain/usecase/send_image_interactor.dart +++ b/lib/domain/usecase/send_image_interactor.dart @@ -1,6 +1,6 @@ import 'package:fluffychat/presentation/extensions/asset_entity_extension.dart'; -import 'package:fluffychat/presentation/extensions/room_extension.dart'; +import 'package:fluffychat/presentation/extensions/send_image_extension.dart'; import 'package:matrix/matrix.dart'; import 'package:photo_manager/photo_manager.dart'; diff --git a/lib/domain/usecase/send_images_interactor.dart b/lib/domain/usecase/send_images_interactor.dart index 45acc582c2..7f289b7b1c 100644 --- a/lib/domain/usecase/send_images_interactor.dart +++ b/lib/domain/usecase/send_images_interactor.dart @@ -1,4 +1,4 @@ -import 'package:fluffychat/presentation/extensions/room_extension.dart'; +import 'package:fluffychat/presentation/extensions/send_image_extension.dart'; import 'package:matrix/matrix.dart'; import 'package:photo_manager/photo_manager.dart'; diff --git a/lib/mixin/comparable_presentation_search_mixin.dart b/lib/mixin/comparable_presentation_search_mixin.dart index 5a58813b9f..b3b489953f 100644 --- a/lib/mixin/comparable_presentation_search_mixin.dart +++ b/lib/mixin/comparable_presentation_search_mixin.dart @@ -1,4 +1,5 @@ -import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/domain/model/search/search_model.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; class ComparablePresentationSearchMixin { int comparePresentationSearch(PresentationSearch searchResultOne, PresentationSearch searchResultTwo) { diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 6ca59cad25..e2fe0058cc 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -10,7 +10,7 @@ import 'package:fluffychat/domain/usecases/get_recovery_words_interactor.dart'; import 'package:fluffychat/pages/bootstrap/tom_bootstrap_dialog.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/pages/settings_security/settings_security.dart'; -import 'package:fluffychat/presentation/extensions/room_extension.dart'; +import 'package:fluffychat/presentation/extensions/send_image_extension.dart'; import 'package:fluffychat/utils/famedlysdk_store.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; diff --git a/lib/pages/new_group/new_group_chat_info.dart b/lib/pages/new_group/new_group_chat_info.dart index e01a20ab6f..d6e2808b4e 100644 --- a/lib/pages/new_group/new_group_chat_info.dart +++ b/lib/pages/new_group/new_group_chat_info.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:fluffychat/presentation/model/presentation_contact.dart'; import 'package:fluffychat/pages/new_group/new_group.dart'; import 'package:fluffychat/pages/new_group/new_group_info_controller.dart'; diff --git a/lib/pages/search/recent_contacts_banner_widget.dart b/lib/pages/search/recent_contacts_banner_widget.dart index 37d701b6d1..98023b08e9 100644 --- a/lib/pages/search/recent_contacts_banner_widget.dart +++ b/lib/pages/search/recent_contacts_banner_widget.dart @@ -1,10 +1,5 @@ -import 'package:collection/collection.dart'; -import 'package:dartz/dartz.dart'; -import 'package:fluffychat/app_state/failure.dart'; -import 'package:fluffychat/domain/app_state/contact/get_contacts_success.dart'; import 'package:fluffychat/pages/search/recent_contacts_banner_widget_style.dart'; import 'package:fluffychat/pages/search/search.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; import 'package:fluffychat/utils/display_name_widget.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:flutter/material.dart'; @@ -16,75 +11,59 @@ class RecentContactsBannerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamBuilder>( - stream: searchController.contactsStreamController.stream, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator(strokeWidth: 2)); - } - - if (snapshot.hasError || snapshot.data!.isLeft()) { - return const SizedBox(); - } - - final contactsList = searchController.getContactsFromFetchStream(snapshot.data!); - - final contactsListSorted = contactsList.sorted((pre, next) => searchController.comparePresentationSearch(pre, next)); - - if (contactsListSorted.isEmpty) { - return const SizedBox(); - } - - return ListView.builder( - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - scrollDirection: Axis.horizontal, - itemCount: contactsListSorted.length, - itemBuilder: (context, index) { - return ChatRecentContactItemWidget( - presentationSearch: contactsListSorted[index], - searchController: searchController, - ); - }, - ); - }, - ); + final contactsList = searchController.getContactsFromRecentChat(); + if (contactsList.isEmpty) { + return const SizedBox.shrink(); + } else { + return ListView.builder( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + scrollDirection: Axis.horizontal, + itemCount: contactsList.length, + itemBuilder: (context, index) { + return ChatRecentContactItemWidget( + user: contactsList[index], + searchController: searchController, + ); + }, + ); + } } } class ChatRecentContactItemWidget extends StatelessWidget { final SearchController searchController; - final PresentationSearch presentationSearch; - const ChatRecentContactItemWidget({super.key, required this.presentationSearch, required this.searchController}); + final User user; + const ChatRecentContactItemWidget({super.key, required this.user, required this.searchController}); @override Widget build(BuildContext context) { - return FutureBuilder( - future: searchController.getProfile(context, presentationSearch), - builder: (context, snapshot) { - return SizedBox( - width: RecentContactsBannerWidgetStyle.chatRecentContactItemWidth, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: RecentContactsBannerWidgetStyle.avatarWidthSize, - child: Avatar( - mxContent: snapshot.data?.avatarUrl, - name: presentationSearch.displayName, - ), + return InkWell( + onTap: () { + searchController.goToChatScreenFormRecentChat(user); + }, + child: SizedBox( + width: RecentContactsBannerWidgetStyle.chatRecentContactItemWidth, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: RecentContactsBannerWidgetStyle.avatarWidthSize, + height: RecentContactsBannerWidgetStyle.avatarWidthSize, + child: Avatar( + mxContent: user.avatarUrl, + name: user.displayName ?? "", ), - Padding( - padding: RecentContactsBannerWidgetStyle.chatRecentContactItemPadding, - child: BuildDisplayName( - profileDisplayName: snapshot.data?.displayname, - contactDisplayName: presentationSearch.displayName - ), + ), + Padding( + padding: RecentContactsBannerWidgetStyle.chatRecentContactItemPadding, + child: BuildDisplayName( + profileDisplayName: user.displayName ?? "", ), - ], - ), - ); - }, + ), + ], + ), + ), ); } } diff --git a/lib/pages/search/recent_item_widget.dart b/lib/pages/search/recent_item_widget.dart index 0f1174e3c4..2c456f61ec 100644 --- a/lib/pages/search/recent_item_widget.dart +++ b/lib/pages/search/recent_item_widget.dart @@ -1,7 +1,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/search/recent_item_widget_style.dart'; import 'package:fluffychat/pages/search/search.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index fa6aaaa610..26be5e495b 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -3,17 +3,14 @@ import 'dart:async'; import 'package:dartz/dartz.dart' hide State; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; -import 'package:fluffychat/domain/app_state/contact/get_contacts_success.dart'; import 'package:fluffychat/domain/app_state/search/search_interactor_state.dart'; -import 'package:fluffychat/domain/model/extensions/contact/contact_extension.dart'; -import 'package:fluffychat/domain/model/extensions/contact/presentation_contact_list_extension.dart'; -import 'package:fluffychat/domain/usecase/fetch_contacts_interactor.dart'; +import 'package:fluffychat/domain/model/extensions/search/search_list_extension.dart'; import 'package:fluffychat/domain/usecase/search/search_interactor.dart'; import 'package:fluffychat/mixin/comparable_presentation_search_mixin.dart'; import 'package:fluffychat/pages/search/search_controller.dart'; import 'package:fluffychat/pages/search/search_view.dart'; import 'package:fluffychat/presentation/mixin/load_more_search_mixin.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -45,10 +42,7 @@ class SearchController extends State with ComparablePresentationSearchM SearchContactAndRecentChatController? searchContactAndRecentChatController; final AutoScrollController recentChatsController = AutoScrollController(); final ScrollController customScrollController = ScrollController(); - final _fetchContactsInteractor = getIt.get(); final _searchContactsAndRecentChatInteractor = getIt.get(); - final getContactStream = StreamController>(); - final contactsStreamController = BehaviorSubject>(); final getContactAndRecentChatStream = StreamController>(); final contactsAndRecentChatStreamController = BehaviorSubject>(); @@ -68,26 +62,12 @@ class SearchController extends State with ComparablePresentationSearchM void listenContactsStartList() { - getContactStream.stream.listen((event) { - Logs().d('SearchController::getContactStream() - event: $event'); - contactsStreamController.add(event); - }); - getContactAndRecentChatStream.stream.listen((event) { Logs().d('SearchController::getContactAndRecentChatStream() - event: $event'); contactsAndRecentChatStreamController.add(event); }); } - void _fetchCurrentTomContacts({ - int? limit, - int? offset, - }) { - _fetchContactsInteractor.execute(limit: limit, offset: offset).listen((event) { - getContactStream.add(event); - }); - } - void _getContactAndRecentChat() { _searchContactsAndRecentChatInteractor.execute( rooms: Matrix.of(context).client.rooms, @@ -100,24 +80,44 @@ class SearchController extends State with ComparablePresentationSearchM }); } - List getContactsFromFetchStream(Either event) { - return event.fold>( - (failure) => [], - (success) { - final currentContacts = success.contacts.expand((contact) => contact.toPresentationContacts()); - updateLastContactIndex(oldContactList.length); - oldContactList = currentContacts.toList().toPresentationSearchList(); - lastContactIndexNotifier.value = oldContactList.length; - return oldContactList; - }, - ).toList(); + List getContactsFromRecentChat() { + final recentRooms = Matrix.of(context).client.rooms; + final List recentChatListPresentationSearch = []; + + for (final room in recentRooms) { + final users = room.getParticipants() + .where((user) => user.membership.isInvite == true && user.displayName != null) + .toSet() + .toList(); + + for (final user in users) { + final isDuplicateUser = recentChatListPresentationSearch + .any((existingUser) => existingUser.displayName == user.displayName); + + if (!isDuplicateUser) { + recentChatListPresentationSearch.add(user); + } + + if (recentChatListPresentationSearch.length == limitPrefetchedRecentContacts) { + break; // Stop getting participants after 7 or more have been added + } + } + + if (recentChatListPresentationSearch.length == limitPrefetchedRecentContacts) { + break; // Stop getting participants after 7 or more have been added + } + } + + Logs().d('SearchController::getContactsFromRecentChat() - event: $recentChatListPresentationSearch'); + + return recentChatListPresentationSearch; } List getContactsAndRecentChatStream(Either event) { return event.fold>( (failure) => [], (success) { - final contactsAndRecentChat = success.presentationSearches; + final contactsAndRecentChat = success.search.toPresentationSearch(); updateLastContactAndRecentChatIndex(contactsAndRecentChat.length); return contactsAndRecentChat; }, @@ -147,7 +147,16 @@ class SearchController extends State with ComparablePresentationSearchM VRouter.of(context).toSegments(['rooms', presentationSearch.matrixId!]); } } + } + void goToChatScreenFormRecentChat(User user) async { + Logs().d('SearchController::getContactAndRecentChatStream() - event: $user'); + final roomIdResult = await showFutureLoadingDialog( + context: context, + future: () => user.startDirectChat(), + ); + if (roomIdResult.error != null) return; + VRouter.of(context).toSegments(['rooms', roomIdResult.result!]); } @override @@ -160,7 +169,6 @@ class SearchController extends State with ComparablePresentationSearchM SchedulerBinding.instance.addPostFrameCallback((_) async { if (mounted) { searchContactAndRecentChatController?.init(); - _fetchCurrentTomContacts(limit: limitPrefetchedRecentContacts); _getContactAndRecentChat(); } }); @@ -170,8 +178,6 @@ class SearchController extends State with ComparablePresentationSearchM void dispose() { recentChatsController.dispose(); customScrollController.dispose(); - getContactStream.close(); - contactsStreamController.close(); getContactAndRecentChatStream.close(); contactsAndRecentChatStreamController.close(); searchContactAndRecentChatController?.dispose(); diff --git a/lib/pages/search/search_controller.dart b/lib/pages/search/search_controller.dart index 5ef570d121..b50cfd8cac 100644 --- a/lib/pages/search/search_controller.dart +++ b/lib/pages/search/search_controller.dart @@ -15,16 +15,15 @@ import 'package:matrix/matrix.dart'; class SearchContactAndRecentChatController { final BuildContext context; SearchContactAndRecentChatController(this.context); - + static const int limitPrefetchedRecentChats = 3; static const debouncerIntervalInMilliseconds = 300; - String searchKeyword = ""; - final SearchContactsAndRecentChatInteractor _searchContactsAndRecentChatInteractor = getIt.get(); - Debouncer? _debouncer; final TextEditingController textEditingController = TextEditingController(); StreamController> getContactAndRecentChatStream = StreamController(); void Function(String)? onSearchKeywordChanged; - late final isSearchModeNotifier = ValueNotifier(false); + Debouncer? _debouncer; + String searchKeyword = ""; + final isSearchModeNotifier = ValueNotifier(false); void init() { _initializeDebouncer(); @@ -49,7 +48,7 @@ class SearchContactAndRecentChatController { final enableSearch = searchKeyword.isNotEmpty && searchKeyword != ''; fetchLookupContacts( enableSearch: enableSearch, - limitRecentChats: !enableSearch ? 3 : null + limitRecentChats: !enableSearch ? limitPrefetchedRecentChats : null ); }); } @@ -65,7 +64,6 @@ class SearchContactAndRecentChatController { rooms: Matrix.of(context).client.rooms, limitContacts: limitContacts, limitRecentChats: limitRecentChats, - enableSearch: enableSearch, ).listen((event) { getContactAndRecentChatStream.add(event); }); diff --git a/lib/domain/model/extensions/contact/presentation_contact_extension.dart b/lib/presentation/extensions/contact/presentation_contact_extension.dart similarity index 63% rename from lib/domain/model/extensions/contact/presentation_contact_extension.dart rename to lib/presentation/extensions/contact/presentation_contact_extension.dart index c3bddeba6f..666a57befa 100644 --- a/lib/domain/model/extensions/contact/presentation_contact_extension.dart +++ b/lib/presentation/extensions/contact/presentation_contact_extension.dart @@ -1,5 +1,5 @@ import 'package:fluffychat/presentation/model/presentation_contact.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; extension PresentaionContactExtension on PresentationContact { @@ -22,13 +22,4 @@ extension PresentaionContactExtension on PresentationContact { return false; } - - PresentationSearch toPresentationSearch() { - return PresentationSearch( - email: email, - displayName: displayName, - directChatMatrixID: matrixId, - searchElementTypeEnum: SearchElementTypeEnum.contact, - ); - } } \ No newline at end of file diff --git a/lib/presentation/extensions/contact/presentation_search_extension.dart b/lib/presentation/extensions/contact/presentation_search_extension.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/presentation/extensions/room_list_extension.dart b/lib/presentation/extensions/room_list_extension.dart deleted file mode 100644 index b46d68f999..0000000000 --- a/lib/presentation/extensions/room_list_extension.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:fluffychat/presentation/extensions/room_extension.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; -import 'package:matrix/matrix.dart'; - -extension RoomListExtension on List { - List toPresentationSearchList(MatrixLocalizations matrixLocalizations) { - return map((room) => room.toPresentationSearch(matrixLocalizations)).toList(); - } -} \ No newline at end of file diff --git a/lib/presentation/extensions/room_extension.dart b/lib/presentation/extensions/send_image_extension.dart similarity index 95% rename from lib/presentation/extensions/room_extension.dart rename to lib/presentation/extensions/send_image_extension.dart index 19e020ace8..3eeb2c9020 100644 --- a/lib/presentation/extensions/room_extension.dart +++ b/lib/presentation/extensions/send_image_extension.dart @@ -4,7 +4,7 @@ import 'package:collection/collection.dart'; import 'package:dartz/dartz.dart' hide id; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/presentation/extensions/asset_entity_extension.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/file_send_request_credentials.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -12,7 +12,7 @@ import 'package:photo_manager/photo_manager.dart'; typedef TransactionId = String; typedef FakeImageEvent = SyncUpdate; -extension SendImage on Room { +extension SendImageExtension on Room { static const maxImagesCacheInRoom = 10; @@ -296,14 +296,4 @@ extension SendImage on Room { EventTypes.Encrypted, ].contains(lastEvent?.type); } - - PresentationSearch toPresentationSearch(MatrixLocalizations matrixLocalizations) { - return PresentationSearch( - displayName: getLocalizedDisplayname(matrixLocalizations), - roomSummary: summary, - directChatMatrixID: directChatMatrixID, - matrixId: id, - searchElementTypeEnum: SearchElementTypeEnum.recentChat - ); - } } \ No newline at end of file diff --git a/lib/presentation/mixin/load_more_search_mixin.dart b/lib/presentation/mixin/load_more_search_mixin.dart index fec84661b1..53fda0c8af 100644 --- a/lib/presentation/mixin/load_more_search_mixin.dart +++ b/lib/presentation/mixin/load_more_search_mixin.dart @@ -1,5 +1,5 @@ import 'package:fluffychat/pages/new_private_chat/fetch_contacts_controller.dart'; -import 'package:fluffychat/presentation/model/presentation_search.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; import 'package:flutter/material.dart'; mixin LoadMoreSearchMixin { diff --git a/lib/presentation/model/presentation_search.dart b/lib/presentation/model/search/presentation_search.dart similarity index 91% rename from lib/presentation/model/presentation_search.dart rename to lib/presentation/model/search/presentation_search.dart index 32d91be294..a8605187cf 100644 --- a/lib/presentation/model/presentation_search.dart +++ b/lib/presentation/model/search/presentation_search.dart @@ -1,11 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:fluffychat/domain/model/search/search_model.dart'; import 'package:matrix/matrix.dart'; -enum SearchElementTypeEnum { - contact, - recentChat, -} - class PresentationSearch extends Equatable { final String? email; diff --git a/lib/widgets/pill.dart b/lib/widgets/pill.dart index c7b9a820ab..44c0d027af 100644 --- a/lib/widgets/pill.dart +++ b/lib/widgets/pill.dart @@ -1,5 +1,5 @@ import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/presentation/extensions/room_extension.dart'; +import 'package:fluffychat/presentation/extensions/send_image_extension.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart';