updated SDK and improved report flow

the report flow is now based on the matrix version and the new one will only be used if the SDK checks if the server supports it.
This commit is contained in:
Mauro Romito
2025-04-29 17:43:56 +02:00
committed by Mauro
parent 16f16b488f
commit b6ade2d4a9
17 changed files with 82 additions and 48 deletions

View File

@@ -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" */ = {

View File

@@ -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"
}
},
{

View File

@@ -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

View File

@@ -92,5 +92,7 @@ extension ClientProxyMock {
}
userIdentityForReturnValue = .success(UserIdentityProxyMock(configuration: .init()))
underlyingIsReportRoomSupported = true
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 }

View File

@@ -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 }
}

View File

@@ -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)

View File

@@ -28,7 +28,6 @@ extension OIDCConfigurationProxy {
logoUri: logoURI.absoluteString,
tosUri: tosURI.absoluteString,
policyUri: policyURI.absoluteString,
contacts: nil,
staticRegistrations: staticRegistrations)
}
}

View File

@@ -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

View File

@@ -113,6 +113,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get }
var isReportRoomSupported: Bool { get async }
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
func startSync()

View File

@@ -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,

View File

@@ -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<SessionSecurityState, Never>? = nil, withInvites: Bool = false) {

View File

@@ -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 {

View File

@@ -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