* provider can now check the current session

* More testable code

* created the test condition

* it works but not always not sure why, need to dig deeper

* sadly we need to use textkit 1 to solve this issue

* removed developer option screen test

* this experimental solution kinda works but I need a way to pill recomputation is weird

* format

* display improvement

* better and faster solution

* pilished the code

* better coloring

* swift format

* just need to solve the caching issue

* fix caching issue

* tests done!

* changelog

* pr comments addressed

* all pr comments addressed

* docs

* line lenght

* updated tests and fixed a parsing permalink issue

* MentionBuilder

* pr comments

* swiftformat

* code blocks should not have links

* Update ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* Update ElementX/Sources/Services/Client/ClientProxy.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* Update UnitTests/Sources/AttributedStringBuilderTests.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* Update ElementX/Sources/UITests/UITestsAppCoordinator.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* Update ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* Update ElementX/Sources/Other/Pills/MentionBuilder.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* pr comments

* swiftformat

---------

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
Mauro 2023-09-27 19:27:07 +02:00 committed by GitHub
parent 8d4c5e2885
commit 7bde419e85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 530 additions and 97 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
@ -445,8 +445,10 @@
8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */; };
8B76191B9DDD1AC90A6E3A35 /* MediaFileHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */; };
8BC8EF6705A78946C1F22891 /* SoftLogoutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */; };
8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */; };
8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; };
8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; };
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
8D605456793F243649EC96AA /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = CD6B0C4639E066915B5E6463 /* target.yml */; };
@ -711,10 +713,10 @@
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; };
D8385A51A3D0FA9283556281 /* RoundedLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */; };
D84D5BDFB1B915389AC807B4 /* CreatePollScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A328F9E556F5CFA89332017 /* CreatePollScreenViewModel.swift */; };
D85D4FA590305180B4A41795 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3073CCD77D906B330BC1D6 /* Tests.swift */; };
D871C8CF46950F959C9A62C3 /* WelcomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C54464351F170D570110AFCA /* WelcomeScreen.swift */; };
D876EC0FED3B6D46C806912A /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */; };
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */; };
D9092786ACCFF72565AD7389 /* PillContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B203689DFD1431181A795F4 /* PillContext.swift */; };
D9473FC9B077A6EDB7A12001 /* LocationRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */; };
D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352359663A0E52BA20761EE /* LoadableImage.swift */; };
@ -724,6 +726,7 @@
DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */; };
DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; };
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; };
DEF1477A76F5AAE0A2EB0F32 /* NSEMentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AF5A7EE5CA321724ED32CC /* NSEMentionBuilder.swift */; };
DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */; };
DF05F9C9D3D977EB77E13692 /* DesignKit in Frameworks */ = {isa = PBXBuildFile; productRef = A593735D882778FD2C9A185B /* DesignKit */; };
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */; };
@ -830,6 +833,7 @@
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; };
FCDA202B246F75BA28E10C5F /* MapTilerAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */; };
FD4C21F8DA1E273DE94FCD1A /* NotificationItemProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B927CF5EF7FCCDA5EDC474B /* NotificationItemProxyProtocol.swift */; };
FD4DEC88210F35C35B2FB386 /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A1398EFF65090FDA1CB639 /* ProcessInfo.swift */; };
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; };
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1593DD87F974F8509BB619 /* ElementAnimations.swift */; };
FF34BF2AF731340AF9414A18 /* SwipeRightAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4552D3466B1453F287223ADA /* SwipeRightAction.swift */; };
@ -943,13 +947,14 @@
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>"; };
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>"; };
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>"; };
142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryDetails.swift; sourceTree = "<group>"; };
1454CF3AABD242F55C8A2615 /* InviteUsersScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenModels.swift; sourceTree = "<group>"; };
153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsSummaryView.swift; sourceTree = "<group>"; };
15748C254911E3654C93B0ED /* MentionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionBuilder.swift; sourceTree = "<group>"; };
15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModel.swift; sourceTree = "<group>"; };
16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenUITests.swift; sourceTree = "<group>"; };
16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenModels.swift; sourceTree = "<group>"; };
@ -1095,7 +1100,7 @@
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>"; };
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>"; };
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>"; };
@ -1109,6 +1114,7 @@
4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = "<group>"; };
4ADC55DFF46083BC957E0019 /* CreatePollScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollScreenModels.swift; sourceTree = "<group>"; };
4B203689DFD1431181A795F4 /* PillContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContext.swift; sourceTree = "<group>"; };
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = "<group>"; };
4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
@ -1293,7 +1299,7 @@
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>"; };
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>"; };
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>"; };
@ -1302,6 +1308,7 @@
8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = "<group>"; };
913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachmentData.swift; sourceTree = "<group>"; };
91CF6F7D08228D16BA69B63B /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
923485F85E1D765EF9D20E88 /* UserProfileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileCell.swift; sourceTree = "<group>"; };
92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCAuthenticationPresenter.swift; sourceTree = "<group>"; };
@ -1403,12 +1410,13 @@
B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenModels.swift; sourceTree = "<group>"; };
B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenCoordinator.swift; sourceTree = "<group>"; };
B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = "<group>"; };
B3A1398EFF65090FDA1CB639 /* ProcessInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessInfo.swift; sourceTree = "<group>"; };
B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = "<group>"; };
B48B7AD4908C5C374517B892 /* MapAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MapAssets.xcassets; 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>"; };
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>"; };
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>"; };
@ -1430,7 +1438,6 @@
BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenModels.swift; sourceTree = "<group>"; };
BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreen.swift; sourceTree = "<group>"; };
BB23BEAF8831DC6A57E39F52 /* CreatePollScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollScreenCoordinator.swift; sourceTree = "<group>"; };
BB3073CCD77D906B330BC1D6 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = "<group>"; };
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = "<group>"; };
@ -1455,6 +1462,7 @@
C2E9B841EE4878283ECDB554 /* InviteUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreen.swift; sourceTree = "<group>"; };
C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKGeneratedMocks.swift; sourceTree = "<group>"; };
C352359663A0E52BA20761EE /* LoadableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableImage.swift; sourceTree = "<group>"; };
C3AF5A7EE5CA321724ED32CC /* NSEMentionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEMentionBuilder.swift; sourceTree = "<group>"; };
C49C1CEBA9BCF5D2AD1884FA /* OnboardingScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreenViewModel.swift; sourceTree = "<group>"; };
C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModelProtocol.swift; sourceTree = "<group>"; };
C4CD503F5E0938FE53C7C6E7 /* UserDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; };
@ -1499,7 +1507,7 @@
CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = "<group>"; };
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.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>"; };
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>"; };
@ -1587,7 +1595,7 @@
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>"; };
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>"; };
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>"; };
@ -1601,7 +1609,7 @@
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>"; };
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>"; };
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>"; };
@ -2470,6 +2478,7 @@
children = (
4959CECEC984B3995616F427 /* DataProtectionManager.swift */,
D3D455BC2423D911A62ACFB2 /* NSELogger.swift */,
C3AF5A7EE5CA321724ED32CC /* NSEMentionBuilder.swift */,
E9DFC0FBA0FC6FC4DC0FC9FC /* NSESettings.swift */,
EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */,
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */,
@ -3200,10 +3209,13 @@
9C4193C4524B35FD6B94B5A9 /* Pills */ = {
isa = PBXGroup;
children = (
15748C254911E3654C93B0ED /* MentionBuilder.swift */,
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */,
1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */,
9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */,
913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */,
7773CBFDBD458E0B7E270507 /* PillView.swift */,
4B203689DFD1431181A795F4 /* PillContext.swift */,
);
path = Pills;
sourceTree = "<group>";
@ -3533,11 +3545,11 @@
C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */,
10B7F8EE25775DE2A305CBB5 /* NotificationCenterProtocol.swift */,
F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */,
B3A1398EFF65090FDA1CB639 /* ProcessInfo.swift */,
53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */,
DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */,
4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */,
B1E227F34BE43B08E098796E /* TestablePreview.swift */,
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */,
35FA991289149D31F4286747 /* UserPreference.swift */,
7431C962E314ADAE38B6D708 /* Analytics */,
@ -4409,6 +4421,7 @@
E2DB696117BAEABAD5718023 /* MediaSourceProxy.swift in Sources */,
4FC085B1E5D1EB804495E2F4 /* MockMediaProvider.swift in Sources */,
5455147CAC63F71E48F7D699 /* NSELogger.swift in Sources */,
DEF1477A76F5AAE0A2EB0F32 /* NSEMentionBuilder.swift in Sources */,
E571163060CBE87D82CE24FD /* NSESettings.swift in Sources */,
30CC4F796B27BE8B1DFDBF5A /* NSEUserSession.swift in Sources */,
1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */,
@ -4776,6 +4789,7 @@
A969147E0EEE0E27EE226570 /* MediaUploadPreviewScreenViewModel.swift in Sources */,
9B872FF37DBE6BE054903831 /* MediaUploadPreviewScreenViewModelProtocol.swift in Sources */,
8A0BD60CA4A6004DB06B5403 /* MediaUploadingPreprocessor.swift in Sources */,
8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */,
64AB99285DC4437C0DDE9585 /* MenuSheetLabelStyle.swift in Sources */,
858B0A45257174AAFD448EA0 /* MessageComposer.swift in Sources */,
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */,
@ -4846,7 +4860,9 @@
962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */,
EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */,
7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */,
8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */,
7E2BB42805C59DB57E95610F /* PillView.swift in Sources */,
D9092786ACCFF72565AD7389 /* PillContext.swift in Sources */,
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */,
1BA04D05EBC6646958B1BE60 /* PlaceholderScreenCoordinator.swift in Sources */,
16CBD087038DE3815CDA512C /* PollMock.swift in Sources */,
@ -4854,6 +4870,7 @@
864C0D3A4077BF433DBC691F /* PollRoomTimelineItem.swift in Sources */,
153E22E8227F46545E5D681C /* PollRoomTimelineView.swift in Sources */,
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */,
FD4DEC88210F35C35B2FB386 /* ProcessInfo.swift in Sources */,
2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */,
9095B9E40DB5CF8BA26CE0D8 /* ReactionsSummaryView.swift in Sources */,
743790BF6A5B0577EA74AF14 /* ReadMarkerRoomTimelineItem.swift in Sources */,
@ -5005,7 +5022,6 @@
275EDE8849A2AC1D9309ED7C /* TemplateScreenViewModel.swift in Sources */,
2C4C750D0039AFABDF24236C /* TemplateScreenViewModelProtocol.swift in Sources */,
642DF13C49ED4121C148230E /* TestablePreview.swift in Sources */,
D85D4FA590305180B4A41795 /* Tests.swift in Sources */,
8317E1314C00DCCC99D30DA8 /* TextBasedRoomTimelineItem.swift in Sources */,
A2A5AB2E8B3F5CA769E531FA /* TextBasedRoomTimelineViewProtocol.swift in Sources */,
BB784A02BADB03C820617A46 /* TextRoomTimelineItem.swift in Sources */,

View File

@ -34,7 +34,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
private var userSession: UserSessionProtocol? {
didSet {
userSessionObserver?.cancel()
if userSession != nil {
configureNotificationManager()
observeUserSessionChanges()

View File

@ -41,6 +41,7 @@ final class AppSettings {
case hasShownWelcomeScreen
case swiftUITimelineEnabled
case voiceMessageEnabled
case mentionsEnabled
}
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@ -248,4 +249,7 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.voiceMessageEnabled, defaultValue: false, storageType: .userDefaults(store))
var voiceMessageEnabled
@UserPreference(key: UserDefaultsKeys.mentionsEnabled, defaultValue: false, storageType: .userDefaults(store))
var mentionsEnabled
}

View File

@ -23,9 +23,9 @@ struct Application: App {
private let appCoordinator: AppCoordinatorProtocol
init() {
if Tests.isRunningUITests {
if ProcessInfo.isRunningUITests {
appCoordinator = UITestsAppCoordinator()
} else if Tests.isRunningUnitTests {
} else if ProcessInfo.isRunningUnitTests {
appCoordinator = UnitTestsAppCoordinator()
} else {
appCoordinator = AppCoordinator()
@ -59,6 +59,6 @@ struct Application: App {
}
private var shouldHideStatusBar: Bool {
Tests.isRunningUITests
ProcessInfo.isRunningUITests
}
}

View File

@ -311,7 +311,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
mediaProvider: userSession.mediaProvider,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL,
mentionBuilder: MentionBuilder(mentionsEnabled: appSettings.mentionsEnabled)),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID),
appSettings: appSettings)

View File

@ -261,7 +261,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private func presentHomeScreen() {
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL,
mentionBuilder: MentionBuilder(mentionsEnabled: ServiceLocator.shared.settings.mentionsEnabled)),
bugReportService: bugReportService,
navigationStackCoordinator: detailNavigationStackCoordinator,
selectedRoomPublisher: selectedRoomSubject.asCurrentValuePublisher())

View File

@ -1,4 +1,4 @@
// Generated using Sourcery 2.0.3 https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.1.1 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable all

View File

@ -23,6 +23,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
private let temporaryCodeBlockMarkingColor = UIColor.cyan
private let linkColor = UIColor.blue
private let permalinkBaseURL: URL
private let mentionBuilder: MentionBuilderProtocol
private static var cache = LRUCache<String, AttributedString>(countLimit: 1000)
@ -30,8 +31,9 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
cache.removeAllValues()
}
init(permalinkBaseURL: URL) {
init(permalinkBaseURL: URL, mentionBuilder: MentionBuilderProtocol) {
self.permalinkBaseURL = permalinkBaseURL
self.mentionBuilder = mentionBuilder
}
func fromPlain(_ string: String?) -> AttributedString? {
@ -45,6 +47,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
let mutableAttributedString = NSMutableAttributedString(string: string)
addLinks(mutableAttributedString)
detectPermalinks(mutableAttributedString)
removeLinkColors(mutableAttributedString)
let result = try? AttributedString(mutableAttributedString, including: \.elementX)
@ -97,10 +100,10 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
removeDefaultForegroundColor(mutableAttributedString)
addLinks(mutableAttributedString)
detectPermalinks(mutableAttributedString)
removeLinkColors(mutableAttributedString)
replaceMarkedBlockquotes(mutableAttributedString)
replaceMarkedCodeBlocks(mutableAttributedString)
detectPermalinks(mutableAttributedString)
removeLinkColors(mutableAttributedString)
removeDTCoreTextArtifacts(mutableAttributedString)
let result = try? AttributedString(mutableAttributedString, including: \.elementX)
@ -140,6 +143,8 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if let value = value as? UIColor,
value == temporaryCodeBlockMarkingColor {
attributedString.addAttribute(.backgroundColor, value: UIColor(.compound._bgCodeBlock) as Any, range: range)
// Codeblocks should not have links
attributedString.removeAttribute(.link, range: range)
}
}
}
@ -177,7 +182,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
guard let matchRange = Range(match.range, in: string) else {
return
}
var hasLink = false
attributedString.enumerateAttribute(.link, in: match.range, options: []) { value, _, stop in
if value != nil {
@ -196,7 +201,9 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
link.insert(contentsOf: "https://", at: link.startIndex)
}
attributedString.addAttribute(.link, value: link as Any, range: match.range)
if let url = URL(string: link) {
attributedString.addAttribute(.link, value: url, range: match.range)
}
}
}
@ -206,7 +213,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if let url = value as? URL {
switch PermalinkBuilder.detectPermalink(in: url, baseURL: permalinkBaseURL) {
case .userIdentifier(let identifier):
attributedString.addAttributes([.MatrixUserID: identifier], range: range)
mentionBuilder.handleUserMention(for: attributedString, in: range, url: url, userID: identifier)
case .roomIdentifier(let identifier):
attributedString.addAttributes([.MatrixRoomID: identifier], range: range)
case .roomAlias(let alias):
@ -280,3 +287,7 @@ extension NSAttributedString.Key {
static let MatrixRoomAlias: NSAttributedString.Key = .init(rawValue: RoomAliasAttribute.name)
static let MatrixEventID: NSAttributedString.Key = .init(rawValue: EventIDAttribute.name)
}
protocol MentionBuilderProtocol {
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String)
}

View File

@ -0,0 +1,49 @@
//
// 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 UIKit
struct MentionBuilder: MentionBuilderProtocol {
// Can be removed when mentions are enabled by default
let mentionsEnabled: Bool
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String) {
guard mentionsEnabled else {
attributedString.addAttributes([.MatrixUserID: userID], range: range)
return
}
let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range)
let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body)
let blockquote = attributes[.MatrixBlockquote]
let attachmentData = PillTextAttachmentData(type: .user(userID: userID), font: font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
attributedString.addAttributes([.MatrixUserID: userID], range: range)
return
}
var attributesToAdd: [NSAttributedString.Key: Any] = [.link: url, .MatrixUserID: userID]
if let blockquote {
// mentions can be in blockquotes, so if the replaced string was in one, we keep the attribute
attributesToAdd[.MatrixBlockquote] = blockquote
}
let attachmentString = NSMutableAttributedString(attachment: attachment)
attachmentString.addAttributes(attributes, range: NSRange(location: 0, length: attachmentString.length))
attributedString.replaceCharacters(in: range, with: attachmentString)
}
}

View File

@ -15,21 +15,49 @@
//
import SwiftUI
import UIKit
final class MessageTextView: UITextView {
var roomContext: RoomScreenViewModel.Context?
var updateClosure: (() -> Void)?
// This prevents the magnifying glass from showing up
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
gestureRecognizer as? UILongPressGestureRecognizer == nil
}
func invalidateTextAttachmentsDisplay(update: Bool) {
attributedText.enumerateAttribute(.attachment,
in: NSRange(location: 0, length: attributedText.length),
options: []) { value, range, _ in
guard value != nil else {
return
}
self.layoutManager.invalidateDisplay(forCharacterRange: range)
if update {
updateClosure?()
}
}
}
// Required to setup the first rendering of the pill view
override func layoutSubviews() {
invalidateTextAttachmentsDisplay(update: false)
super.layoutSubviews()
}
}
struct MessageText: UIViewRepresentable {
@Environment(\.openURL) private var openURLAction: OpenURLAction
let attributedString: AttributedString
@EnvironmentObject private var viewModel: RoomScreenViewModel.Context
@State var attributedString: AttributedString
func makeUIView(context: Context) -> MessageTextView {
let textView = MessageTextView()
// Need to use TextKit 1 for mentions
let textView = MessageTextView(usingTextLayoutManager: false)
textView.roomContext = viewModel
textView.updateClosure = {
attributedString = AttributedString(textView.attributedText)
}
textView.isEditable = false
textView.isScrollEnabled = false
textView.adjustsFontForContentSizeCategory = true
@ -46,7 +74,7 @@ struct MessageText: UIViewRepresentable {
textView.contentInsetAdjustmentBehavior = .never
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.textLayoutManager?.usesFontLeading = false
textView.layoutManager.usesFontLeading = false
textView.backgroundColor = .clear
textView.attributedText = NSAttributedString(attributedString)
textView.delegate = context.coordinator
@ -94,9 +122,20 @@ struct MessageText_Previews: PreviewProvider, TestablePreview {
container.font = UIFont.preferredFont(forTextStyle: .body)
return container
}()
private static let attributedString = AttributedString("Hello World! Hello world! Hello world! Hello world! Hello World! Hellooooooooooooooooooooooo Woooooooooooooooooooooorld", attributes: defaultFontContainer)
private static let attributedStringWithAttachment = "Hello " + AttributedString(NSAttributedString(attachment: PillTextAttachment(data: Data(), ofType: InfoPlistReader.main.pillsUTType))) + " World!"
private static let attributedStringWithAttachment: AttributedString = {
let testData = PillTextAttachmentData(type: .user(userID: "@alice:example.com"), font: .preferredFont(forTextStyle: .body))
guard let attachment = PillTextAttachment(attachmentData: testData) else {
return AttributedString()
}
var attributedString = "Hello test test test " + AttributedString(NSAttributedString(attachment: attachment)) + " World!"
attributedString
.mergeAttributes(defaultFontContainer)
return attributedString
}()
private static let htmlStringWithQuote =
"""
@ -106,28 +145,36 @@ struct MessageText_Previews: PreviewProvider, TestablePreview {
private static let htmlStringWithList = "<p>This is a list</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n<li>And number 3</li>\n</ul>\n"
private static let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL)
private static let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: true))
static var attachmentPreview: some View {
MessageText(attributedString: attributedStringWithAttachment)
.border(Color.purple)
.environmentObject(RoomScreenViewModel.mock.context)
}
static var previews: some View {
MessageText(attributedString: attributedString)
.border(Color.purple)
.previewDisplayName("Custom Text")
.environmentObject(RoomScreenViewModel.mock.context)
// For comparison
Text(attributedString)
.border(Color.purple)
.previewDisplayName("SwiftUI Default Text")
MessageText(attributedString: attributedStringWithAttachment)
.border(Color.purple)
attachmentPreview
.previewDisplayName("Custom Attachment")
if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithQuote) {
MessageText(attributedString: attributedString)
.border(Color.purple)
.previewDisplayName("With block quote")
.environmentObject(RoomScreenViewModel.mock.context)
}
if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithList) {
MessageText(attributedString: attributedString)
.border(Color.purple)
.previewDisplayName("With list")
.environmentObject(RoomScreenViewModel.mock.context)
}
}
}

View File

@ -15,21 +15,52 @@
//
import SwiftUI
import SwiftUIIntrospect
import UIKit
final class PillAttachmentViewProvider: NSTextAttachmentViewProvider {
private weak var messageTextView: MessageTextView?
// MARK: - Override
override init(textAttachment: NSTextAttachment, parentView: UIView?, textLayoutManager: NSTextLayoutManager?, location: NSTextLocation) {
super.init(textAttachment: textAttachment, parentView: parentView, textLayoutManager: textLayoutManager, location: location)
// Keep a reference to the parent text view for size adjustments and pills flushing.
messageTextView = parentView?.superview as? MessageTextView
tracksTextAttachmentViewBounds = true
}
@MainActor
override func loadView() {
super.loadView()
guard textAttachment is PillTextAttachment else {
MXLog.failure("[PillAttachmentViewProvider]: attachment is missing or not of expected class")
guard let textAttachmentData = (textAttachment as? PillTextAttachment)?.pillData else {
MXLog.failure("[PillAttachmentViewProvider]: attachment is missing data or not of expected class")
return
}
let view = PillView()
let viewModel: PillContext
let imageProvider: ImageProviderProtocol?
if ProcessInfo.isXcodePreview || ProcessInfo.isRunningTests {
// The mock viewModel simulates the loading logic for testing purposes
viewModel = PillContext.mock(type: .loadUser)
imageProvider = MockMediaProvider()
} else if let roomContext = messageTextView?.roomContext {
viewModel = PillContext(roomContext: roomContext, data: textAttachmentData)
imageProvider = roomContext.imageProvider
} else {
MXLog.failure("[PillAttachmentViewProvider]: missing room context")
return
}
let view = PillView(imageProvider: imageProvider, viewModel: viewModel) { [weak self] in
self?.messageTextView?.invalidateTextAttachmentsDisplay(update: true)
}
let controller = UIHostingController(rootView: view)
controller.view.backgroundColor = .clear
// This allows the text view to handle it as a link
controller.view.isUserInteractionEnabled = false
self.view = controller.view
}
}

View File

@ -0,0 +1,110 @@
//
// 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
@MainActor
final class PillContext: ObservableObject {
enum PillViewState {
case loading(contentID: String)
case loaded(contentID: String, name: String, avatarURL: URL?)
}
@Published private var state: PillViewState
var url: URL? {
switch state {
case .loading:
return nil
case .loaded(_, _, let url):
return url
}
}
var name: String? {
switch state {
case .loading:
return nil
case .loaded(_, let name, _):
return name
}
}
var displayText: String {
switch state {
case .loaded(_, let name, _):
return name
case .loading(let contentID):
return contentID
}
}
var contentID: String {
switch state {
case .loaded(let contentID, _, _), .loading(let contentID):
return contentID
}
}
private var cancellable: AnyCancellable?
init(roomContext: RoomScreenViewModel.Context, data: PillTextAttachmentData) {
switch data.type {
case let .user(id):
if let profile = roomContext.viewState.members[id] {
state = .loaded(contentID: id, name: profile.displayName ?? id, avatarURL: profile.avatarURL)
} else {
state = .loading(contentID: id)
cancellable = roomContext.$viewState.sink { [weak self] viewState in
guard let self else {
return
}
if let profile = viewState.members[id] {
state = .loaded(contentID: id, name: profile.displayName ?? id, avatarURL: profile.avatarURL)
cancellable = nil
}
}
}
}
}
}
extension PillContext {
enum MockType {
case loadUser
case loadedUser
}
static func mock(type: MockType) -> PillContext {
let pillType: PillType
switch type {
case .loadUser:
pillType = .user(userID: "@test:test.com")
let viewModel = PillContext(roomContext: RoomScreenViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
Task {
try? await Task.sleep(for: .seconds(2))
viewModel.state = .loaded(contentID: "@test:test.com", name: "Test Longer Display Text", avatarURL: URL.documentsDirectory)
}
return viewModel
case .loadedUser:
pillType = .user(userID: "@test:test.com")
let viewModel = PillContext(roomContext: RoomScreenViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
viewModel.state = .loaded(contentID: "@test:test.com", name: "Very Very Long Test Display Text", avatarURL: URL.documentsDirectory)
return viewModel
}
}
}

View File

@ -17,4 +17,27 @@
import UIKit
/// Text attachment for pills display.
final class PillTextAttachment: NSTextAttachment { }
final class PillTextAttachment: NSTextAttachment {
convenience init?(attachmentData: PillTextAttachmentData) {
let encoder = JSONEncoder()
guard let encodedData = try? encoder.encode(attachmentData) else { return nil }
self.init(data: encodedData, ofType: InfoPlistReader.main.pillsUTType)
}
var pillData: PillTextAttachmentData? {
guard let contents else {
return nil
}
let decoder = JSONDecoder()
return try? decoder.decode(PillTextAttachmentData.self, from: contents)
}
override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
var rect = super.attachmentBounds(for: textContainer, proposedLineFragment: lineFrag, glyphPosition: position, characterIndex: charIndex)
if let font = pillData?.font {
// Align the pill text vertically with the surrounding text.
rect.origin.y = font.descender + (font.lineHeight - rect.height) / 2.0
}
return rect
}
}

View File

@ -0,0 +1,64 @@
//
// 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 UIKit
enum PillType: Codable {
/// A pill that mentions a user
case user(userID: String)
}
struct PillTextAttachmentData {
// MARK: - Properties
/// Pill type
let type: PillType
/// Font for the display name
let font: UIFont
}
extension PillTextAttachmentData: Codable {
// MARK: - Codable
enum CodingKeys: String, CodingKey {
case type
case font
}
enum PillTextAttachmentDataError: Error {
case noFontData
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(PillType.self, forKey: .type)
let fontData = try container.decode(Data.self, forKey: .font)
if let font = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIFont.self, from: fontData) {
self.font = font
} else {
throw PillTextAttachmentDataError.noFontData
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
let fontData = try NSKeyedArchiver.archivedData(withRootObject: font, requiringSecureCoding: false)
try container.encode(fontData, forKey: .font)
}
}

View File

@ -17,21 +17,43 @@
import SwiftUI
struct PillView: View {
let imageProvider: ImageProviderProtocol?
@ObservedObject var viewModel: PillContext
/// callback triggerd by changes in the display text
let didChangeText: () -> Void
var body: some View {
Button {
MXLog.info("TEXT ATTACHMENT TEST")
} label: {
HStack {
Image(asset: Asset.Images.appLogo)
.resizable()
.scaledToFit()
}
HStack(spacing: 4) {
LoadableAvatarImage(url: viewModel.url, name: viewModel.name, contentID: viewModel.contentID, avatarSize: .custom(24), imageProvider: imageProvider)
Text(viewModel.displayText)
.font(.compound.bodyLGSemibold)
.foregroundColor(.compound.textOnSolidPrimary)
.lineLimit(1)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
// for now design has defined no color so we will just use gray
.background(Capsule().foregroundColor(.gray))
.frame(maxWidth: 235)
.onChange(of: viewModel.displayText) { _ in
didChangeText()
}
}
}
struct PillView_Previews: PreviewProvider, TestablePreview {
static let mockMediaProvider = MockMediaProvider()
static var loading: some View {
PillView(imageProvider: mockMediaProvider,
viewModel: PillContext.mock(type: .loadUser)) { }
}
static var previews: some View {
PillView()
loading
.previewDisplayName("Loading")
PillView(imageProvider: mockMediaProvider,
viewModel: PillContext.mock(type: .loadedUser)) { }
.previewDisplayName("Loaded Long")
}
}

View File

@ -16,11 +16,11 @@
import Foundation
public enum Tests {
extension ProcessInfo {
/// Flag indicating whether the app is running the unit tests.
static var isRunningUnitTests: Bool {
#if DEBUG
ProcessInfo.processInfo.environment["IS_RUNNING_UNIT_TESTS"] == "1"
processInfo.environment["IS_RUNNING_UNIT_TESTS"] == "1"
#else
false
#endif
@ -29,7 +29,7 @@ public enum Tests {
/// Flag indicating whether the app is running the UI tests.
static var isRunningUITests: Bool {
#if DEBUG
ProcessInfo.processInfo.environment["UI_TESTS_SCREEN"] != nil
processInfo.environment["UI_TESTS_SCREEN"] != nil
#else
false
#endif
@ -41,9 +41,9 @@ public enum Tests {
}
/// The identifier of the screen to be loaded when running UI tests.
static var screenID: UITestsScreenIdentifier? {
static var testScreenID: UITestsScreenIdentifier? {
#if DEBUG
ProcessInfo.processInfo.environment["UI_TESTS_SCREEN"].flatMap(UITestsScreenIdentifier.init)
processInfo.environment["UI_TESTS_SCREEN"].flatMap(UITestsScreenIdentifier.init)
#else
nil
#endif
@ -55,9 +55,17 @@ public enum Tests {
}
#if DEBUG
return ProcessInfo.processInfo.environment["UI_TESTS_DISABLE_TIMELINE_ACCESSIBILITY"] != nil
return processInfo.environment["UI_TESTS_DISABLE_TIMELINE_ACCESSIBILITY"] != nil
#else
return false
#endif
}
static var isXcodePreview: Bool {
#if DEBUG
processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
#else
false
#endif
}
}

View File

@ -23,13 +23,13 @@ public extension Animation {
/// `noAnimation` if running tests, otherwise `default` animation if `UIAccessibility.isReduceMotionEnabled` is false
static var elementDefault: Animation {
let animation: Animation = Tests.isRunningTests ? .noAnimation : .default
let animation: Animation = ProcessInfo.isRunningTests ? .noAnimation : .default
return animation.disabledIfReduceMotionEnabled()
}
// `noAnimation` if running tests, otherwise `self` if `UIAccessibility.isReduceMotionEnabled` is false
func disabledDuringTests() -> Self {
let animation: Animation = Tests.isRunningTests ? .noAnimation : self
let animation: Animation = ProcessInfo.isRunningTests ? .noAnimation : self
return animation.disabledIfReduceMotionEnabled()
}

View File

@ -78,7 +78,8 @@ extension View {
struct ShimmerOverlay_Previews: PreviewProvider, TestablePreview {
static let viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: MockClientProxy(userID: ""),
mediaProvider: MockMediaProvider()),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL,
mentionBuilder: MentionBuilder(mentionsEnabled: ServiceLocator.shared.settings.mentionsEnabled)),
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,

View File

@ -338,7 +338,7 @@ struct HomeScreen_Previews: PreviewProvider, TestablePreview {
mediaProvider: MockMediaProvider())
return HomeScreenViewModel(userSession: userSession,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: ServiceLocator.shared.settings.mentionsEnabled)),
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,

View File

@ -154,7 +154,7 @@ struct HomeScreenEmptyStateView_Previews: PreviewProvider, TestablePreview {
mediaProvider: MockMediaProvider())
return HomeScreenViewModel(userSession: userSession,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: ServiceLocator.shared.settings.mentionsEnabled)),
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,

View File

@ -188,7 +188,8 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
mediaProvider: MockMediaProvider())
let viewModel = HomeScreenViewModel(userSession: userSession,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL,
mentionBuilder: MentionBuilder(mentionsEnabled: ServiceLocator.shared.settings.mentionsEnabled)),
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,

View File

@ -201,7 +201,7 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview {
"<p>This is a list</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n<li>And number 3</li>\n</ul>\n"
]
let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL)
let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: ServiceLocator.shared.settings.mentionsEnabled))
ScrollView {
VStack(alignment: .leading, spacing: 24.0) {
@ -234,6 +234,7 @@ private struct PreviewBubbleModifier: ViewModifier {
.padding(timelineStyle == .bubbles ? 8 : 0)
.background(timelineStyle == .bubbles ? Color.compound._bgBubbleOutgoing : nil)
.cornerRadius(timelineStyle == .bubbles ? 12 : 0)
.environmentObject(RoomScreenViewModel.mock.context)
}
}

View File

@ -104,7 +104,7 @@ class TimelineTableViewController: UIViewController {
// Prevents XCUITest from invoking the diffable dataSource's cellProvider
// for each possible cell, causing layout issues
tableView.accessibilityElementsHidden = Tests.shouldDisableTimelineAccessibility
tableView.accessibilityElementsHidden = ProcessInfo.shouldDisableTimelineAccessibility
scrollToBottomPublisher
.sink { [weak self] _ in

View File

@ -50,6 +50,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
var readReceiptsEnabled: Bool { get set }
var swiftUITimelineEnabled: Bool { get set }
var voiceMessageEnabled: Bool { get set }
var mentionsEnabled: Bool { get set }
}
extension AppSettings: DeveloperOptionsProtocol { }

View File

@ -45,6 +45,11 @@ struct DeveloperOptionsScreen: View {
Text("SwiftUI Timeline")
Text("Resets on reboot")
}
Toggle(isOn: $context.mentionsEnabled) {
Text("Show user mentions")
Text("Requires app reboot")
}
}
Section("Room creation") {

View File

@ -443,7 +443,8 @@ class ClientProxy: ClientProxyProtocol {
.finish()
let roomListService = syncService.roomListService()
let roomMessageEventStringBuilder = RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .homeDirectory))
let roomMessageEventStringBuilder = RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL,
mentionBuilder: MentionBuilder(mentionsEnabled: appSettings.mentionsEnabled)))
let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID),
messageEventStringBuilder: roomMessageEventStringBuilder)
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,

View File

@ -97,7 +97,9 @@ enum RoomTimelineItemFixtures {
isThreaded: false,
sender: .init(id: "", displayName: "Helena"),
content: .init(body: "",
formattedBody: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL).fromHTML("Hol' up <blockquote>New home office set up!</blockquote>That's amazing! Congrats 🥳")))
formattedBody: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL,
mentionBuilder: MentionBuilder(mentionsEnabled: ServiceLocator.shared.settings.mentionsEnabled))
.fromHTML("Hol' up <blockquote>New home office set up!</blockquote>That's amazing! Congrats 🥳")))
]
/// A small chunk of events, containing 2 text items.

View File

@ -50,7 +50,7 @@ class UITestsAppCoordinator: AppCoordinatorProtocol {
delegate.window??.layer.speed = 0
}
guard let screenID = Tests.screenID else { fatalError("Unable to launch with unknown screen.") }
guard let screenID = ProcessInfo.testScreenID else { fatalError("Unable to launch with unknown screen.") }
let mockScreen = MockScreen(id: screenID)
navigationRootCoordinator.setRootCoordinator(mockScreen.coordinator)
@ -162,7 +162,7 @@ class MockScreen: Identifiable {
let session = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:matrix.org"),
mediaProvider: MockMediaProvider())
let coordinator = HomeScreenCoordinator(parameters: .init(userSession: session,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: true)),
bugReportService: BugReportServiceMock(),
navigationStackCoordinator: navigationStackCoordinator,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher()))

View File

@ -20,7 +20,7 @@ import UserNotifications
class NotificationServiceExtension: UNNotificationServiceExtension {
private let settings = NSESettings()
private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .homeDirectory)))
private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .homeDirectory, mentionBuilder: NSEMentionBuilder())))
private lazy var keychainController = KeychainController(service: .sessions,
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
private var handler: ((UNNotificationContent) -> Void)?

View File

@ -0,0 +1,23 @@
//
// 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
struct NSEMentionBuilder: MentionBuilderProtocol {
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String) {
attributedString.addAttributes([.MatrixUserID: userID], range: range)
}
}

View File

@ -18,7 +18,7 @@
import XCTest
class AttributedStringBuilderTests: XCTestCase {
let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL)
let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: true))
let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2)
func testRenderHTMLStringWithHeaders() {
@ -413,6 +413,14 @@ class AttributedStringBuilderTests: XCTestCase {
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
}
func testUserMentionAtachment() {
let string = "https://matrix.to/#/@test:matrix.org"
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
XCTAssertNotNil(attributedStringFromHTML?.attachment)
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
XCTAssert(attributedStringFromPlain?.attachment.isNil == false)
}
// MARK: - Private
private func checkLinkIn(attributedString: AttributedString?, expectedLink: String, expectedRuns: Int) {

View File

@ -21,7 +21,7 @@ class AttributedStringTests: XCTestCase {
func testReplacingFontWithPresentationIntent() {
// Given a string parsed from HTML that contains specific fixed size fonts.
let boldString = "Bold"
guard let originalString = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL)
guard let originalString = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: true))
.fromHTML("Normal <b>\(boldString)</b> Normal.") else {
XCTFail("The attributed string should be built from the HTML.")
return

View File

@ -31,7 +31,7 @@ class HomeScreenViewModelTests: XCTestCase {
clientProxy = MockClientProxy(userID: "@mock:client.com")
viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: clientProxy,
mediaProvider: MockMediaProvider()),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: true)),
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
changelog.d/1804.feature Normal file
View File

@ -0,0 +1 @@
User mentions pills (behind a Feature Flag).