mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Completion Suggestion view for user mentions (#1859)
* created the list but I need to find a way to overlay it * make the list able to have an intrinsic height * best solution so far but does not work with expansion * needs testing * more scalable solution * tests completed * changelog * injecting the media provider * fix tests * pr suggestions * better testing
This commit is contained in:
parent
72a96b6850
commit
8270a868a7
@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 56;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@ -551,6 +551,12 @@
|
|||||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
||||||
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
||||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||||
|
A78643F02ACDBA54005B2BFB /* CompletionSuggestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A78643EF2ACDBA54005B2BFB /* CompletionSuggestionView.swift */; };
|
||||||
|
A78643F22ACDBAEF005B2BFB /* MentionSuggestionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A78643F12ACDBAEF005B2BFB /* MentionSuggestionItemView.swift */; };
|
||||||
|
A7AD63BA2ACED2FA00E1B12F /* CompletionSuggestionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7AD63B92ACED2FA00E1B12F /* CompletionSuggestionService.swift */; };
|
||||||
|
A7AD63BC2ACF0B4D00E1B12F /* CompletionSuggestionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7AD63BB2ACF0B4D00E1B12F /* CompletionSuggestionServiceTests.swift */; };
|
||||||
|
A7AD63BE2AD019AD00E1B12F /* CompletionSuggestionServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7AD63BD2AD019AD00E1B12F /* CompletionSuggestionServiceProtocol.swift */; };
|
||||||
|
A7AD63C02AD01A1000E1B12F /* CompletionSuggestionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7AD63BF2AD01A1000E1B12F /* CompletionSuggestionModels.swift */; };
|
||||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||||
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
||||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
||||||
@ -964,7 +970,7 @@
|
|||||||
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
|
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
|
||||||
12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = "<group>"; };
|
12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = "<group>"; };
|
12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
||||||
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
||||||
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||||
@ -1121,7 +1127,7 @@
|
|||||||
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
|
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
|
||||||
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DesignKit; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
||||||
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
||||||
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||||
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
|
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
@ -1322,7 +1328,7 @@
|
|||||||
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
||||||
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
||||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
|
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||||
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
||||||
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
||||||
@ -1393,6 +1399,12 @@
|
|||||||
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
||||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||||
|
A78643EF2ACDBA54005B2BFB /* CompletionSuggestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionView.swift; sourceTree = "<group>"; };
|
||||||
|
A78643F12ACDBAEF005B2BFB /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = "<group>"; };
|
||||||
|
A7AD63B92ACED2FA00E1B12F /* CompletionSuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionService.swift; sourceTree = "<group>"; };
|
||||||
|
A7AD63BB2ACF0B4D00E1B12F /* CompletionSuggestionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionServiceTests.swift; sourceTree = "<group>"; };
|
||||||
|
A7AD63BD2AD019AD00E1B12F /* CompletionSuggestionServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionServiceProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
A7AD63BF2AD01A1000E1B12F /* CompletionSuggestionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionModels.swift; sourceTree = "<group>"; };
|
||||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
|
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
|
||||||
@ -1439,7 +1451,7 @@
|
|||||||
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
|
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
|
||||||
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
||||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
||||||
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = "<group>"; };
|
B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
@ -1531,7 +1543,7 @@
|
|||||||
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||||
CE1CD5EC6265A09315772DB7 /* AudioConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverter.swift; sourceTree = "<group>"; };
|
CE1CD5EC6265A09315772DB7 /* AudioConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverter.swift; sourceTree = "<group>"; };
|
||||||
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
||||||
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
||||||
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
||||||
D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = "<group>"; };
|
D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
|
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
|
||||||
@ -1621,7 +1633,7 @@
|
|||||||
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
||||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||||
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
|
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
|
||||||
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
@ -1635,7 +1647,7 @@
|
|||||||
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
|
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
|
||||||
F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = "<group>"; };
|
F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = "<group>"; };
|
||||||
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
|
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = "<group>"; };
|
F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = "<group>"; };
|
||||||
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
|
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
|
||||||
F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = "<group>"; };
|
F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = "<group>"; };
|
F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = "<group>"; };
|
||||||
@ -2022,8 +2034,11 @@
|
|||||||
children = (
|
children = (
|
||||||
BA188BB0A216D763E46E3279 /* ComposerToolbarModels.swift */,
|
BA188BB0A216D763E46E3279 /* ComposerToolbarModels.swift */,
|
||||||
CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */,
|
CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */,
|
||||||
|
A7AD63B92ACED2FA00E1B12F /* CompletionSuggestionService.swift */,
|
||||||
E44928D844E16EE48A311FCA /* ComposerToolbarViewModelProtocol.swift */,
|
E44928D844E16EE48A311FCA /* ComposerToolbarViewModelProtocol.swift */,
|
||||||
4BBA16517DB72736545D0F6E /* View */,
|
4BBA16517DB72736545D0F6E /* View */,
|
||||||
|
A7AD63BD2AD019AD00E1B12F /* CompletionSuggestionServiceProtocol.swift */,
|
||||||
|
A7AD63BF2AD01A1000E1B12F /* CompletionSuggestionModels.swift */,
|
||||||
);
|
);
|
||||||
path = ComposerToolbar;
|
path = ComposerToolbar;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2426,6 +2441,8 @@
|
|||||||
0AE449DFBA7CC863EEB2FD2A /* FormattingToolbar.swift */,
|
0AE449DFBA7CC863EEB2FD2A /* FormattingToolbar.swift */,
|
||||||
A0A01AECFF54281CF35909A6 /* MessageComposer.swift */,
|
A0A01AECFF54281CF35909A6 /* MessageComposer.swift */,
|
||||||
3E6A9B9DFEE964962C179DE3 /* RoomAttachmentPicker.swift */,
|
3E6A9B9DFEE964962C179DE3 /* RoomAttachmentPicker.swift */,
|
||||||
|
A78643EF2ACDBA54005B2BFB /* CompletionSuggestionView.swift */,
|
||||||
|
A78643F12ACDBAEF005B2BFB /* MentionSuggestionItemView.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2811,6 +2828,7 @@
|
|||||||
7583EAC171059A86B767209F /* MediaProvider */,
|
7583EAC171059A86B767209F /* MediaProvider */,
|
||||||
7DBC911559934065993A5FF4 /* NotificationManager */,
|
7DBC911559934065993A5FF4 /* NotificationManager */,
|
||||||
1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */,
|
1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */,
|
||||||
|
A7AD63BB2ACF0B4D00E1B12F /* CompletionSuggestionServiceTests.swift */,
|
||||||
);
|
);
|
||||||
path = Sources;
|
path = Sources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -4567,6 +4585,7 @@
|
|||||||
4E8F17EBA24FBBA6ABB62ECB /* MockBackgroundTaskService.swift in Sources */,
|
4E8F17EBA24FBBA6ABB62ECB /* MockBackgroundTaskService.swift in Sources */,
|
||||||
1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */,
|
1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */,
|
||||||
DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */,
|
DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */,
|
||||||
|
A7AD63BC2ACF0B4D00E1B12F /* CompletionSuggestionServiceTests.swift in Sources */,
|
||||||
A896998A6784DB6F16E912F4 /* MockMediaLoader.swift in Sources */,
|
A896998A6784DB6F16E912F4 /* MockMediaLoader.swift in Sources */,
|
||||||
981853650217B6C8ECDD998C /* NavigationRootCoordinatorTests.swift in Sources */,
|
981853650217B6C8ECDD998C /* NavigationRootCoordinatorTests.swift in Sources */,
|
||||||
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */,
|
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */,
|
||||||
@ -4635,6 +4654,7 @@
|
|||||||
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */,
|
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */,
|
||||||
53C1E7F6A7D6409D89F36ED7 /* AggregatedReactionMock.swift in Sources */,
|
53C1E7F6A7D6409D89F36ED7 /* AggregatedReactionMock.swift in Sources */,
|
||||||
4219391CD2351E410554B3E8 /* AggregratedReaction.swift in Sources */,
|
4219391CD2351E410554B3E8 /* AggregratedReaction.swift in Sources */,
|
||||||
|
A7AD63BE2AD019AD00E1B12F /* CompletionSuggestionServiceProtocol.swift in Sources */,
|
||||||
64D05250CEDE8B604119F6E6 /* Alert.swift in Sources */,
|
64D05250CEDE8B604119F6E6 /* Alert.swift in Sources */,
|
||||||
39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */,
|
39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */,
|
||||||
155063E980E763D4910EA3CF /* Analytics+SwiftUI.swift in Sources */,
|
155063E980E763D4910EA3CF /* Analytics+SwiftUI.swift in Sources */,
|
||||||
@ -4941,6 +4961,7 @@
|
|||||||
EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */,
|
EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */,
|
||||||
767D366C40F1311CFA333763 /* PillContext.swift in Sources */,
|
767D366C40F1311CFA333763 /* PillContext.swift in Sources */,
|
||||||
7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */,
|
7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */,
|
||||||
|
A78643F22ACDBAEF005B2BFB /* MentionSuggestionItemView.swift in Sources */,
|
||||||
8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */,
|
8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */,
|
||||||
7E2BB42805C59DB57E95610F /* PillView.swift in Sources */,
|
7E2BB42805C59DB57E95610F /* PillView.swift in Sources */,
|
||||||
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */,
|
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */,
|
||||||
@ -4982,6 +5003,7 @@
|
|||||||
D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */,
|
D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */,
|
||||||
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */,
|
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */,
|
||||||
FA4296218444C48BC890F46B /* RoomMemberDetails.swift in Sources */,
|
FA4296218444C48BC890F46B /* RoomMemberDetails.swift in Sources */,
|
||||||
|
A78643F02ACDBA54005B2BFB /* CompletionSuggestionView.swift in Sources */,
|
||||||
19FE025AE9BA2959B6589B0D /* RoomMemberDetailsScreen.swift in Sources */,
|
19FE025AE9BA2959B6589B0D /* RoomMemberDetailsScreen.swift in Sources */,
|
||||||
899793EFC63DF93C3E0141E7 /* RoomMemberDetailsScreenCoordinator.swift in Sources */,
|
899793EFC63DF93C3E0141E7 /* RoomMemberDetailsScreenCoordinator.swift in Sources */,
|
||||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */,
|
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */,
|
||||||
@ -5047,6 +5069,7 @@
|
|||||||
5894C2514400A4FBC9327632 /* ServerConfirmationScreenCoordinator.swift in Sources */,
|
5894C2514400A4FBC9327632 /* ServerConfirmationScreenCoordinator.swift in Sources */,
|
||||||
401BB28CD6B7DD6B4E7863E7 /* ServerConfirmationScreenModels.swift in Sources */,
|
401BB28CD6B7DD6B4E7863E7 /* ServerConfirmationScreenModels.swift in Sources */,
|
||||||
F0A26CD502C3A5868353B0FA /* ServerConfirmationScreenViewModel.swift in Sources */,
|
F0A26CD502C3A5868353B0FA /* ServerConfirmationScreenViewModel.swift in Sources */,
|
||||||
|
A7AD63BA2ACED2FA00E1B12F /* CompletionSuggestionService.swift in Sources */,
|
||||||
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */,
|
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */,
|
||||||
43F35A7E5703D64DB0519C59 /* ServerSelectionScreen.swift in Sources */,
|
43F35A7E5703D64DB0519C59 /* ServerSelectionScreen.swift in Sources */,
|
||||||
E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */,
|
E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */,
|
||||||
@ -5174,6 +5197,7 @@
|
|||||||
8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */,
|
8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */,
|
||||||
4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */,
|
4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */,
|
||||||
3113065AABBC14CEAE6843FA /* UserSessionFlowCoordinatorStateMachine.swift in Sources */,
|
3113065AABBC14CEAE6843FA /* UserSessionFlowCoordinatorStateMachine.swift in Sources */,
|
||||||
|
A7AD63C02AD01A1000E1B12F /* CompletionSuggestionModels.swift in Sources */,
|
||||||
978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */,
|
978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */,
|
||||||
7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */,
|
7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */,
|
||||||
AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */,
|
AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */,
|
||||||
|
@ -340,10 +340,13 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace)
|
analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace)
|
||||||
|
|
||||||
|
let completionSuggestionService = CompletionSuggestionService(roomProxy: roomProxy, areSuggestionsEnabled: appSettings.mentionsEnabled)
|
||||||
|
|
||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: roomProxy,
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: roomProxy,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: userSession.mediaProvider,
|
mediaProvider: userSession.mediaProvider,
|
||||||
emojiProvider: emojiProvider)
|
emojiProvider: emojiProvider,
|
||||||
|
completionSuggestionService: completionSuggestionService)
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
coordinator.actions
|
coordinator.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
@ -197,6 +197,35 @@ class BugReportServiceMock: BugReportServiceProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class CompletionSuggestionServiceMock: CompletionSuggestionServiceProtocol {
|
||||||
|
var areSuggestionsEnabled: Bool {
|
||||||
|
get { return underlyingAreSuggestionsEnabled }
|
||||||
|
set(value) { underlyingAreSuggestionsEnabled = value }
|
||||||
|
}
|
||||||
|
var underlyingAreSuggestionsEnabled: Bool!
|
||||||
|
var suggestionsPublisher: AnyPublisher<[SuggestionItem], Never> {
|
||||||
|
get { return underlyingSuggestionsPublisher }
|
||||||
|
set(value) { underlyingSuggestionsPublisher = value }
|
||||||
|
}
|
||||||
|
var underlyingSuggestionsPublisher: AnyPublisher<[SuggestionItem], Never>!
|
||||||
|
|
||||||
|
//MARK: - setSuggestionTrigger
|
||||||
|
|
||||||
|
var setSuggestionTriggerCallsCount = 0
|
||||||
|
var setSuggestionTriggerCalled: Bool {
|
||||||
|
return setSuggestionTriggerCallsCount > 0
|
||||||
|
}
|
||||||
|
var setSuggestionTriggerReceivedSuggestionTrigger: SuggestionPattern?
|
||||||
|
var setSuggestionTriggerReceivedInvocations: [SuggestionPattern?] = []
|
||||||
|
var setSuggestionTriggerClosure: ((SuggestionPattern?) -> Void)?
|
||||||
|
|
||||||
|
func setSuggestionTrigger(_ suggestionTrigger: SuggestionPattern?) {
|
||||||
|
setSuggestionTriggerCallsCount += 1
|
||||||
|
setSuggestionTriggerReceivedSuggestionTrigger = suggestionTrigger
|
||||||
|
setSuggestionTriggerReceivedInvocations.append(suggestionTrigger)
|
||||||
|
setSuggestionTriggerClosure?(suggestionTrigger)
|
||||||
|
}
|
||||||
|
}
|
||||||
class NotificationCenterMock: NotificationCenterProtocol {
|
class NotificationCenterMock: NotificationCenterProtocol {
|
||||||
|
|
||||||
//MARK: - post
|
//MARK: - post
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import WysiwygComposer
|
||||||
|
|
||||||
|
enum SuggestionItem: Identifiable, Equatable {
|
||||||
|
case user(item: MentionSuggestionItem)
|
||||||
|
|
||||||
|
var id: String {
|
||||||
|
switch self {
|
||||||
|
case .user(let user):
|
||||||
|
return user.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MentionSuggestionItem: Identifiable, Equatable {
|
||||||
|
let id: String
|
||||||
|
let displayName: String?
|
||||||
|
let avatarURL: URL?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WysiwygComposer.SuggestionPattern {
|
||||||
|
var toElementPattern: SuggestionPattern? {
|
||||||
|
switch key {
|
||||||
|
case .at:
|
||||||
|
return SuggestionPattern(type: .user, text: text)
|
||||||
|
// Not yet supported
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
|
||||||
|
let areSuggestionsEnabled: Bool
|
||||||
|
let suggestionsPublisher: AnyPublisher<[SuggestionItem], Never>
|
||||||
|
|
||||||
|
private let suggestionTriggerSubject = CurrentValueSubject<SuggestionPattern?, Never>(nil)
|
||||||
|
|
||||||
|
init(roomProxy: RoomProxyProtocol, areSuggestionsEnabled: Bool) {
|
||||||
|
self.areSuggestionsEnabled = areSuggestionsEnabled
|
||||||
|
guard areSuggestionsEnabled else {
|
||||||
|
suggestionsPublisher = Empty().eraseToAnyPublisher()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
suggestionsPublisher = suggestionTriggerSubject
|
||||||
|
.combineLatest(roomProxy.members)
|
||||||
|
.map { suggestionTrigger, members -> [SuggestionItem] in
|
||||||
|
guard let suggestionTrigger else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
switch suggestionTrigger.type {
|
||||||
|
case .user:
|
||||||
|
return members.filter { member in
|
||||||
|
guard !member.isAccountOwner && member.membership == .join else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let containedInUserID = member.userID.localizedStandardContains(suggestionTrigger.text.lowercased())
|
||||||
|
|
||||||
|
let containedInDisplayName: Bool
|
||||||
|
if let displayName = member.displayName {
|
||||||
|
containedInDisplayName = displayName.localizedStandardContains(suggestionTrigger.text.lowercased())
|
||||||
|
} else {
|
||||||
|
containedInDisplayName = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return containedInUserID || containedInDisplayName
|
||||||
|
}
|
||||||
|
.map { SuggestionItem.user(item: .init(id: $0.userID, displayName: $0.displayName, avatarURL: $0.avatarURL)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.debounce(for: 0.5, scheduler: DispatchQueue.main)
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSuggestionTrigger(_ suggestionTrigger: SuggestionPattern?) {
|
||||||
|
suggestionTriggerSubject.value = suggestionTrigger
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
// sourcery: AutoMockable
|
||||||
|
protocol CompletionSuggestionServiceProtocol {
|
||||||
|
// To be removed once we suggestions and mentions are always enabled
|
||||||
|
var areSuggestionsEnabled: Bool { get }
|
||||||
|
var suggestionsPublisher: AnyPublisher<[SuggestionItem], Never> { get }
|
||||||
|
|
||||||
|
func setSuggestionTrigger(_ suggestionTrigger: SuggestionPattern?)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CompletionSuggestionServiceMock {
|
||||||
|
struct CompletionSuggestionServiceMockConfiguration {
|
||||||
|
var areSuggestionsEnabled = true
|
||||||
|
var suggestions: [SuggestionItem] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(configuration: CompletionSuggestionServiceMockConfiguration) {
|
||||||
|
self.init()
|
||||||
|
underlyingAreSuggestionsEnabled = configuration.areSuggestionsEnabled
|
||||||
|
underlyingSuggestionsPublisher = Just(configuration.suggestions).eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,8 @@ enum ComposerToolbarViewAction {
|
|||||||
struct ComposerToolbarViewState: BindableState {
|
struct ComposerToolbarViewState: BindableState {
|
||||||
var composerMode: RoomScreenComposerMode = .default
|
var composerMode: RoomScreenComposerMode = .default
|
||||||
var composerEmpty = true
|
var composerEmpty = true
|
||||||
|
var areSuggestionsEnabled = true
|
||||||
|
var suggestions: [SuggestionItem] = []
|
||||||
|
|
||||||
var bindings: ComposerToolbarViewStateBindings
|
var bindings: ComposerToolbarViewStateBindings
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ typealias ComposerToolbarViewModelType = StateStoreViewModel<ComposerToolbarView
|
|||||||
|
|
||||||
final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerToolbarViewModelProtocol {
|
final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerToolbarViewModelProtocol {
|
||||||
private let wysiwygViewModel: WysiwygComposerViewModel
|
private let wysiwygViewModel: WysiwygComposerViewModel
|
||||||
|
private let completionSuggestionService: CompletionSuggestionServiceProtocol
|
||||||
private let actionsSubject: PassthroughSubject<ComposerToolbarViewModelAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<ComposerToolbarViewModelAction, Never> = .init()
|
||||||
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> {
|
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
@ -37,10 +38,11 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
|
|
||||||
private var currentLinkData: WysiwygLinkData?
|
private var currentLinkData: WysiwygLinkData?
|
||||||
|
|
||||||
init(wysiwygViewModel: WysiwygComposerViewModel) {
|
init(wysiwygViewModel: WysiwygComposerViewModel, completionSuggestionService: CompletionSuggestionServiceProtocol, mediaProvider: MediaProviderProtocol) {
|
||||||
self.wysiwygViewModel = wysiwygViewModel
|
self.wysiwygViewModel = wysiwygViewModel
|
||||||
|
self.completionSuggestionService = completionSuggestionService
|
||||||
|
|
||||||
super.init(initialViewState: ComposerToolbarViewState(bindings: .init()))
|
super.init(initialViewState: ComposerToolbarViewState(areSuggestionsEnabled: completionSuggestionService.areSuggestionsEnabled, bindings: .init()), imageProvider: mediaProvider)
|
||||||
|
|
||||||
context.$viewState
|
context.$viewState
|
||||||
.map(\.composerMode)
|
.map(\.composerMode)
|
||||||
@ -69,6 +71,16 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
}
|
}
|
||||||
.weakAssign(to: \.state.bindings.formatItems, on: self)
|
.weakAssign(to: \.state.bindings.formatItems, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
wysiwygViewModel.$suggestionPattern
|
||||||
|
.sink { [weak self] suggestionPattern in
|
||||||
|
self?.completionSuggestionService.setSuggestionTrigger(suggestionPattern?.toElementPattern)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
completionSuggestionService.suggestionsPublisher
|
||||||
|
.weakAssign(to: \.state.suggestions, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
@ -16,6 +16,15 @@
|
|||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
struct SuggestionPattern: Equatable {
|
||||||
|
enum SuggestionType: Equatable {
|
||||||
|
case user
|
||||||
|
}
|
||||||
|
|
||||||
|
let type: SuggestionType
|
||||||
|
let text: String
|
||||||
|
}
|
||||||
|
|
||||||
protocol ComposerToolbarViewModelProtocol {
|
protocol ComposerToolbarViewModelProtocol {
|
||||||
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get }
|
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get }
|
||||||
var context: ComposerToolbarViewModelType.Context { get }
|
var context: ComposerToolbarViewModelType.Context { get }
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CompletionSuggestionView: View {
|
||||||
|
let imageProvider: ImageProviderProtocol?
|
||||||
|
let items: [SuggestionItem]
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let topPadding: CGFloat = 8.0
|
||||||
|
static let listItemPadding: CGFloat = 4.0
|
||||||
|
static let lineSpacing: CGFloat = 10.0
|
||||||
|
static let maxHeight: CGFloat = 300.0
|
||||||
|
static let maxVisibleRows = 4
|
||||||
|
|
||||||
|
/*
|
||||||
|
As of iOS 16.0, SwiftUI's List uses `UICollectionView` instead
|
||||||
|
of `UITableView` internally, this value is an adjustment to apply
|
||||||
|
to the list items in order to be as close as possible as the
|
||||||
|
`UITableView` display.
|
||||||
|
*/
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
static let collectionViewPaddingCorrection: CGFloat = -5.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Public
|
||||||
|
|
||||||
|
var showBackgroundShadow = true
|
||||||
|
@State private var prototypeListItemFrame: CGRect = .zero
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if items.isEmpty {
|
||||||
|
EmptyView()
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
MentionSuggestionItemView(imageProvider: nil, item: .init(id: "", displayName: nil, avatarURL: nil))
|
||||||
|
.background(ViewFrameReader(frame: $prototypeListItemFrame))
|
||||||
|
.hidden()
|
||||||
|
if showBackgroundShadow {
|
||||||
|
BackgroundView {
|
||||||
|
list()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func list() -> some View {
|
||||||
|
List(items) { item in
|
||||||
|
Button { } label: {
|
||||||
|
switch item {
|
||||||
|
case .user(let mention):
|
||||||
|
MentionSuggestionItemView(imageProvider: imageProvider, item: mention)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modifier(ListItemPaddingModifier(isFirst: items.first?.id == item.id))
|
||||||
|
}
|
||||||
|
.listStyle(PlainListStyle())
|
||||||
|
.frame(height: min(Constants.maxHeight,
|
||||||
|
min(contentHeightForRowCount(Constants.maxVisibleRows),
|
||||||
|
contentHeightForRowCount(items.count))))
|
||||||
|
.background(Color.compound.bgCanvasDefault)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func contentHeightForRowCount(_ count: Int) -> CGFloat {
|
||||||
|
(prototypeListItemFrame.height + (Constants.listItemPadding * 2) + Constants.lineSpacing) * CGFloat(count) + Constants.topPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ListItemPaddingModifier: ViewModifier {
|
||||||
|
private let isFirst: Bool
|
||||||
|
|
||||||
|
init(isFirst: Bool) {
|
||||||
|
self.isFirst = isFirst
|
||||||
|
}
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
var topPadding: CGFloat = isFirst ? Constants.listItemPadding + Constants.topPadding : Constants.listItemPadding
|
||||||
|
var bottomPadding: CGFloat = Constants.listItemPadding
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
topPadding += Constants.collectionViewPaddingCorrection
|
||||||
|
bottomPadding += Constants.collectionViewPaddingCorrection
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
.padding(.top, topPadding)
|
||||||
|
.padding(.bottom, bottomPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct BackgroundView<Content: View>: View {
|
||||||
|
var content: () -> Content
|
||||||
|
|
||||||
|
private let shadowRadius: CGFloat = 20.0
|
||||||
|
|
||||||
|
init(@ViewBuilder content: @escaping () -> Content) {
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content()
|
||||||
|
.background(Color.compound.bgSubtlePrimary)
|
||||||
|
.clipShape(RoundedCornerShape(radius: shadowRadius, corners: [.topLeft, .topRight]))
|
||||||
|
.shadow(color: .black.opacity(0.20), radius: 20.0, x: 0.0, y: 3.0)
|
||||||
|
.mask(Rectangle().padding(.init(top: -(shadowRadius * 2), leading: 0.0, bottom: 0.0, trailing: 0.0)))
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct CompletionSuggestion_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static let multipleItems: [SuggestionItem] = (0...10).map { index in
|
||||||
|
SuggestionItem.user(item: MentionSuggestionItem(id: "\(index)", displayName: "\(index)", avatarURL: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
// Putting them is VStack allows the preview to work properly in tests
|
||||||
|
VStack {
|
||||||
|
CompletionSuggestionView(imageProvider: MockMediaProvider(),
|
||||||
|
items: [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil)),
|
||||||
|
.user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: URL.documentsDirectory))])
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
CompletionSuggestionView(imageProvider: MockMediaProvider(),
|
||||||
|
items: multipleItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,8 @@ struct ComposerToolbar: View {
|
|||||||
@FocusState private var composerFocused: Bool
|
@FocusState private var composerFocused: Bool
|
||||||
@ScaledMetric private var sendButtonIconSize = 16
|
@ScaledMetric private var sendButtonIconSize = 16
|
||||||
@ScaledMetric(relativeTo: .title) private var closeRTEButtonSize = 30
|
@ScaledMetric(relativeTo: .title) private var closeRTEButtonSize = 30
|
||||||
|
|
||||||
|
@State private var frame: CGRect = .zero
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
@ -34,8 +36,21 @@ struct ComposerToolbar: View {
|
|||||||
bottomBar
|
bottomBar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.background {
|
||||||
|
ViewFrameReader(frame: $frame)
|
||||||
|
}
|
||||||
|
.overlay(alignment: .bottom) {
|
||||||
|
if context.viewState.areSuggestionsEnabled {
|
||||||
|
suggestionView
|
||||||
|
.offset(y: -frame.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
.alert(item: $context.alertInfo)
|
.alert(item: $context.alertInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var suggestionView: some View {
|
||||||
|
CompletionSuggestionView(imageProvider: context.imageProvider, items: context.viewState.suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
private var topBar: some View {
|
private var topBar: some View {
|
||||||
HStack(alignment: .bottom, spacing: 5) {
|
HStack(alignment: .bottom, spacing: 5) {
|
||||||
@ -176,8 +191,24 @@ struct ComposerToolbar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static let wysiwygViewModel = WysiwygComposerViewModel()
|
||||||
|
static let composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions)),
|
||||||
|
mediaProvider: MockMediaProvider())
|
||||||
|
static let suggestions: [SuggestionItem] = [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil)),
|
||||||
|
.user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: URL.documentsDirectory))]
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ComposerToolbar.mock()
|
ComposerToolbar.mock()
|
||||||
|
|
||||||
|
// Putting them is VStack allows the completion suggestion preview to work properly in tests
|
||||||
|
VStack {
|
||||||
|
// The mock functon can't be used in this context because it does not hold a reference to the view model, losing the combine subscriptions
|
||||||
|
ComposerToolbar(context: composerViewModel.context,
|
||||||
|
wysiwygViewModel: wysiwygViewModel,
|
||||||
|
keyCommandHandler: { _ in false })
|
||||||
|
}
|
||||||
|
.previewDisplayName("With Suggestions")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +217,9 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
|||||||
extension ComposerToolbar {
|
extension ComposerToolbar {
|
||||||
static func mock() -> ComposerToolbar {
|
static func mock() -> ComposerToolbar {
|
||||||
let wysiwygViewModel = WysiwygComposerViewModel()
|
let wysiwygViewModel = WysiwygComposerViewModel()
|
||||||
let composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel)
|
let composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
|
mediaProvider: MockMediaProvider())
|
||||||
return ComposerToolbar(context: composerViewModel.context,
|
return ComposerToolbar(context: composerViewModel.context,
|
||||||
wysiwygViewModel: wysiwygViewModel,
|
wysiwygViewModel: wysiwygViewModel,
|
||||||
keyCommandHandler: { _ in false })
|
keyCommandHandler: { _ in false })
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MentionSuggestionItemView: View {
|
||||||
|
let imageProvider: ImageProviderProtocol?
|
||||||
|
let item: MentionSuggestionItem
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
LoadableAvatarImage(url: item.avatarURL,
|
||||||
|
name: item.displayName,
|
||||||
|
contentID: item.id,
|
||||||
|
avatarSize: .custom(42),
|
||||||
|
imageProvider: imageProvider)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(item.displayName ?? item.id)
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.lineLimit(1)
|
||||||
|
if item.displayName != nil {
|
||||||
|
Text(item.id)
|
||||||
|
.font(.compound.bodySM)
|
||||||
|
.foregroundColor(.compound.textSecondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MentionSuggestionItemView_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static let mockMediaProvider = MockMediaProvider()
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
MentionSuggestionItemView(imageProvider: mockMediaProvider, item: .init(id: "test", displayName: "Test", avatarURL: URL.documentsDirectory))
|
||||||
|
MentionSuggestionItemView(imageProvider: mockMediaProvider, item: .init(id: "test2", displayName: nil, avatarURL: nil))
|
||||||
|
}
|
||||||
|
}
|
@ -115,7 +115,9 @@ struct RoomAttachmentPicker: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RoomAttachmentPicker_Previews: PreviewProvider, TestablePreview {
|
struct RoomAttachmentPicker_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = ComposerToolbarViewModel(wysiwygViewModel: WysiwygComposerViewModel())
|
static let viewModel = ComposerToolbarViewModel(wysiwygViewModel: WysiwygComposerViewModel(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
|
mediaProvider: MockMediaProvider())
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
RoomAttachmentPicker(context: viewModel.context)
|
RoomAttachmentPicker(context: viewModel.context)
|
||||||
|
@ -24,6 +24,7 @@ struct RoomScreenCoordinatorParameters {
|
|||||||
let timelineController: RoomTimelineControllerProtocol
|
let timelineController: RoomTimelineControllerProtocol
|
||||||
let mediaProvider: MediaProviderProtocol
|
let mediaProvider: MediaProviderProtocol
|
||||||
let emojiProvider: EmojiProviderProtocol
|
let emojiProvider: EmojiProviderProtocol
|
||||||
|
let completionSuggestionService: CompletionSuggestionServiceProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomScreenCoordinatorAction {
|
enum RoomScreenCoordinatorAction {
|
||||||
@ -54,7 +55,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
|
|
||||||
init(parameters: RoomScreenCoordinatorParameters) {
|
init(parameters: RoomScreenCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
viewModel = RoomScreenViewModel(timelineController: parameters.timelineController,
|
viewModel = RoomScreenViewModel(timelineController: parameters.timelineController,
|
||||||
mediaProvider: parameters.mediaProvider,
|
mediaProvider: parameters.mediaProvider,
|
||||||
roomProxy: parameters.roomProxy,
|
roomProxy: parameters.roomProxy,
|
||||||
@ -66,7 +67,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
maxCompressedHeight: ComposerConstant.maxHeight,
|
maxCompressedHeight: ComposerConstant.maxHeight,
|
||||||
maxExpandedHeight: ComposerConstant.maxHeight,
|
maxExpandedHeight: ComposerConstant.maxHeight,
|
||||||
parserStyle: .elementX)
|
parserStyle: .elementX)
|
||||||
composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel)
|
composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel, completionSuggestionService: parameters.completionSuggestionService, mediaProvider: parameters.mediaProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
@ -233,7 +233,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
appSettings.$readReceiptsEnabled
|
appSettings.$readReceiptsEnabled
|
||||||
.weakAssign(to: \.state.readReceiptsEnabled, on: self)
|
.weakAssign(to: \.state.readReceiptsEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
roomProxy.members
|
roomProxy.members
|
||||||
.map { members in
|
.map { members in
|
||||||
members.reduce(into: [String: RoomMemberState]()) { dictionary, member in
|
members.reduce(into: [String: RoomMemberState]()) { dictionary, member in
|
||||||
|
@ -235,7 +235,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Some room name", avatarURL: nil)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Some room name", avatarURL: nil)),
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
@ -244,7 +245,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Some room name", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Some room name", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
@ -255,7 +257,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
@ -266,7 +269,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
@ -278,7 +282,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
@ -292,7 +297,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Small timeline", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Small timeline", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
@ -306,7 +312,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Small timeline, paginating", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Small timeline, paginating", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
@ -320,7 +327,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
@ -335,7 +343,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
@ -349,7 +358,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
@ -363,7 +373,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
@ -377,7 +388,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
@ -391,7 +403,8 @@ class MockScreen: Identifiable {
|
|||||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
emojiProvider: EmojiProvider())
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()))
|
||||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
53
UnitTests/Sources/CompletionSuggestionServiceTests.swift
Normal file
53
UnitTests/Sources/CompletionSuggestionServiceTests.swift
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import ElementX
|
||||||
|
|
||||||
|
final class CompletionSuggestionServiceTests: XCTestCase {
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
cancellables.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUserSuggestons() async throws {
|
||||||
|
let alice: RoomMemberProxyMock = .mockAlice
|
||||||
|
let members: [RoomMemberProxyMock] = [alice, .mockBob, .mockCharlie, .mockMe]
|
||||||
|
let roomProxyMock = RoomProxyMock(with: .init(displayName: "test", members: members))
|
||||||
|
let service = CompletionSuggestionService(roomProxy: roomProxyMock, areSuggestionsEnabled: true)
|
||||||
|
|
||||||
|
var deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in
|
||||||
|
suggestions == []
|
||||||
|
}
|
||||||
|
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in
|
||||||
|
suggestions == [.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL))]
|
||||||
|
}
|
||||||
|
service.setSuggestionTrigger(.init(type: .user, text: "ali"))
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in
|
||||||
|
suggestions == []
|
||||||
|
}
|
||||||
|
service.setSuggestionTrigger(.init(type: .user, text: "me"))
|
||||||
|
try await deferred.fulfill()
|
||||||
|
}
|
||||||
|
}
|
@ -14,15 +14,18 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
import WysiwygComposer
|
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
|
import WysiwygComposer
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class ComposerToolbarViewModelTests: XCTestCase {
|
class ComposerToolbarViewModelTests: XCTestCase {
|
||||||
private var appSettings: AppSettings!
|
private var appSettings: AppSettings!
|
||||||
private var wysiwygViewModel: WysiwygComposerViewModel!
|
private var wysiwygViewModel: WysiwygComposerViewModel!
|
||||||
private var viewModel: ComposerToolbarViewModel!
|
private var viewModel: ComposerToolbarViewModel!
|
||||||
|
private var completionSuggestionServiceMock: CompletionSuggestionServiceMock!
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
AppSettings.reset()
|
AppSettings.reset()
|
||||||
@ -30,7 +33,10 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
appSettings.richTextEditorEnabled = true
|
appSettings.richTextEditorEnabled = true
|
||||||
ServiceLocator.shared.register(appSettings: appSettings)
|
ServiceLocator.shared.register(appSettings: appSettings)
|
||||||
wysiwygViewModel = WysiwygComposerViewModel()
|
wysiwygViewModel = WysiwygComposerViewModel()
|
||||||
viewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel)
|
completionSuggestionServiceMock = CompletionSuggestionServiceMock(configuration: .init())
|
||||||
|
viewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||||
|
completionSuggestionService: completionSuggestionServiceMock,
|
||||||
|
mediaProvider: MockMediaProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testComposerFocus() {
|
func testComposerFocus() {
|
||||||
@ -92,4 +98,23 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
viewModel.process(viewAction: .composerAction(action: .link))
|
viewModel.process(viewAction: .composerAction(action: .link))
|
||||||
XCTAssertNotNil(viewModel.state.bindings.alertInfo)
|
XCTAssertNotNil(viewModel.state.bindings.alertInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSuggestions() {
|
||||||
|
let suggestions: [SuggestionItem] = [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil)),
|
||||||
|
.user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: URL.documentsDirectory))]
|
||||||
|
let mockCompletionSuggestionService = CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions))
|
||||||
|
viewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||||
|
completionSuggestionService: mockCompletionSuggestionService,
|
||||||
|
mediaProvider: MockMediaProvider())
|
||||||
|
|
||||||
|
XCTAssertEqual(viewModel.state.suggestions, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSuggestionTrigger() {
|
||||||
|
wysiwygViewModel.setMarkdownContent("@test")
|
||||||
|
wysiwygViewModel.setMarkdownContent("#not_implemented_yey")
|
||||||
|
|
||||||
|
// The first one is nil because when initialised the view model is empty
|
||||||
|
XCTAssertEqual(completionSuggestionServiceMock.setSuggestionTriggerReceivedInvocations, [nil, .init(type: .user, text: "test"), nil])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.2.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.With-Suggestions.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.With-Suggestions.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.2.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
1
changelog.d/1826.feature
Normal file
1
changelog.d/1826.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Added the user suggestions view when trying to mention a user (but it doesn't react to tap yet).
|
Loading…
x
Reference in New Issue
Block a user