diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 1dce2aefe..57fc215b6 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -8659,7 +8659,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 25.04.16; + version = 25.04.29; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0a5e7eaf5..970b0d62e 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -158,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "895ba54c171859cace965e77b6fe2f64919ecb27", - "version" : "25.4.16" + "revision" : "cd4509acd56641c4d110b29f692ecafe5438698f", + "version" : "25.4.29" } }, { diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 24c43e707..d33da3139 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -57,8 +57,6 @@ final class AppSettings { case fuzzyRoomListSearchEnabled case enableOnlySignedDeviceIsolationMode case knockingEnabled - case reportRoomEnabled - case reportInviteEnabled case threadsEnabled } @@ -327,11 +325,6 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store)) var knockingEnabled - @UserPreference(key: UserDefaultsKeys.reportRoomEnabled, defaultValue: false, storageType: .userDefaults(store)) var reportRoomEnabled - - @UserPreference(key: UserDefaultsKeys.reportInviteEnabled, defaultValue: false, storageType: .userDefaults(store)) - var reportInviteEnabled - @UserPreference(key: UserDefaultsKeys.threadsEnabled, defaultValue: false, storageType: .userDefaults(store)) var threadsEnabled diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index 31bd7bad5..aa1c97912 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -92,5 +92,7 @@ extension ClientProxyMock { } userIdentityForReturnValue = .success(UserIdentityProxyMock(configuration: .init())) + + underlyingIsReportRoomSupported = true } } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index e59c9275f..11a049ebc 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2303,6 +2303,23 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { } var underlyingSecureBackupController: SecureBackupControllerProtocol! var sessionVerificationController: SessionVerificationControllerProxyProtocol? + var isReportRoomSupportedCallsCount = 0 + var isReportRoomSupportedCalled: Bool { + return isReportRoomSupportedCallsCount > 0 + } + + var isReportRoomSupported: Bool { + get async { + isReportRoomSupportedCallsCount += 1 + if let isReportRoomSupportedClosure = isReportRoomSupportedClosure { + return await isReportRoomSupportedClosure() + } else { + return underlyingIsReportRoomSupported + } + } + } + var underlyingIsReportRoomSupported: Bool! + var isReportRoomSupportedClosure: (() async -> Bool)? //MARK: - isOnlyDeviceLeft diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 4281c87c3..cd5aac873 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -108,9 +108,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .weakAssign(to: \.state.hideInviteAvatars, on: self) .store(in: &cancellables) - appSettings.$reportRoomEnabled - .weakAssign(to: \.state.reportRoomEnabled, on: self) - .store(in: &cancellables) + Task { + state.reportRoomEnabled = await userSession.clientProxy.isReportRoomSupported + } let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused) let searchQuery = context.$viewState.map(\.bindings.searchQuery) @@ -208,7 +208,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol await acceptInvite(roomID: roomIdentifier) } case .declineInvite(let roomIdentifier): - showDeclineInviteConfirmationAlert(roomID: roomIdentifier) + Task { await showDeclineInviteConfirmationAlert(roomID: roomIdentifier) } } } @@ -419,7 +419,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } } - private func showDeclineInviteConfirmationAlert(roomID: String) { + private func showDeclineInviteConfirmationAlert(roomID: String) async { guard let room = state.rooms.first(where: { $0.id == roomID }) else { displayError() return @@ -429,7 +429,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol let title = room.isDirect ? L10n.screenInvitesDeclineDirectChatTitle : L10n.screenInvitesDeclineChatTitle let message = room.isDirect ? L10n.screenInvitesDeclineDirectChatMessage(roomPlaceholder) : L10n.screenInvitesDeclineChatMessage(roomPlaceholder) - if appSettings.reportInviteEnabled, let userID = room.inviter?.id { + if await userSession.clientProxy.isReportRoomSupported, let userID = room.inviter?.id { state.bindings.alertInfo = .init(id: UUID(), title: title, message: message, diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift index caf401169..38f82d13e 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift @@ -83,7 +83,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo case .dismiss: actionsSubject.send(.dismiss) case .declineInviteAndBlock(let userID): - showDeclineAndBlockConfirmationAlert(userID: userID) + Task { await showDeclineAndBlockConfirmationAlert(userID: userID) } } } @@ -322,8 +322,8 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo secondaryButton: .init(title: L10n.screenJoinRoomCancelKnockAlertConfirmation, role: .destructive) { Task { await self.cancelKnock() } }) } - private func showDeclineAndBlockConfirmationAlert(userID: String) { - if appSettings.reportInviteEnabled { + private func showDeclineAndBlockConfirmationAlert(userID: String) async { + if await clientProxy.isReportRoomSupported { actionsSubject.send(.presentDeclineAndBlock(userID: userID)) } else { state.bindings.alertInfo = .init(id: .declineInviteAndBlock, diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index 698d18106..4fdd0cadf 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -78,9 +78,9 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr .weakAssign(to: \.state.knockingEnabled, on: self) .store(in: &cancellables) - appSettings.$reportRoomEnabled - .weakAssign(to: \.state.reportRoomEnabled, on: self) - .store(in: &cancellables) + Task { + state.reportRoomEnabled = await clientProxy.isReportRoomSupported + } appMediator.networkMonitor.reachabilityPublisher .filter { $0 == .reachable } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index ea89e82b8..2f086e04c 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -44,8 +44,6 @@ protocol DeveloperOptionsProtocol: AnyObject { var enableOnlySignedDeviceIsolationMode: Bool { get set } var elementCallBaseURLOverride: URL? { get set } var knockingEnabled: Bool { get set } - var reportRoomEnabled: Bool { get set } - var reportInviteEnabled: Bool { get set } var threadsEnabled: Bool { get set } var isNewBloomEnabled: Bool { get set } } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index ab92a2fd1..de89245e0 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -73,17 +73,6 @@ struct DeveloperOptionsScreen: View { } footer: { Text("This setting controls how end-to-end encryption (E2EE) keys are exchanged. Enabling it will prevent the inclusion of devices that have not been explicitly verified by their owners.") } - - Section("Reporting") { - Toggle(isOn: $context.reportRoomEnabled) { - Text("Report rooms") - Text("Report API might not work properly") - } - Toggle(isOn: $context.reportInviteEnabled) { - Text("Report invites") - Text("Report API might not work properly") - } - } Section { TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString) diff --git a/ElementX/Sources/Services/Authentication/OIDCConfigurationProxy.swift b/ElementX/Sources/Services/Authentication/OIDCConfigurationProxy.swift index 17acfcb45..b36b2b815 100644 --- a/ElementX/Sources/Services/Authentication/OIDCConfigurationProxy.swift +++ b/ElementX/Sources/Services/Authentication/OIDCConfigurationProxy.swift @@ -28,7 +28,6 @@ extension OIDCConfigurationProxy { logoUri: logoURI.absoluteString, tosUri: tosURI.absoluteString, policyUri: policyURI.absoluteString, - contacts: nil, staticRegistrations: staticRegistrations) } } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index ffa1e9f90..b3b570138 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -168,7 +168,7 @@ class ClientProxy: ClientProxyProtocol { syncServiceStateUpdateTaskHandle = createSyncServiceStateObserver(syncService) roomListStateUpdateTaskHandle = createRoomListServiceObserver(roomListService) roomListStateLoadingStateUpdateTaskHandle = createRoomListLoadingStateUpdateObserver(roomListService) - + delegateHandle = client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in self?.hasEncounteredAuthError = true self?.actionsSubject.send(.receivedAuthError(isSoftLogout: isSoftLogout)) @@ -269,6 +269,17 @@ class ClientProxy: ClientProxyProtocol { return nil } } + + var isReportRoomSupported: Bool { + get async { + do { + return try await client.isReportRoomApiSupported() + } catch { + MXLog.error("Failed checking report room support with error: \(error)") + return false + } + } + } private(set) lazy var pusherNotificationClientIdentifier: String? = { // NOTE: The result is stored as part of the restoration token. Any changes diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 97450350e..bc979ed1d 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -113,6 +113,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get } + var isReportRoomSupported: Bool { get async } + func isOnlyDeviceLeft() async -> Result func startSync() diff --git a/ElementX/Sources/Services/Timeline/TimelineProxy.swift b/ElementX/Sources/Services/Timeline/TimelineProxy.swift index 494bdfde5..a35fbef57 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxy.swift @@ -233,7 +233,7 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Sending audio") do { - let handle = try timeline.sendAudio(params: .init(filename: url.path(percentEncoded: false), + let handle = try timeline.sendAudio(params: .init(source: .file(filename: url.path(percentEncoded: false)), caption: caption, formattedCaption: nil, // Rust will build this from the caption's markdown. mentions: nil, @@ -261,7 +261,7 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Sending file") do { - let handle = try timeline.sendFile(params: .init(filename: url.path(percentEncoded: false), + let handle = try timeline.sendFile(params: .init(source: .file(filename: url.path(percentEncoded: false)), caption: caption, formattedCaption: nil, // Rust will build this from the caption's markdown. mentions: nil, @@ -290,7 +290,7 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Sending image") do { - let handle = try timeline.sendImage(params: .init(filename: url.path(percentEncoded: false), + let handle = try timeline.sendImage(params: .init(source: .file(filename: url.path(percentEncoded: false)), caption: caption, formattedCaption: nil, // Rust will build this from the caption's markdown. mentions: nil, @@ -338,7 +338,7 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Sending video") do { - let handle = try timeline.sendVideo(params: .init(filename: url.path(percentEncoded: false), + let handle = try timeline.sendVideo(params: .init(source: .file(filename: url.path(percentEncoded: false)), caption: caption, formattedCaption: nil, mentions: nil, @@ -367,7 +367,7 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Sending voice message") do { - let handle = try timeline.sendVoiceMessage(params: .init(filename: url.path(percentEncoded: false), + let handle = try timeline.sendVoiceMessage(params: .init(source: .file(filename: url.path(percentEncoded: false)), caption: nil, formattedCaption: nil, mentions: nil, diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 6513c4eb8..1565e0f66 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -304,7 +304,6 @@ class HomeScreenViewModelTests: XCTestCase { func testDeclineInvite() async throws { setupViewModel(withInvites: true) - let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID) appSettings.seenInvites = Set(invitedRoomIDs) XCTAssertEqual(invitedRoomIDs.count, 2) @@ -323,7 +322,7 @@ class HomeScreenViewModelTests: XCTestCase { return .invited(roomProxy) } - context.viewState.bindings.alertInfo?.secondaryButton?.action?() + context.viewState.bindings.alertInfo?.verticalButtons?[0].action?() await fulfillment(of: [rejectExpectation], timeout: 1.0) XCTAssertEqual(appSettings.seenInvites, [invitedRoomIDs[1]]) @@ -331,6 +330,21 @@ class HomeScreenViewModelTests: XCTestCase { XCTAssertEqual(notificationManager.removeDeliveredMessageNotificationsForReceivedInvocations, [invitedRoomIDs[0]]) } + func testDeclineAndBlockInvite() async throws { + setupViewModel(withInvites: true) + let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID) + appSettings.seenInvites = Set(invitedRoomIDs) + XCTAssertEqual(invitedRoomIDs.count, 2) + + let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil } + context.send(viewAction: .declineInvite(roomIdentifier: invitedRoomIDs[0])) + try await deferred.fulfill() + + let deferredAction = deferFulfillment(viewModel.actions) { $0 == .presentDeclineAndBlock(userID: RoomMemberProxyMock.mockCharlie.userID, roomID: invitedRoomIDs[0]) } + context.viewState.bindings.alertInfo?.secondaryButton?.action?() + try await deferredAction.fulfill() + } + // MARK: - Helpers private func setupViewModel(securityStatePublisher: CurrentValuePublisher? = nil, withInvites: Bool = false) { diff --git a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift index 2abfab8ee..95b4de388 100644 --- a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift +++ b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift @@ -111,8 +111,9 @@ class JoinRoomScreenViewModelTests: XCTestCase { try await deferred.fulfill() } - func testDeclineAndBlockInviteInteraction() async throws { + func testDeclineAndBlockInviteLegacyInteraction() async throws { setupViewModel(mode: .invited) + clientProxy.underlyingIsReportRoomSupported = false let expectation = expectation(description: "Wait for the user to be ignored") clientProxy.ignoreUserClosure = { userID in defer { expectation.fulfill() } @@ -124,15 +125,23 @@ class JoinRoomScreenViewModelTests: XCTestCase { context.send(viewAction: .declineInviteAndBlock(userID: "@test:matrix.org")) + try await deferFulfillment(viewModel.context.$viewState) { $0.bindings.alertInfo != nil }.fulfill() XCTAssertEqual(viewModel.context.alertInfo?.id, .declineInviteAndBlock) let deferred = deferFulfillment(viewModel.actionsPublisher) { action in action == .dismiss } context.alertInfo?.secondaryButton?.action?() - try await deferred.fulfill() - await fulfillment(of: [expectation], timeout: 10) + try await deferred.fulfill() + } + + func testDeclineAndBlockInviteInteraction() async throws { + setupViewModel(mode: .invited) + try await deferFulfillment(viewModel.context.$viewState) { $0.roomDetails != nil }.fulfill() + let deferredAction = deferFulfillment(viewModel.actionsPublisher) { $0 == .presentDeclineAndBlock(userID: "@test:matrix.org") } + context.send(viewAction: .declineInviteAndBlock(userID: "@test:matrix.org")) + try await deferredAction.fulfill() } func testForgetRoom() async throws { diff --git a/project.yml b/project.yml index 563d7e319..4b9512010 100644 --- a/project.yml +++ b/project.yml @@ -59,7 +59,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 25.04.16 + exactVersion: 25.04.29 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios