diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 308a52bc2..bd6737132 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -102,6 +102,7 @@ 155063E980E763D4910EA3CF /* Analytics+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */; }; 1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */; }; 157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */; }; + 1583E2D766E4485FF91662FC /* PermalinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3EB5B1848CF4F64E63C6B7 /* PermalinkTests.swift */; }; 158A2D528CC78C4E7A8ED608 /* MockRoomTimelineControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71556206CD5E8B1F53F07178 /* MockRoomTimelineControllerFactory.swift */; }; 167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */; }; 16CBD087038DE3815CDA512C /* PollMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D38391154120264910D19528 /* PollMock.swift */; }; @@ -148,7 +149,6 @@ 22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; }; 22C5483D01EEB290B8339817 /* HomeScreenInviteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FC33C3F6BF597E095CE9FA /* HomeScreenInviteCell.swift */; }; 2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */; }; - 234E2C782981003971ABE96E /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; }; 23701DE32ACD6FD40AA992C3 /* MediaUploadingPreprocessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.swift */; }; 237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */; }; 241CDEFE23819867D9B39066 /* RoomChangePermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE75941583A033A9EDC9FE0 /* RoomChangePermissionsScreenViewModel.swift */; }; @@ -169,7 +169,6 @@ 274CE3C986841D15FD530BF5 /* ShimmerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97CE98208321C4D66E363612 /* ShimmerModifier.swift */; }; 275EDE8849A2AC1D9309ED7C /* TemplateScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */; }; 2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; }; - 27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */; }; 27F015B0D5436633B5B3C8C3 /* SecureBackupRecoveryKeyScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */; }; 2814E7075BF3A5C0CCBC9F90 /* RoomDirectorySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */; }; 281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; }; @@ -539,7 +538,6 @@ 7FF6E1FBE6E9517FD29A1D8E /* RoomChangeRolesScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48A5C34C4E4268EF65D171EF /* RoomChangeRolesScreenModels.swift */; }; 8015842CB4DE1BE414D2CDED /* AppLockSetupBiometricsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C62E07C1164F5120727A2A8 /* AppLockSetupBiometricsScreenCoordinator.swift */; }; 804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; }; - 80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; }; 80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */; }; 81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36C0A6D59717193F49EA986 /* UserSessionTests.swift */; }; 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; }; @@ -1569,7 +1567,6 @@ 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = ""; }; 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = ""; }; 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelTests.swift; sourceTree = ""; }; - 6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermalinkBuilderTests.swift; sourceTree = ""; }; 6FC5015B9634698BDB8701AF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = ""; }; 7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = ""; }; 7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenViewModel.swift; sourceTree = ""; }; @@ -2120,7 +2117,6 @@ F733F135E6D67BBBEB76CC30 /* AppLockUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockUITests.swift; sourceTree = ""; }; F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = ""; }; F7478623CECC9438014244BA /* ServerConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreen.swift; sourceTree = ""; }; - F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermalinkBuilder.swift; sourceTree = ""; }; F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProviderProtocol.swift; sourceTree = ""; }; F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRootCoordinatorTests.swift; sourceTree = ""; }; F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionScreenTests.swift; sourceTree = ""; }; @@ -2129,6 +2125,7 @@ F9E543072DE58E751F028998 /* TimelineProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxy.swift; sourceTree = ""; }; F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = ""; }; F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineView.swift; sourceTree = ""; }; + FA3EB5B1848CF4F64E63C6B7 /* PermalinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermalinkTests.swift; sourceTree = ""; }; FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = ""; }; FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreen.swift; sourceTree = ""; }; FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModel.swift; sourceTree = ""; }; @@ -3586,7 +3583,7 @@ 9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */, 8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */, 514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */, - 6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */, + FA3EB5B1848CF4F64E63C6B7 /* PermalinkTests.swift */, 31A6314FDC51DA25712D9A81 /* PillContextTests.swift */, 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */, 25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */, @@ -4581,7 +4578,6 @@ 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */, 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */, 10B7F8EE25775DE2A305CBB5 /* NotificationCenterProtocol.swift */, - F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */, B3A1398EFF65090FDA1CB639 /* ProcessInfo.swift */, 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */, 4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */, @@ -5691,7 +5687,6 @@ 5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */, B89990DD875B0B603D4D4332 /* NotificationItemProxyProtocol.swift in Sources */, B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */, - 234E2C782981003971ABE96E /* PermalinkBuilder.swift in Sources */, 62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */, 55CDD3968D95D1A820B5491E /* PlaceholderAvatarImage.swift in Sources */, F12F6BED7B6D7EE4BEE55039 /* PlainMentionBuilder.swift in Sources */, @@ -5782,7 +5777,7 @@ C11939FDC40716C4387275A4 /* NotificationSettingsEditScreenViewModelTests.swift in Sources */, E3AC72E3E58F364EF15C1CC7 /* NotificationSettingsScreenViewModelTests.swift in Sources */, 50381244BA280451771BE3ED /* PINTextFieldTests.swift in Sources */, - 27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */, + 1583E2D766E4485FF91662FC /* PermalinkTests.swift in Sources */, 3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */, FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */, D415764645491F10344FC6AC /* Publisher.swift in Sources */, @@ -6237,7 +6232,6 @@ 847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */, 7501442D52A65F73DF79FFD4 /* PaginationIndicatorRoomTimelineItem.swift in Sources */, 764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */, - 80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */, 962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */, EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */, 899359A4D1147601F6C4E364 /* PillConstants.swift in Sources */, diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 2c8a43fe8..47d2c9351 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -289,8 +289,6 @@ final class AppSettings { #endif // MARK: - Shared - - let permalinkBaseURL: URL = "https://matrix.to" @UserPreference(key: UserDefaultsKeys.logLevel, defaultValue: TracingConfiguration.LogLevel.info, storageType: .userDefaults(store)) var logLevel diff --git a/ElementX/Sources/Application/Navigation/AppRoutes.swift b/ElementX/Sources/Application/Navigation/AppRoutes.swift index bcc313ad0..67c55b932 100644 --- a/ElementX/Sources/Application/Navigation/AppRoutes.swift +++ b/ElementX/Sources/Application/Navigation/AppRoutes.swift @@ -15,6 +15,7 @@ // import Foundation +import MatrixRustSDK enum AppRoute: Equatable { /// The callback used to complete login with OIDC. @@ -43,7 +44,7 @@ struct AppRouteURLParser { init(appSettings: AppSettings) { urlParsers = [ - MatrixPermalinkParser(appSettings: appSettings), + MatrixPermalinkParser(), OIDCCallbackURLParser(appSettings: appSettings), ElementCallURLParser() ] @@ -121,13 +122,15 @@ struct ElementCallURLParser: URLParser { } struct MatrixPermalinkParser: URLParser { - let appSettings: AppSettings - func route(from url: URL) -> AppRoute? { - switch PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL) { - case .userIdentifier(let userID): + guard let matrixEntity = parseMatrixEntityFrom(uri: url.absoluteString) else { + return nil + } + + switch matrixEntity.id { + case .user(let userID): return .roomMemberDetails(userID: userID) - case .roomIdentifier(let roomID): + case .room(let roomID): return .room(roomID: roomID) default: return nil diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index a789f5854..23b1614e6 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -421,8 +421,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let userID = userSession.clientProxy.userID let timelineItemFactory = RoomTimelineItemFactory(userID: userID, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL, - mentionBuilder: MentionBuilder()), + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy, @@ -509,8 +508,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { analyticsService: analytics, userIndicatorController: userIndicatorController, notificationSettings: userSession.clientProxy.notificationSettings, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) let coordinator = RoomDetailsScreenCoordinator(parameters: params) coordinator.actions.sink { [weak self] action in guard let self else { return } @@ -846,8 +844,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let userID = userSession.clientProxy.userID let timelineItemFactory = RoomTimelineItemFactory(userID: userID, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL, - mentionBuilder: MentionBuilder()), + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy, diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 6b56584df..5e4d73a8a 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -326,8 +326,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private func presentHomeScreen() { let parameters = HomeScreenCoordinatorParameters(userSession: userSession, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder()), + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), bugReportService: bugReportService, navigationStackCoordinator: detailNavigationStackCoordinator, selectedRoomPublisher: selectedRoomSubject.asCurrentValuePublisher()) diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift index bb417fb33..c8e2ef8d0 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift @@ -17,12 +17,12 @@ import DTCoreText import Foundation import LRUCache +import MatrixRustSDK struct AttributedStringBuilder: AttributedStringBuilderProtocol { private let cacheKey: String private let temporaryBlockquoteMarkingColor = UIColor.magenta private let temporaryCodeBlockMarkingColor = UIColor.cyan - private let permalinkBaseURL: URL private let mentionBuilder: MentionBuilderProtocol private static let defaultKey = "default" @@ -34,9 +34,8 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { caches.removeAll() } - init(cacheKey: String = defaultKey, permalinkBaseURL: URL, mentionBuilder: MentionBuilderProtocol) { + init(cacheKey: String = defaultKey, mentionBuilder: MentionBuilderProtocol) { self.cacheKey = cacheKey - self.permalinkBaseURL = permalinkBaseURL self.mentionBuilder = mentionBuilder } @@ -206,29 +205,45 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { private func addLinksAndMentions(_ attributedString: NSMutableAttributedString) { let string = attributedString.string - var matches = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .permalink(type: .userID)) } - matches.append(contentsOf: MatrixEntityRegex.roomIdentifierRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .permalink(type: .roomID)) }) + // Event identifiers and room aliases and identifiers detected in plain text are techincally incomplete + // without via parameters and we won't bother detecting them - // As of right now we do not handle event id links in any way so there is no need to add them as links - // matches.append(contentsOf: MatrixEntityRegex.eventIdentifierRegex.matches(in: string, options: [])) + var matches: [TextParsingMatch] = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: []).compactMap { match in + guard let matchRange = Range(match.range, in: string) else { + return nil + } + + let identifier = String(string[matchRange]) + + return TextParsingMatch(type: .userID(identifier: identifier), range: match.range) + } - matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .permalink(type: .roomAlias)) }) + matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: []).compactMap { match in + guard let matchRange = Range(match.range, in: string) else { + return nil + } + + var link = String(string[matchRange]) + + if !link.contains("://") { + link.insert(contentsOf: "https://", at: link.startIndex) + } + + return TextParsingMatch(type: .link(urlString: link), range: match.range) + }) - matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .link) }) - - matches.append(contentsOf: MatrixEntityRegex.allUsersRegex.matches(in: attributedString.string, options: []).map { TypedMatch(match: $0, type: .atRoom) }) + matches.append(contentsOf: MatrixEntityRegex.allUsersRegex.matches(in: attributedString.string, options: []).map { match in + TextParsingMatch(type: .atRoom, range: match.range) + }) guard matches.count > 0 else { return } + // Sort the links by length so the longest one always takes priority - matches.sorted { $0.match.range.length > $1.match.range.length }.forEach { [attributedString] typedMatch in - guard let matchRange = Range(typedMatch.match.range, in: string) else { - return - } - + matches.sorted { $0.range.length > $1.range.length }.forEach { [attributedString] match in var hasLink = false - attributedString.enumerateAttribute(.link, in: typedMatch.match.range, options: []) { value, _, stop in + attributedString.enumerateAttribute(.link, in: match.range, options: []) { value, _, stop in if value != nil { hasLink = true stop.pointee = true @@ -239,24 +254,12 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { return } - switch typedMatch.type { + switch match.type { case .atRoom: - attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: typedMatch.match.range) - case let .permalink(type): - let identifier = String(string[matchRange]) - - if let url = type.getPermalinkFrom(identifier: identifier, baseURL: permalinkBaseURL) { - attributedString.addAttribute(.link, value: url, range: typedMatch.match.range) - } - case .link: - var link = String(string[matchRange]) - - if !link.contains("://") { - link.insert(contentsOf: "https://", at: link.startIndex) - } - - if let url = URL(string: link) { - attributedString.addAttribute(.link, value: url, range: typedMatch.match.range) + attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: match.range) + case .userID, .link: + if let url = match.link { + attributedString.addAttribute(.link, value: url, range: match.range) } } } @@ -265,18 +268,18 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { func detectPermalinks(_ attributedString: NSMutableAttributedString) { attributedString.enumerateAttribute(.link, in: .init(location: 0, length: attributedString.length), options: []) { value, range, _ in if value != nil { - if let url = value as? URL { - switch PermalinkBuilder.detectPermalink(in: url, baseURL: permalinkBaseURL) { - case .userIdentifier(let identifier): - mentionBuilder.handleUserMention(for: attributedString, in: range, url: url, userID: identifier) - case .roomIdentifier(let identifier): - attributedString.addAttributes([.MatrixRoomID: identifier], range: range) + if let url = value as? URL, let matrixEntity = parseMatrixEntityFrom(uri: url.absoluteString) { + switch matrixEntity.id { + case .user(let userID): + mentionBuilder.handleUserMention(for: attributedString, in: range, url: url, userID: userID) + case .room(let roomID): + attributedString.addAttributes([.MatrixRoomID: roomID], range: range) case .roomAlias(let alias): attributedString.addAttributes([.MatrixRoomAlias: alias], range: range) - case .event(let roomIdentifier, let eventIdentifier): - attributedString.addAttributes([.MatrixEventID: EventIDAttributeValue(roomID: roomIdentifier, eventID: eventIdentifier)], range: range) - case .none: - break + case .eventOnRoomId(let roomID, let eventID): + attributedString.addAttributes([.MatrixEventOnRoomID: EventOnRoomIDAttribute.Value(roomID: roomID, eventID: eventID)], range: range) + case .eventOnRoomAlias(let alias, let eventID): + attributedString.addAttributes([.MatrixEventOnRoomAlias: EventOnRoomAliasAttribute.Value(alias: alias, eventID: eventID)], range: range) } } } @@ -347,7 +350,8 @@ extension NSAttributedString.Key { static let MatrixUserID: NSAttributedString.Key = .init(rawValue: UserIDAttribute.name) static let MatrixRoomID: NSAttributedString.Key = .init(rawValue: RoomIDAttribute.name) static let MatrixRoomAlias: NSAttributedString.Key = .init(rawValue: RoomAliasAttribute.name) - static let MatrixEventID: NSAttributedString.Key = .init(rawValue: EventIDAttribute.name) + static let MatrixEventOnRoomID: NSAttributedString.Key = .init(rawValue: EventOnRoomIDAttribute.name) + static let MatrixEventOnRoomAlias: NSAttributedString.Key = .init(rawValue: EventOnRoomAliasAttribute.name) static let MatrixAllUsersMention: NSAttributedString.Key = .init(rawValue: AllUsersMentionAttribute.name) } @@ -356,30 +360,24 @@ protocol MentionBuilderProtocol { func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) } -private struct TypedMatch { +private struct TextParsingMatch { enum MatchType { - case permalink(type: MentionType) - case link + case userID(identifier: String) + case link(urlString: String) case atRoom } - enum MentionType { - case roomAlias - case roomID - case userID - - func getPermalinkFrom(identifier: String, baseURL: URL) -> URL? { - switch self { - case .roomAlias: - return try? PermalinkBuilder.permalinkTo(roomAlias: identifier, baseURL: baseURL) - case .roomID: - return try? PermalinkBuilder.permalinkTo(roomIdentifier: identifier, baseURL: baseURL) - case .userID: - return try? PermalinkBuilder.permalinkTo(userIdentifier: identifier, baseURL: baseURL) - } + let type: MatchType + let range: NSRange + + var link: URL? { + switch type { + case .userID(let identifier): + return try? URL(string: matrixToUserPermalink(userId: identifier)) + case .link(let urlString): + return URL(string: urlString) + default: + return nil } } - - let match: NSTextCheckingResult - let type: MatchType } diff --git a/ElementX/Sources/Other/HTMLParsing/ElementXAttributeScope.swift b/ElementX/Sources/Other/HTMLParsing/ElementXAttributeScope.swift index 9b01508a3..8eff73f53 100644 --- a/ElementX/Sources/Other/HTMLParsing/ElementXAttributeScope.swift +++ b/ElementX/Sources/Other/HTMLParsing/ElementXAttributeScope.swift @@ -36,21 +36,29 @@ enum RoomAliasAttribute: AttributedStringKey { static var name = "MXRoomAliasAttribute" } +enum EventOnRoomIDAttribute: AttributedStringKey { + struct Value: Hashable { + let roomID: String + let eventID: String + } + + static var name = "MXEventOnRoomIDAttribute" +} + +enum EventOnRoomAliasAttribute: AttributedStringKey { + struct Value: Hashable { + let alias: String + let eventID: String + } + + static var name = "MXEventOnRoomAliasAttribute" +} + enum AllUsersMentionAttribute: AttributedStringKey { typealias Value = Bool static var name = "MXAllUsersMentionAttribute" } -struct EventIDAttributeValue: Hashable { - let roomID: String - let eventID: String -} - -enum EventIDAttribute: AttributedStringKey { - typealias Value = EventIDAttributeValue - static var name = "MXEventIDAttribute" -} - // periphery: ignore - required to make NSAttributedString to AttributedString conversion even if not used directly extension AttributeScopes { struct ElementXAttributes: AttributeScope { @@ -59,7 +67,8 @@ extension AttributeScopes { let userID: UserIDAttribute let roomID: RoomIDAttribute let roomAlias: RoomAliasAttribute - let eventID: EventIDAttribute + let eventOnRoomID: EventOnRoomIDAttribute + let eventOnRoomAlias: EventOnRoomAliasAttribute let allUsersMention: AllUsersMentionAttribute diff --git a/ElementX/Sources/Other/PermalinkBuilder.swift b/ElementX/Sources/Other/PermalinkBuilder.swift deleted file mode 100644 index 41e6097e6..000000000 --- a/ElementX/Sources/Other/PermalinkBuilder.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// Copyright 2022 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 - -enum PermalinkBuilderError: Error { - case invalidUserIdentifier - case invalidRoomIdentifier - case invalidRoomAlias - case invalidEventIdentifier - case failedConstructingURL - case failedAddingPercentEncoding -} - -enum PermalinkType: Equatable { - case userIdentifier(String) - case roomIdentifier(String) - case roomAlias(String) - case event(roomIdentifier: String, eventIdentifier: String) -} - -enum PermalinkBuilder { - private static var uriComponentCharacterSet: CharacterSet = { - var charset = CharacterSet.alphanumerics - charset.insert(charactersIn: "-_.!~*'()") - return charset - }() - - static func detectPermalink(in url: URL, baseURL: URL) -> PermalinkType? { - guard url.absoluteString.hasPrefix(baseURL.absoluteString) else { - return nil - } - - guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) else { - return nil - } - - guard var fragment = urlComponents.fragment else { - return nil - } - - if fragment.hasPrefix("/") { - fragment = String(fragment.dropFirst(1)) - } - - if let userIdentifierRange = MatrixEntityRegex.userIdentifierRegex.firstMatch(in: fragment)?.range { - return .userIdentifier((fragment as NSString).substring(with: userIdentifierRange)) - } - - if let roomAliasRange = MatrixEntityRegex.roomAliasRegex.firstMatch(in: fragment)?.range { - return .roomAlias((fragment as NSString).substring(with: roomAliasRange)) - } - - if let roomIdentifierRange = MatrixEntityRegex.roomIdentifierRegex.firstMatch(in: fragment)?.range { - let roomIdentifier = (fragment as NSString).substring(with: roomIdentifierRange) - - if let eventIdentifierRange = MatrixEntityRegex.eventIdentifierRegex.firstMatch(in: fragment)?.range { - let eventIdentifier = (fragment as NSString).substring(with: eventIdentifierRange) - return .event(roomIdentifier: roomIdentifier, eventIdentifier: eventIdentifier) - } - - return .roomIdentifier(roomIdentifier) - } - - return nil - } - - @available(*, deprecated, message: "Use a room's `matrixToPermalink` method instead") - static func permalinkTo(userIdentifier: String, baseURL: URL) throws -> URL { - guard MatrixEntityRegex.isMatrixUserIdentifier(userIdentifier) else { - throw PermalinkBuilderError.invalidUserIdentifier - } - - let urlString = "\(baseURL)/#/\(userIdentifier)" - - guard let url = URL(string: urlString) else { - throw PermalinkBuilderError.failedConstructingURL - } - - return url - } - - @available(*, deprecated, message: "Use a room's `matrixToPermalink` method instead") - static func permalinkTo(roomIdentifier: String, baseURL: URL) throws -> URL { - guard MatrixEntityRegex.isMatrixRoomIdentifier(roomIdentifier) else { - throw PermalinkBuilderError.invalidRoomIdentifier - } - - return try permalinkTo(roomIdentifierOrAlias: roomIdentifier, baseURL: baseURL) - } - - @available(*, deprecated, message: "Use a room's `matrixToPermalink` method instead") - static func permalinkTo(roomAlias: String, baseURL: URL) throws -> URL { - guard MatrixEntityRegex.isMatrixRoomAlias(roomAlias) else { - throw PermalinkBuilderError.invalidRoomAlias - } - - return try permalinkTo(roomIdentifierOrAlias: roomAlias, baseURL: baseURL) - } - - @available(*, deprecated, message: "Use a room's `matrixToEventPermalink` method instead") - static func permalinkTo(eventIdentifier: String, roomIdentifier: String, baseURL: URL) throws -> URL { - guard MatrixEntityRegex.isMatrixEventIdentifier(eventIdentifier) else { - throw PermalinkBuilderError.invalidEventIdentifier - } - guard MatrixEntityRegex.isMatrixRoomIdentifier(roomIdentifier) else { - throw PermalinkBuilderError.invalidRoomIdentifier - } - - guard let roomId = roomIdentifier.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet), - let eventId = eventIdentifier.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet) else { - throw PermalinkBuilderError.failedAddingPercentEncoding - } - - let urlString = "\(baseURL)/#/\(roomId)/\(eventId)" - - guard let url = URL(string: urlString) else { - throw PermalinkBuilderError.failedConstructingURL - } - - return url - } - - // MARK: - Private - - private static func permalinkTo(roomIdentifierOrAlias: String, baseURL: URL) throws -> URL { - guard let identifier = roomIdentifierOrAlias.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet) else { - throw PermalinkBuilderError.failedAddingPercentEncoding - } - - let urlString = "\(baseURL)/#/\(identifier)" - - guard let url = URL(string: urlString) else { - throw PermalinkBuilderError.failedConstructingURL - } - - return url - } -} diff --git a/ElementX/Sources/Other/Pills/MessageText.swift b/ElementX/Sources/Other/Pills/MessageText.swift index ff5e11615..3bc00a09a 100644 --- a/ElementX/Sources/Other/Pills/MessageText.swift +++ b/ElementX/Sources/Other/Pills/MessageText.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import MatrixRustSDK import SwiftUI final class MessageTextView: UITextView, PillAttachmentViewProviderDelegate { @@ -129,8 +130,7 @@ struct MessageText: UIViewRepresentable { final class Coordinator: NSObject, UITextViewDelegate { var openURLAction: OpenURLAction - let permalinkBaseURL = ServiceLocator.shared.settings.permalinkBaseURL - + init(openURLAction: OpenURLAction) { self.openURLAction = openURLAction } @@ -146,7 +146,7 @@ struct MessageText: UIViewRepresentable { return false case .preview: // We don't want to show a URL preview for permalinks - return PermalinkBuilder.detectPermalink(in: URL, baseURL: permalinkBaseURL) == nil + return parseMatrixEntityFrom(uri: URL.absoluteString) == nil default: return true } @@ -157,7 +157,7 @@ struct MessageText: UIViewRepresentable { switch textItem.content { case let .link(url): // We don't want to show a URL preview for permalinks - let isPermalink = PermalinkBuilder.detectPermalink(in: url, baseURL: permalinkBaseURL) != nil + let isPermalink = parseMatrixEntityFrom(uri: url.absoluteString) != nil return .init(preview: isPermalink ? nil : .default, menu: defaultMenu) default: return nil @@ -197,7 +197,7 @@ struct MessageText_Previews: PreviewProvider, TestablePreview { private static let htmlStringWithList = "

This is a list

\n
    \n
  • One
  • \n
  • Two
  • \n
  • And number 3
  • \n
\n" - private static let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder()) + private static let attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder()) static var attachmentPreview: some View { MessageText(attributedString: attributedStringWithAttachment) diff --git a/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift b/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift index dc8eb9bed..eea292285 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import MatrixRustSDK import SwiftUI struct MatrixUserShareLink: View { @@ -22,8 +23,7 @@ struct MatrixUserShareLink: View { init(userID: String, @ViewBuilder label: () -> Label) { self.label = label() - permalink = try? PermalinkBuilder.permalinkTo(userIdentifier: userID, - baseURL: ServiceLocator.shared.settings.permalinkBaseURL) + permalink = try? URL(string: matrixToUserPermalink(userId: userID)) } var body: some View { diff --git a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift index 102cb0675..318d3bbd4 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift @@ -17,6 +17,7 @@ import Combine import Foundation import GameKit +import MatrixRustSDK import SwiftUI import WysiwygComposer @@ -210,7 +211,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool private func setupMentionsHandling(mentionDisplayHelper: MentionDisplayHelper) { wysiwygViewModel.mentionDisplayHelper = mentionDisplayHelper - let attributedStringBuilder = AttributedStringBuilder(cacheKey: "Composer", permalinkBaseURL: appSettings.permalinkBaseURL, mentionBuilder: MentionBuilder()) + let attributedStringBuilder = AttributedStringBuilder(cacheKey: "Composer", mentionBuilder: MentionBuilder()) wysiwygViewModel.mentionReplacer = ComposerMentionReplacer { urlString, string in let attributedString: NSMutableAttributedString @@ -231,7 +232,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool private func handleSuggestion(_ suggestion: SuggestionItem) { switch suggestion { case let .user(item): - guard let url = try? PermalinkBuilder.permalinkTo(userIdentifier: item.id, baseURL: appSettings.permalinkBaseURL) else { + guard let url = try? URL(string: matrixToUserPermalink(userId: item.id)) else { MXLog.error("Could not build user permalink") return } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift index a0d19e2b0..7ad303c57 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift @@ -319,8 +319,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxy, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .userDirectory, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) }() static let dmRoomViewModel = { @@ -345,8 +344,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxy, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .userDirectory, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) }() static let simpleRoomViewModel = { @@ -370,8 +368,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxy, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .userDirectory, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift index 5e956c535..dc95dc0f5 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift @@ -209,7 +209,7 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview { "

test

\n

test

" ] - let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder()) + let attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder()) ScrollView { VStack(alignment: .leading, spacing: 24.0) { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift index 0d0331728..d3f9f8425 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift @@ -110,7 +110,7 @@ struct TextRoomTimelineView_Previews: PreviewProvider, TestablePreview { } private static func itemWith(html: String, timestamp: String, isOutgoing: Bool, senderId: String) -> TextRoomTimelineItem { - let builder = AttributedStringBuilder(cacheKey: "preview", permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder()) + let builder = AttributedStringBuilder(cacheKey: "preview", mentionBuilder: MentionBuilder()) let attributedString = builder.fromHTML(html) return TextRoomTimelineItem(id: .random, diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index e6ccf0029..395420cd8 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -681,7 +681,6 @@ class ClientProxy: ClientProxyProtocol { let roomListService = syncService.roomListService() let roomMessageEventStringBuilder = RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(cacheKey: "roomList", - permalinkBaseURL: appSettings.permalinkBaseURL, mentionBuilder: PlainMentionBuilder())) let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID), messageEventStringBuilder: roomMessageEventStringBuilder) diff --git a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift index e9785525a..0802129d4 100644 --- a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift @@ -37,8 +37,7 @@ extension RoomMemberProxyProtocol { } var permalink: URL? { - try? PermalinkBuilder.permalinkTo(userIdentifier: userID, - baseURL: ServiceLocator.shared.settings.permalinkBaseURL) + try? URL(string: matrixToUserPermalink(userId: userID)) } /// The name used for sorting the member alphabetically. This will be the displayname if, diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index 80b91411d..2c3cf16a4 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -97,8 +97,7 @@ enum RoomTimelineItemFixtures { isThreaded: false, sender: .init(id: "", displayName: "Helena"), content: .init(body: "", - formattedBody: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder()) + formattedBody: AttributedStringBuilder(mentionBuilder: MentionBuilder()) .fromHTML("Hol' up
New home office set up!
That's amazing! Congrats 🥳"))) ] diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index 8ca8750f8..a05a4884c 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -41,8 +41,7 @@ import UserNotifications // database, logging, etc. are only ever setup once per *process* private let settings: NSESettingsProtocol = AppSettings() -private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: settings.permalinkBaseURL, - mentionBuilder: PlainMentionBuilder()))) +private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()))) private let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) private var userSessions = [String: NSEUserSession]() diff --git a/NSE/Sources/Other/NSESettingsProtocol.swift b/NSE/Sources/Other/NSESettingsProtocol.swift index bbef735ca..943b1fe5a 100644 --- a/NSE/Sources/Other/NSESettingsProtocol.swift +++ b/NSE/Sources/Other/NSESettingsProtocol.swift @@ -17,8 +17,6 @@ import Foundation protocol NSESettingsProtocol { - var permalinkBaseURL: URL { get } - var logLevel: TracingConfiguration.LogLevel { get } } diff --git a/NSE/SupportingFiles/target.yml b/NSE/SupportingFiles/target.yml index 3f5534fb8..30fcccbcf 100644 --- a/NSE/SupportingFiles/target.yml +++ b/NSE/SupportingFiles/target.yml @@ -91,7 +91,6 @@ targets: - path: ../../ElementX/Sources/Other/InfoPlistReader.swift - path: ../../ElementX/Sources/Other/Logging - path: ../../ElementX/Sources/Other/MatrixEntityRegex.swift - - path: ../../ElementX/Sources/Other/PermalinkBuilder.swift - path: ../../ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift - path: ../../ElementX/Sources/Other/UserAgentBuilder.swift - path: ../../ElementX/Sources/Other/UserPreference.swift diff --git a/UnitTests/Sources/AppRouteURLParserTests.swift b/UnitTests/Sources/AppRouteURLParserTests.swift index a7a29412e..54a0f99f0 100644 --- a/UnitTests/Sources/AppRouteURLParserTests.swift +++ b/UnitTests/Sources/AppRouteURLParserTests.swift @@ -111,7 +111,7 @@ class AppRouteURLParserTests: XCTestCase { func testMatrixUserURL() { let userID = "@test:matrix.org" - guard let url = URL(string: "\(appSettings.permalinkBaseURL)/#/\(userID)") else { + guard let url = URL(string: "https://matrix.to/#/\(userID)") else { XCTFail("Invalid url") return } @@ -123,7 +123,7 @@ class AppRouteURLParserTests: XCTestCase { func testMatrixRoomIdentifierURL() { let id = "!abcdefghijklmnopqrstuvwxyz1234567890:matrix.org" - guard let url = URL(string: "\(appSettings.permalinkBaseURL)/#/\(id)") else { + guard let url = URL(string: "https://matrix.to/#/\(id)") else { XCTFail("Invalid url") return } diff --git a/UnitTests/Sources/AttributedStringBuilderTests.swift b/UnitTests/Sources/AttributedStringBuilderTests.swift index 8680ebc3d..553eb1674 100644 --- a/UnitTests/Sources/AttributedStringBuilderTests.swift +++ b/UnitTests/Sources/AttributedStringBuilderTests.swift @@ -18,8 +18,7 @@ import XCTest class AttributedStringBuilderTests: XCTestCase { - private let permalinkBaseURL = ServiceLocator.shared.settings.permalinkBaseURL - private lazy var attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: permalinkBaseURL, mentionBuilder: MentionBuilder()) + private lazy var attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder()) private let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2) func testRenderHTMLStringWithHeaders() { @@ -192,35 +191,11 @@ class AttributedStringBuilderTests: XCTestCase { func testUserIdLink() { let userId = "@user:matrix.org" let string = "The user is \(userId)." - let expectedLink = "\(permalinkBaseURL)/#/\(userId)" + let expectedLink = "https://matrix.to/#/\(userId)" checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: expectedLink, expectedRuns: 3) checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: expectedLink, expectedRuns: 3) } - - func testRoomAliasLink() { - let roomAlias = "#matrix:matrix.org" - let string = "The room alias is \(roomAlias)." - let expectedLink = "https://matrix.to/#/%23matrix%3Amatrix.org" - checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: expectedLink, expectedRuns: 3) - checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: expectedLink, expectedRuns: 3) - } - - func testRoomIdLink() { - let roomId = "!roomidentifier:matrix.org" - let string = "The room is \(roomId)." - let expectedLink = "https://matrix.to/#/!roomidentifier%3Amatrix.org" - checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: expectedLink, expectedRuns: 3) - checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: expectedLink, expectedRuns: 3) - } - - // As of right now we do not handle event id links in any way so there is no need to add them as links -// func testEventIdLink() { -// let eventId = "$eventidentifier" -// let string = "The event is \(eventId)." -// checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: eventId) -// checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: eventId) -// } - + func testDefaultFont() { let htmlString = "Test string." diff --git a/UnitTests/Sources/AttributedStringTests.swift b/UnitTests/Sources/AttributedStringTests.swift index bbd11ccad..a1dad32a2 100644 --- a/UnitTests/Sources/AttributedStringTests.swift +++ b/UnitTests/Sources/AttributedStringTests.swift @@ -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, mentionBuilder: MentionBuilder()) + guard let originalString = AttributedStringBuilder(mentionBuilder: MentionBuilder()) .fromHTML("Normal \(boldString) Normal.") else { XCTFail("The attributed string should be built from the HTML.") return diff --git a/UnitTests/Sources/PermalinkBuilderTests.swift b/UnitTests/Sources/PermalinkBuilderTests.swift deleted file mode 100644 index cddc15333..000000000 --- a/UnitTests/Sources/PermalinkBuilderTests.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// Copyright 2022 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. -// - -@testable import ElementX -import XCTest - -class PermalinkBuilderTests: XCTestCase { - private var appSettings: AppSettings! - - override func setUp() { - AppSettings.configureWithSuiteName("io.element.elementx.unitests") - AppSettings.resetAllSettings() - appSettings = AppSettings() - } - - func testUserIdentifierPermalink() { - let userId = "@abcdefghijklmnopqrstuvwxyz1234567890._-=/:matrix.org" - - do { - let permalink = try PermalinkBuilder.permalinkTo(userIdentifier: userId, baseURL: appSettings.permalinkBaseURL) - XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/\(userId)")) - } catch { - XCTFail("User identifier must be valid: \(error)") - } - } - - func testInvalidUserIdentifier() { - do { - _ = try PermalinkBuilder.permalinkTo(userIdentifier: "This1sN0tV4lid!@#$%^&*()", baseURL: appSettings.permalinkBaseURL) - XCTFail("A permalink should not be created.") - } catch { - XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidUserIdentifier) - } - } - - func testRoomIdentifierPermalink() throws { - let roomId = "!abcdefghijklmnopqrstuvwxyz1234567890:matrix.org" - - do { - let permalink = try PermalinkBuilder.permalinkTo(roomIdentifier: roomId, baseURL: appSettings.permalinkBaseURL) - XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org")) - } catch { - XCTFail("Room identifier must be valid: \(error)") - } - } - - func testMautrixBridgePermalink() throws { - let roomId = "!mautrix-signal-v6:maunium.net" - - do { - let permalink = try PermalinkBuilder.permalinkTo(roomIdentifier: roomId, baseURL: appSettings.permalinkBaseURL) - XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/!mautrix-signal-v6%3Amaunium.net")) - } catch { - XCTFail("Room identifier must be valid: \(error)") - } - } - - func testInvalidRoomIdentifier() { - do { - _ = try PermalinkBuilder.permalinkTo(roomIdentifier: "This1sN0tV4lid!@#$%^&*()", baseURL: appSettings.permalinkBaseURL) - XCTFail("A permalink should not be created.") - } catch { - XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidRoomIdentifier) - } - } - - func testRoomAliasPermalink() throws { - let roomAlias = "#abcdefghijklmnopqrstuvwxyz-_.1234567890:matrix.org" - - do { - let permalink = try PermalinkBuilder.permalinkTo(roomAlias: roomAlias, baseURL: appSettings.permalinkBaseURL) - XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/%23abcdefghijklmnopqrstuvwxyz-_.1234567890%3Amatrix.org")) - } catch { - XCTFail("Room alias must be valid: \(error)") - } - } - - func testInvalidRoomAlias() { - do { - _ = try PermalinkBuilder.permalinkTo(roomAlias: "This1sN0tV4lid!@#$%^&*()", baseURL: appSettings.permalinkBaseURL) - XCTFail("A permalink should not be created.") - } catch { - XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidRoomAlias) - } - } - - func testEventPermalink() throws { - let eventId = "$abcdefghijklmnopqrstuvwxyz1234567890" - let roomId = "!abcdefghijklmnopqrstuvwxyz1234567890:matrix.org" - - do { - let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventId, roomIdentifier: roomId, baseURL: appSettings.permalinkBaseURL) - XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org/%24abcdefghijklmnopqrstuvwxyz1234567890")) - } catch { - XCTFail("Room and event identifiers must be valid: \(error)") - } - } - - func testInvalidEventIdentifier() { - do { - _ = try PermalinkBuilder.permalinkTo(eventIdentifier: "This1sN0tV4lid!@#$%^&*()", roomIdentifier: "", baseURL: appSettings.permalinkBaseURL) - XCTFail("A permalink should not be created.") - } catch { - XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidEventIdentifier) - } - } - - func testPermalinkDetection() { - var url: URL = "https://www.matrix.org" - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), nil) - - url = "https://matrix.to/#/@bob:matrix.org?via=matrix.org" - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.userIdentifier("@bob:matrix.org")) - - url = "https://matrix.to/#/!roomidentifier:matrix.org?via=matrix.org" - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.roomIdentifier("!roomidentifier:matrix.org")) - - url = "https://matrix.to/#/%23roomalias:matrix.org?via=matrix.org" - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.roomAlias("#roomalias:matrix.org")) - - url = "https://matrix.to/#/!roomidentifier:matrix.org/$eventidentifier?via=matrix.org" - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.event(roomIdentifier: "!roomidentifier:matrix.org", eventIdentifier: "$eventidentifier")) - } -} diff --git a/UnitTests/Sources/PermalinkTests.swift b/UnitTests/Sources/PermalinkTests.swift new file mode 100644 index 000000000..554d4f0ee --- /dev/null +++ b/UnitTests/Sources/PermalinkTests.swift @@ -0,0 +1,60 @@ +// +// Copyright 2022 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. +// + +@testable import ElementX +import MatrixRustSDK +import XCTest + +/// Just for API sanity checking, they're already properly tested in the SDK/Ruma +class PermalinkTests: XCTestCase { + func testUserIdentifierPermalink() { + let invalidUserId = "This1sN0tV4lid!@#$%^&*()" + XCTAssertNil(try? matrixToUserPermalink(userId: invalidUserId)) + + let validUserId = "@abcdefghijklmnopqrstuvwxyz1234567890._-=/:matrix.org" + XCTAssertEqual(try? matrixToUserPermalink(userId: validUserId), .some("https://matrix.to/#/@abcdefghijklmnopqrstuvwxyz1234567890._-=%2F:matrix.org")) + } + + func testPermalinkDetection() { + var url: URL = "https://www.matrix.org" + XCTAssertNil(parseMatrixEntityFrom(uri: url.absoluteString)) + + url = "https://matrix.to/#/@bob:matrix.org?via=matrix.org" + XCTAssertEqual(parseMatrixEntityFrom(uri: url.absoluteString), + MatrixEntity(id: .user(id: "@bob:matrix.org"), + via: ["matrix.org"])) + + url = "https://matrix.to/#/!roomidentifier:matrix.org?via=matrix.org" + XCTAssertEqual(parseMatrixEntityFrom(uri: url.absoluteString), + MatrixEntity(id: .room(id: "!roomidentifier:matrix.org"), + via: ["matrix.org"])) + + url = "https://matrix.to/#/%23roomalias:matrix.org?via=matrix.org" + XCTAssertEqual(parseMatrixEntityFrom(uri: url.absoluteString), + MatrixEntity(id: .roomAlias(alias: "#roomalias:matrix.org"), + via: ["matrix.org"])) + + url = "https://matrix.to/#/!roomidentifier:matrix.org/$eventidentifier?via=matrix.org" + XCTAssertEqual(parseMatrixEntityFrom(uri: url.absoluteString), + MatrixEntity(id: .eventOnRoomId(roomId: "!roomidentifier:matrix.org", eventId: "$eventidentifier"), + via: ["matrix.org"])) + + url = "https://matrix.to/#/#roomalias:matrix.org/$eventidentifier?via=matrix.org" + XCTAssertEqual(parseMatrixEntityFrom(uri: url.absoluteString), + MatrixEntity(id: .eventOnRoomAlias(alias: "#roomalias:matrix.org", eventId: "$eventidentifier"), + via: ["matrix.org"])) + } +} diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index 92928390a..8f716d4c7 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -39,8 +39,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxyMock, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) AppSettings.resetAllSettings() } @@ -54,8 +53,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) let deferred = deferFulfillment(context.$viewState) { state in state.bindings.leaveRoomAlertItem != nil } @@ -76,8 +74,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) let deferred = deferFulfillment(context.$viewState) { state in state.bindings.leaveRoomAlertItem != nil } @@ -99,8 +96,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) context.send(viewAction: .processTapLeave) XCTAssertEqual(context.leaveRoomAlertItem?.state, .empty) @@ -152,8 +148,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) let deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -175,8 +170,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -209,8 +203,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -242,8 +235,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -276,8 +268,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -311,8 +302,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -328,8 +318,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -364,8 +353,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -387,8 +375,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -410,8 +397,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -430,8 +416,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -450,8 +435,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -468,8 +452,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxyMock, - attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, - mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) var deferred = deferFulfillment(context.$viewState) { state in state.notificationSettingsState.isError