Refactor SecureBackupControllerListener into SDKListener and use it everywhere. (#4030)

This commit is contained in:
Doug
2025-04-16 08:36:57 +01:00
committed by GitHub
parent b1b2b6bf8a
commit fb104a4077
11 changed files with 125 additions and 171 deletions

View File

@@ -324,6 +324,7 @@
3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
3F2148F11164C7C5609984EB /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 2B788C81F6369D164ADEB917 /* GZIP */; };
3F327A62D233933F54F0F33A /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 3FE40E79C36E7903121E6E3B /* SwiftOGG */; };
3F55721B5C08E8D9F1295592 /* SDKListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5CD2993048222B64C45006 /* SDKListener.swift */; };
3F70E237CE4C3FAB02FC227F /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; };
401BB28CD6B7DD6B4E7863E7 /* ServerConfirmationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9342F5D6729627B6393AF853 /* ServerConfirmationScreenModels.swift */; };
407DCE030E0F9B7C9861D38A /* LoremSwiftum in Frameworks */ = {isa = PBXBuildFile; productRef = 1A6B622CCFDEFB92D9CF1CA5 /* LoremSwiftum */; };
@@ -2578,6 +2579,7 @@
FDEDD4D2DE0646DA724985D5 /* QRCodeLoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenModels.swift; sourceTree = "<group>"; };
FDF73F49E6B6683F7E2D26F0 /* SecureBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenCoordinator.swift; sourceTree = "<group>"; };
FE1E6FAA3719E9B7A2D5510B /* FormattingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingToolbar.swift; sourceTree = "<group>"; };
FE5CD2993048222B64C45006 /* SDKListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKListener.swift; sourceTree = "<group>"; };
FF720BA68256297680980481 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
FFECCE59967018204876D0A5 /* LocationMarkerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationMarkerView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -5367,6 +5369,7 @@
6A580295A56B55A856CC4084 /* InfoPlistReader.swift */,
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */,
FE5CD2993048222B64C45006 /* SDKListener.swift */,
4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */,
B1E227F34BE43B08E098796E /* TestablePreview.swift */,
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */,
@@ -7549,6 +7552,7 @@
41CE5E1289C8768FC5B6490C /* RoomTimelineItemViewState.swift in Sources */,
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */,
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */,
3F55721B5C08E8D9F1295592 /* SDKListener.swift in Sources */,
88CBF1595E39CE697928DE48 /* SFNumberedListView.swift in Sources */,
FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */,
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */,

View File

@@ -2002,8 +2002,8 @@ class AuthenticationClientBuilderMock: AuthenticationClientBuilderProtocol, @unc
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerCalled: Bool {
return buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerCallsCount > 0
}
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedArguments: (qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: QrLoginProgressListenerProxy)?
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedInvocations: [(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: QrLoginProgressListenerProxy)] = []
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedArguments: (qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: SDKListener<QrLoginProgress>)?
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedInvocations: [(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: SDKListener<QrLoginProgress>)] = []
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerUnderlyingReturnValue: ClientProtocol!
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReturnValue: ClientProtocol! {
@@ -2029,9 +2029,9 @@ class AuthenticationClientBuilderMock: AuthenticationClientBuilderProtocol, @unc
}
}
}
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerClosure: ((QrCodeData, OIDCConfigurationProxy, QrLoginProgressListenerProxy) async throws -> ClientProtocol)?
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerClosure: ((QrCodeData, OIDCConfigurationProxy, SDKListener<QrLoginProgress>) async throws -> ClientProtocol)?
func buildWithQRCode(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: QrLoginProgressListenerProxy) async throws -> ClientProtocol {
func buildWithQRCode(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: SDKListener<QrLoginProgress>) async throws -> ClientProtocol {
if let error = buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerThrowableError {
throw error
}

View File

@@ -0,0 +1,101 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
/// A helper class that can be passed as this listener for SDK callbacks.
///
/// To use this you'll need to add a conformance to the required listener
/// protocol with a specialisation for the type it listens for.
final class SDKListener<T> {
private let onUpdateClosure: (T) -> Void
/// Creates a new listener.
/// - Parameter onUpdateClosure: A closure that will be called whenever a new value is available.
init(_ onUpdateClosure: @escaping (T) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
}
// MARK: QRCodeLoginService
extension SDKListener: QrLoginProgressListener where T == QrLoginProgress {
func onUpdate(state: QrLoginProgress) { onUpdateClosure(state) }
}
// MARK: ClientProxy
extension SDKListener: SyncServiceStateObserver where T == SyncServiceState {
func onUpdate(state: SyncServiceState) { onUpdateClosure(state) }
}
extension SDKListener: RoomListServiceStateListener where T == RoomListServiceState {
func onUpdate(state: RoomListServiceState) { onUpdateClosure(state) }
}
extension SDKListener: RoomListServiceSyncIndicatorListener where T == RoomListServiceSyncIndicator {
func onUpdate(syncIndicator: RoomListServiceSyncIndicator) { onUpdateClosure(syncIndicator) }
}
extension SDKListener: VerificationStateListener where T == VerificationState {
func onUpdate(status: VerificationState) { onUpdateClosure(status) }
}
// MARK: SecureBackupController
extension SDKListener: BackupStateListener where T == BackupState {
func onUpdate(status: BackupState) { onUpdateClosure(status) }
}
extension SDKListener: RecoveryStateListener where T == RecoveryState {
func onUpdate(status: RecoveryState) { onUpdateClosure(status) }
}
extension SDKListener: EnableRecoveryProgressListener where T == EnableRecoveryProgress {
func onUpdate(status: EnableRecoveryProgress) { onUpdateClosure(status) }
}
extension SDKListener: BackupSteadyStateListener where T == BackupUploadState {
func onUpdate(status: BackupUploadState) { onUpdateClosure(status) }
}
// MARK: RoomSummaryProvider
extension SDKListener: RoomListEntriesListener where T == [RoomListEntriesUpdate] {
func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) { onUpdateClosure(roomEntriesUpdate) }
}
extension SDKListener: RoomListLoadingStateListener where T == RoomListLoadingState {
func onUpdate(state: RoomListLoadingState) { onUpdateClosure(state) }
}
// MARK: TimelineProxy
extension SDKListener: PaginationStatusListener where T == RoomPaginationStatus {
func onUpdate(status: RoomPaginationStatus) { onUpdateClosure(status) }
}
extension SDKListener: ProgressWatcher where T == Double {
func transmissionProgress(progress: TransmissionProgress) {
DispatchQueue.main.async { [weak self] in
self?.onUpdateClosure(Double(progress.current) / Double(progress.total))
}
}
}
// MARK: TimelineProvider
extension SDKListener: TimelineListener where T == [TimelineDiff] {
func onUpdate(diff: [TimelineDiff]) { onUpdateClosure(diff) }
}
// MARK: RoomDirectorySearchProxy
extension SDKListener: RoomDirectorySearchEntriesListener where T == [RoomDirectorySearchEntryUpdate] {
func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) { onUpdateClosure(roomEntriesUpdate) }
}

View File

@@ -13,7 +13,7 @@ protocol AuthenticationClientBuilderProtocol {
func build(homeserverAddress: String) async throws -> ClientProtocol
func buildWithQRCode(qrCodeData: QrCodeData,
oidcConfiguration: OIDCConfigurationProxy,
progressListener: QrLoginProgressListenerProxy) async throws -> ClientProtocol
progressListener: SDKListener<QrLoginProgress>) async throws -> ClientProtocol
}
/// A wrapper around `ClientBuilder` to share reusable code between Normal and QR logins.
@@ -33,7 +33,7 @@ struct AuthenticationClientBuilder: AuthenticationClientBuilderProtocol {
/// Builds a Client, authenticating with the given QR code data.
func buildWithQRCode(qrCodeData: QrCodeData,
oidcConfiguration: OIDCConfigurationProxy,
progressListener: QrLoginProgressListenerProxy) async throws -> ClientProtocol {
progressListener: SDKListener<QrLoginProgress>) async throws -> ClientProtocol {
try await makeClientBuilder().buildWithQrCode(qrCodeData: qrCodeData,
oidcConfiguration: oidcConfiguration.rustValue,
progressListener: progressListener)

View File

@@ -192,7 +192,7 @@ class ClientProxy: ClientProxyProtocol {
await updateVerificationState(client.encryption().verificationState())
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: SDKListener { [weak self] verificationState in
Task { await self?.updateVerificationState(verificationState) }
})
@@ -838,7 +838,7 @@ class ClientProxy: ClientProxyProtocol {
}
private func createSyncServiceStateObserver(_ syncService: SyncService) -> TaskHandle {
syncService.state(listener: SyncServiceStateObserverProxy { [weak self] state in
syncService.state(listener: SDKListener { [weak self] state in
guard let self else { return }
MXLog.info("Received sync service update: \(state)")
@@ -856,7 +856,7 @@ class ClientProxy: ClientProxyProtocol {
}
private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle {
roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
roomListService.state(listener: SDKListener { [weak self] state in
guard let self else { return }
MXLog.info("Received room list update: \(state)")
@@ -877,7 +877,7 @@ class ClientProxy: ClientProxyProtocol {
}
private func createRoomListLoadingStateUpdateObserver(_ roomListService: RoomListService) -> TaskHandle {
roomListService.syncIndicator(delayBeforeShowingInMs: 1000, delayBeforeHidingInMs: 0, listener: RoomListServiceSyncIndicatorListenerProxy { [weak self] state in
roomListService.syncIndicator(delayBeforeShowingInMs: 1000, delayBeforeHidingInMs: 0, listener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -1039,54 +1039,6 @@ extension ClientProxy: MediaLoaderProtocol {
}
}
private class SyncServiceStateObserverProxy: SyncServiceStateObserver {
private let onUpdateClosure: (SyncServiceState) -> Void
init(onUpdateClosure: @escaping (SyncServiceState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: SyncServiceState) {
onUpdateClosure(state)
}
}
private class RoomListStateListenerProxy: RoomListServiceStateListener {
private let onUpdateClosure: (RoomListServiceState) -> Void
init(onUpdateClosure: @escaping (RoomListServiceState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: RoomListServiceState) {
onUpdateClosure(state)
}
}
private class RoomListServiceSyncIndicatorListenerProxy: RoomListServiceSyncIndicatorListener {
private let onUpdateClosure: (RoomListServiceSyncIndicator) -> Void
init(onUpdateClosure: @escaping (RoomListServiceSyncIndicator) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(syncIndicator: RoomListServiceSyncIndicator) {
onUpdateClosure(syncIndicator)
}
}
private class VerificationStateListenerProxy: VerificationStateListener {
private let onUpdateClosure: (VerificationState) -> Void
init(onUpdateClosure: @escaping (VerificationState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: VerificationState) {
onUpdateClosure(status)
}
}
private class ClientDelegateWrapper: ClientDelegate {
private let authErrorCallback: (Bool) -> Void

View File

@@ -43,7 +43,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
return .failure(.invalidQRCode)
}
let listener = QrLoginProgressListenerProxy { [weak self] progress in
let listener = SDKListener { [weak self] progress in
self?.qrLoginProgressSubject.send(progress)
}
@@ -92,18 +92,6 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
}
}
final class QrLoginProgressListenerProxy: QrLoginProgressListener {
private let onUpdateClosure: (QrLoginProgress) -> Void
init(onUpdateClosure: @escaping (QrLoginProgress) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: QrLoginProgress) {
onUpdateClosure(state)
}
}
private extension HumanQrLoginError {
var serviceError: QRCodeLoginServiceError {
switch self {

View File

@@ -89,7 +89,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
self.roomList = roomList
do {
listUpdatesSubscriptionResult = roomList.entriesWithDynamicAdapters(pageSize: UInt32(roomListPageSize), listener: RoomListEntriesListenerProxy { [weak self] updates in
listUpdatesSubscriptionResult = roomList.entriesWithDynamicAdapters(pageSize: UInt32(roomListPageSize), listener: SDKListener { [weak self] updates in
guard let self else { return }
diffsPublisher.send(updates)
})
@@ -97,7 +97,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
// Forces the listener above to be called with the current state
setFilter(.all(filters: []))
let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: RoomListStateObserver { [weak self] state in
let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: SDKListener { [weak self] state in
guard let self else { return }
MXLog.info("\(name): Received state update: \(state)")
stateSubject.send(RoomSummaryProviderState(roomListState: state))
@@ -383,27 +383,3 @@ extension RoomSummaryProviderState {
}
}
}
private class RoomListEntriesListenerProxy: RoomListEntriesListener {
private let onUpdateClosure: ([RoomListEntriesUpdate]) -> Void
init(_ onUpdateClosure: @escaping ([RoomListEntriesUpdate]) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) {
onUpdateClosure(roomEntriesUpdate)
}
}
private class RoomListStateObserver: RoomListLoadingStateListener {
private let onUpdateClosure: (RoomListLoadingState) -> Void
init(_ onUpdateClosure: @escaping (RoomListLoadingState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: RoomListLoadingState) {
onUpdateClosure(state)
}
}

View File

@@ -42,7 +42,7 @@ final class RoomDirectorySearchProxy: RoomDirectorySearchProxyProtocol {
.store(in: &cancellables)
Task {
searchEntriesSubscription = await roomDirectorySearch.results(listener: RoomDirectorySearchEntriesListenerProxy { [weak self] updates in
searchEntriesSubscription = await roomDirectorySearch.results(listener: SDKListener { [weak self] updates in
self?.diffsPublisher.send(updates)
})
}
@@ -152,15 +152,3 @@ final class RoomDirectorySearchProxy: RoomDirectorySearchProxyProtocol {
canBeJoined: value.joinRule == .public || (appSettings.knockingEnabled && value.joinRule == .knock))
}
}
private final class RoomDirectorySearchEntriesListenerProxy: RoomDirectorySearchEntriesListener {
private let onUpdateClosure: ([RoomDirectorySearchEntryUpdate]) -> Void
func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) {
onUpdateClosure(roomEntriesUpdate)
}
init(_ onUpdateClosure: @escaping (([RoomDirectorySearchEntryUpdate]) -> Void)) {
self.onUpdateClosure = onUpdateClosure
}
}

View File

@@ -35,7 +35,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
init(encryption: Encryption) {
self.encryption = encryption
backupStateListenerTaskHandle = encryption.backupStateListener(listener: SecureBackupControllerListener { [weak self] state in
backupStateListenerTaskHandle = encryption.backupStateListener(listener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -62,7 +62,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
}
})
recoveryStateListenerTaskHandle = encryption.recoveryStateListener(listener: SecureBackupControllerListener { [weak self] state in
recoveryStateListenerTaskHandle = encryption.recoveryStateListener(listener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -121,7 +121,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
MXLog.info("Enabling recovery")
var keyUploadErrored = false
let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, passphrase: nil, progressListener: SecureBackupControllerListener { [weak self] state in
let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, passphrase: nil, progressListener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -157,7 +157,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
func waitForKeyBackupUpload(uploadStateSubject: CurrentValueSubject<SecureBackupSteadyState, Never>) async -> Result<Void, SecureBackupControllerError> {
do {
MXLog.info("Waiting for backup upload steady state")
try await encryption.waitForBackupUploadSteadyState(progressListener: SecureBackupControllerListener { state in
try await encryption.waitForBackupUploadSteadyState(progressListener: SDKListener { state in
let uploadState: SecureBackupSteadyState = switch state {
case .waiting: .waiting
case .uploading(let backedUpCount, let totalCount): .uploading(uploadedKeyCount: Int(backedUpCount), totalKeyCount: Int(totalCount))
@@ -210,20 +210,3 @@ class SecureBackupController: SecureBackupControllerProtocol {
}
}
}
private final class SecureBackupControllerListener<T> {
private let onUpdateClosure: (T) -> Void
init(_ onUpdateClosure: @escaping (T) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: T) {
onUpdateClosure(status)
}
}
extension SecureBackupControllerListener: BackupStateListener where T == BackupState { }
extension SecureBackupControllerListener: RecoveryStateListener where T == RecoveryState { }
extension SecureBackupControllerListener: EnableRecoveryProgressListener where T == EnableRecoveryProgress { }
extension SecureBackupControllerListener: BackupSteadyStateListener where T == BackupUploadState { }

View File

@@ -57,7 +57,7 @@ class TimelineProvider: TimelineProviderProtocol {
.store(in: &cancellables)
Task {
roomTimelineObservationToken = await timeline.addListener(listener: RoomTimelineListener { [weak self] timelineDiffs in
roomTimelineObservationToken = await timeline.addListener(listener: SDKListener { [weak self] timelineDiffs in
self?.serialDispatchQueue.sync {
self?.updateItemsWithDiffs(timelineDiffs)
}
@@ -215,18 +215,6 @@ private extension VirtualTimelineItem {
}
}
private final class RoomTimelineListener: TimelineListener {
private let onUpdateClosure: ([TimelineDiff]) -> Void
init(_ onUpdateClosure: @escaping ([TimelineDiff]) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(diff: [TimelineDiff]) {
onUpdateClosure(diff)
}
}
private extension Array where Element == TimelineDiff {
var debugDescription: String {
"[" + map(\.debugDescription).joined(separator: ",") + "]"

View File

@@ -577,7 +577,7 @@ final class TimelineProxy: TimelineProxyProtocol {
private func subscribeToPagination() async {
switch kind {
case .live:
let backPaginationListener = RoomPaginationStatusListener { [weak self] status in
let backPaginationListener = SDKListener<RoomPaginationStatus> { [weak self] status in
guard let self else {
return
}
@@ -610,32 +610,6 @@ final class TimelineProxy: TimelineProxyProtocol {
}
}
private final class RoomPaginationStatusListener: PaginationStatusListener {
private let onUpdateClosure: (RoomPaginationStatus) -> Void
init(_ onUpdateClosure: @escaping (RoomPaginationStatus) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: RoomPaginationStatus) {
onUpdateClosure(status)
}
}
private final class UploadProgressListener: ProgressWatcher {
private let onUpdateClosure: (Double) -> Void
init(_ onUpdateClosure: @escaping (Double) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func transmissionProgress(progress: TransmissionProgress) {
DispatchQueue.main.async { [weak self] in
self?.onUpdateClosure(Double(progress.current) / Double(progress.total))
}
}
}
private extension MatrixRustSDK.PollKind {
init(pollKind: Poll.Kind) {
switch pollKind {