Room list service (#1138)

* Adopt the new RoomListService

* Use client.roomListWithEncryption

* Store the roomList so that it doesn't get dropped and create problems on the rust side

* Use roomListService instead of old roomList client method

* Added back documentation removed by mistake

* Tweaks following code review, SDK bump
This commit is contained in:
Stefan Ceriu 2023-06-22 19:59:32 +03:00 committed by GitHub
parent 6018221e1e
commit 652fd3cf76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 109 additions and 96 deletions

View File

@ -3,7 +3,6 @@ disabled_rules:
- unused_setter_value - unused_setter_value
- redundant_discardable_let - redundant_discardable_let
- identifier_name - identifier_name
- unhandled_throwing_task
opt_in_rules: opt_in_rules:
- force_unwrapping - force_unwrapping

View File

@ -5004,7 +5004,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = "1.0.81-alpha"; version = "1.0.82-alpha";
}; };
}; };
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {

View File

@ -11,7 +11,7 @@
{ {
"identity" : "compound-ios", "identity" : "compound-ios",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/vector-im/compound-ios", "location" : "https://github.com/vector-im/compound-ios.git",
"state" : { "state" : {
"revision" : "d1a28b8a311e33ddb517d10391037f1547a3c7b6" "revision" : "d1a28b8a311e33ddb517d10391037f1547a3c7b6"
} }
@ -111,8 +111,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "eb26d0b26995a36df31b3bd965088ccfbe612779", "revision" : "fbac557ed23d326f4838f2cd4a902c6b54851311",
"version" : "1.0.81-alpha" "version" : "1.0.82-alpha"
} }
}, },
{ {

View File

@ -533,13 +533,19 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
let identifier = "StaleDataIndicator" let identifier = "StaleDataIndicator"
func showLoadingIndicator() {
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true))
}
showLoadingIndicator()
clientProxyObserver = userSession.clientProxy clientProxyObserver = userSession.clientProxy
.callbacks .callbacks
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { action in .sink { action in
switch action { switch action {
case .startedUpdating: case .startedUpdating:
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) showLoadingIndicator()
case .receivedSyncUpdate: case .receivedSyncUpdate:
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier) ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier)
default: default:

View File

@ -454,46 +454,46 @@ class SDKClientMock: SDKClientProtocol {
restoreSessionSessionReceivedInvocations.append(`session`) restoreSessionSessionReceivedInvocations.append(`session`)
try restoreSessionSessionClosure?(`session`) try restoreSessionSessionClosure?(`session`)
} }
//MARK: - `roomList` //MARK: - `roomListService`
public var roomListThrowableError: Error? public var roomListServiceThrowableError: Error?
public var roomListCallsCount = 0 public var roomListServiceCallsCount = 0
public var roomListCalled: Bool { public var roomListServiceCalled: Bool {
return roomListCallsCount > 0 return roomListServiceCallsCount > 0
} }
public var roomListReturnValue: RoomList! public var roomListServiceReturnValue: RoomListService!
public var roomListClosure: (() throws -> RoomList)? public var roomListServiceClosure: (() throws -> RoomListService)?
public func `roomList`() throws -> RoomList { public func `roomListService`() throws -> RoomListService {
if let error = roomListThrowableError { if let error = roomListServiceThrowableError {
throw error throw error
} }
roomListCallsCount += 1 roomListServiceCallsCount += 1
if let roomListClosure = roomListClosure { if let roomListServiceClosure = roomListServiceClosure {
return try roomListClosure() return try roomListServiceClosure()
} else { } else {
return roomListReturnValue return roomListServiceReturnValue
} }
} }
//MARK: - `roomListWithEncryption` //MARK: - `roomListServiceWithEncryption`
public var roomListWithEncryptionThrowableError: Error? public var roomListServiceWithEncryptionThrowableError: Error?
public var roomListWithEncryptionCallsCount = 0 public var roomListServiceWithEncryptionCallsCount = 0
public var roomListWithEncryptionCalled: Bool { public var roomListServiceWithEncryptionCalled: Bool {
return roomListWithEncryptionCallsCount > 0 return roomListServiceWithEncryptionCallsCount > 0
} }
public var roomListWithEncryptionReturnValue: RoomList! public var roomListServiceWithEncryptionReturnValue: RoomListService!
public var roomListWithEncryptionClosure: (() throws -> RoomList)? public var roomListServiceWithEncryptionClosure: (() throws -> RoomListService)?
public func `roomListWithEncryption`() throws -> RoomList { public func `roomListServiceWithEncryption`() throws -> RoomListService {
if let error = roomListWithEncryptionThrowableError { if let error = roomListServiceWithEncryptionThrowableError {
throw error throw error
} }
roomListWithEncryptionCallsCount += 1 roomListServiceWithEncryptionCallsCount += 1
if let roomListWithEncryptionClosure = roomListWithEncryptionClosure { if let roomListServiceWithEncryptionClosure = roomListServiceWithEncryptionClosure {
return try roomListWithEncryptionClosure() return try roomListServiceWithEncryptionClosure()
} else { } else {
return roomListWithEncryptionReturnValue return roomListServiceWithEncryptionReturnValue
} }
} }
//MARK: - `rooms` //MARK: - `rooms`

View File

@ -70,14 +70,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return return
} }
Publishers.CombineLatest(roomSummaryProvider.statePublisher, roomSummaryProvider.statePublisher
roomSummaryProvider.roomListPublisher)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] state, rooms in .sink { [weak self] state in
guard let self else { return } guard let self else { return }
let isLoadingData = state == .notLoaded let isLoadingData = !state.isLoaded
let hasNoRooms = (state == .fullyLoaded && rooms.count == 0) let hasNoRooms = (state.isLoaded && state.totalNumberOfRooms == 0)
var roomListMode = self.state.roomListMode var roomListMode = self.state.roomListMode
if isLoadingData { if isLoadingData {
@ -94,7 +93,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
self.state.roomListMode = roomListMode self.state.roomListMode = roomListMode
MXLog.info("Received visibleRoomsSummaryProvider update, setting view room list mode to \"\(self.state.roomListMode)\"") MXLog.info("Received room summary provider update, setting view room list mode to \"\(self.state.roomListMode)\"")
// Delay user profile detail loading until after the initial room list loads // Delay user profile detail loading until after the initial room list loads
if roomListMode == .rooms { if roomListMode == .rooms {

View File

@ -381,7 +381,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
return .init(actions: actions, debugActions: debugActions) return .init(actions: actions, debugActions: debugActions)
} }
// swiftlint:disable:next cyclomatic_complexity function_body_length // swiftlint:disable:next cyclomatic_complexity
private func processTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: String) { private func processTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: String) {
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }), guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else { let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {

View File

@ -26,7 +26,7 @@ class ClientProxy: ClientProxyProtocol {
private let mediaLoader: MediaLoaderProtocol private let mediaLoader: MediaLoaderProtocol
private let clientQueue: DispatchQueue private let clientQueue: DispatchQueue
private var roomListService: RoomList? private var roomListService: RoomListService?
private var roomListStateUpdateTaskHandle: TaskHandle? private var roomListStateUpdateTaskHandle: TaskHandle?
var roomSummaryProvider: RoomSummaryProviderProtocol? var roomSummaryProvider: RoomSummaryProviderProtocol?
@ -227,8 +227,8 @@ class ClientProxy: ClientProxyProtocol {
return nil return nil
} }
if roomSummaryProvider.statePublisher.value != .fullyLoaded { if !roomSummaryProvider.statePublisher.value.isLoaded {
_ = await roomSummaryProvider.statePublisher.values.first(where: { $0 == .fullyLoaded }) _ = await roomSummaryProvider.statePublisher.values.first(where: { $0.isLoaded })
} }
(roomListItem, room) = await Task.dispatch(on: clientQueue) { (roomListItem, room) = await Task.dispatch(on: clientQueue) {
@ -366,7 +366,7 @@ class ClientProxy: ClientProxyProtocol {
} }
do { do {
let roomListService = try client.roomList() let roomListService = try client.roomListServiceWithEncryption()
roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
guard let self else { return } guard let self else { return }
MXLog.info("Received room list update: \(state)") MXLog.info("Received room list update: \(state)")
@ -379,9 +379,12 @@ class ClientProxy: ClientProxyProtocol {
// The invites are available only when entering `running` // The invites are available only when entering `running`
if state == .running { if state == .running {
Task { Task {
// Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms do {
await self.inviteSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.invites(listener:), // Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms
entriesLoadingStateFunction: nil) try await self.inviteSummaryProvider?.setRoomList(roomListService.invites())
} catch {
MXLog.error("Failed configuring invites room list with error: \(error)")
}
} }
} }
@ -397,8 +400,7 @@ class ClientProxy: ClientProxyProtocol {
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
name: "AllRooms") name: "AllRooms")
await roomSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.entries(listener:), try await roomSummaryProvider?.setRoomList(roomListService.allRooms())
entriesLoadingStateFunction: roomListService.entriesLoadingState(listener:))
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService, inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
@ -437,14 +439,14 @@ extension ClientProxy: MediaLoaderProtocol {
} }
} }
private class RoomListStateListenerProxy: RoomListStateListener { private class RoomListStateListenerProxy: RoomListServiceStateListener {
private let onUpdateClosure: (RoomListState) -> Void private let onUpdateClosure: (RoomListServiceState) -> Void
init(_ onUpdateClosure: @escaping (RoomListState) -> Void) { init(_ onUpdateClosure: @escaping (RoomListServiceState) -> Void) {
self.onUpdateClosure = onUpdateClosure self.onUpdateClosure = onUpdateClosure
} }
func onUpdate(state: RoomListState) { func onUpdate(state: RoomListServiceState) {
onUpdateClosure(state) onUpdateClosure(state)
} }
} }

View File

@ -38,12 +38,11 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
statePublisher = .init(.notLoaded) statePublisher = .init(.notLoaded)
case .loaded(let rooms): case .loaded(let rooms):
roomListPublisher = .init(rooms) roomListPublisher = .init(rooms)
statePublisher = .init(.fullyLoaded) statePublisher = .init(.loaded(totalNumberOfRooms: UInt(rooms.count)))
} }
} }
func subscribeIfNecessary(entriesFunction: EntriesFunction, func setRoomList(_ roomList: RoomList) { }
entriesLoadingStateFunction: LoadingStateFunction?) async { }
func updateVisibleRange(_ range: Range<Int>) { } func updateVisibleRange(_ range: Range<Int>) { }
} }

View File

@ -19,12 +19,14 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
class RoomSummaryProvider: RoomSummaryProviderProtocol { class RoomSummaryProvider: RoomSummaryProviderProtocol {
private let roomListService: RoomListProtocol private let roomListService: RoomListServiceProtocol
private let eventStringBuilder: RoomEventStringBuilder private let eventStringBuilder: RoomEventStringBuilder
private let name: String private let name: String
private let serialDispatchQueue: DispatchQueue private let serialDispatchQueue: DispatchQueue
private var roomList: RoomListProtocol?
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
private var listUpdatesTaskHandle: TaskHandle? private var listUpdatesTaskHandle: TaskHandle?
private var stateUpdatesTaskHandle: TaskHandle? private var stateUpdatesTaskHandle: TaskHandle?
@ -49,7 +51,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
} }
} }
init(roomListService: RoomListProtocol, init(roomListService: RoomListServiceProtocol,
eventStringBuilder: RoomEventStringBuilder, eventStringBuilder: RoomEventStringBuilder,
name: String) { name: String) {
self.roomListService = roomListService self.roomListService = roomListService
@ -63,37 +65,35 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
.store(in: &cancellables) .store(in: &cancellables)
} }
func subscribeIfNecessary(entriesFunction: EntriesFunction, func setRoomList(_ roomList: RoomList) {
entriesLoadingStateFunction: LoadingStateFunction?) async {
guard listUpdatesTaskHandle == nil, stateUpdatesTaskHandle == nil else { guard listUpdatesTaskHandle == nil, stateUpdatesTaskHandle == nil else {
return return
} }
self.roomList = roomList
do { do {
let listUpdatesSubscriptionResult = try await entriesFunction(RoomListEntriesListenerProxy { [weak self] update in let listUpdatesSubscriptionResult = try roomList.entries(listener: RoomListEntriesListenerProxy { [weak self] update in
guard let self else { return } guard let self else { return }
MXLog.verbose("\(name): Received list update") MXLog.verbose("\(name): Received list update")
diffPublisher.send(update) diffPublisher.send(update)
}) })
listUpdatesTaskHandle = listUpdatesSubscriptionResult.entriesStream listUpdatesTaskHandle = listUpdatesSubscriptionResult.entriesStream
rooms = listUpdatesSubscriptionResult.entries.map { roomListEntry in rooms = listUpdatesSubscriptionResult.entries.map { roomListEntry in
buildSummaryForRoomListEntry(roomListEntry) buildSummaryForRoomListEntry(roomListEntry)
} }
if let entriesLoadingStateFunction { let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: RoomListStateObserver { [weak self] state in
let stateUpdatesSubscriptionResult = try await entriesLoadingStateFunction(RoomListStateObserver { [weak self] state in guard let self else { return }
guard let self else { return } MXLog.info("\(name): Received state update: \(state)")
MXLog.info("\(name): Received state update: \(state)") stateSubject.send(RoomSummaryProviderState(roomListState: state))
stateSubject.send(RoomSummaryProviderState(slidingSyncState: state)) })
})
stateSubject.send(RoomSummaryProviderState(slidingSyncState: stateUpdatesSubscriptionResult.entriesLoadingState))
stateUpdatesTaskHandle = stateUpdatesSubscriptionResult.entriesLoadingStateStream
}
stateUpdatesTaskHandle = stateUpdatesSubscriptionResult.stateStream
stateSubject.send(RoomSummaryProviderState(roomListState: stateUpdatesSubscriptionResult.state))
} catch { } catch {
MXLog.error("Failed setting up room list entry listener with error: \(error)") MXLog.error("Failed setting up room list entry listener with error: \(error)")
} }
@ -266,16 +266,12 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
} }
extension RoomSummaryProviderState { extension RoomSummaryProviderState {
init(slidingSyncState: SlidingSyncListLoadingState) { init(roomListState: RoomListLoadingState) {
switch slidingSyncState { switch roomListState {
case .notLoaded: case .notLoaded:
self = .notLoaded self = .notLoaded
case .preloaded: case .loaded(let maximumNumberOfRooms):
self = .preloaded self = .loaded(totalNumberOfRooms: UInt(maximumNumberOfRooms ?? 0))
case .partiallyLoaded:
self = .partiallyLoaded
case .fullyLoaded:
self = .fullyLoaded
} }
} }
} }
@ -314,14 +310,14 @@ private class RoomListEntriesListenerProxy: RoomListEntriesListener {
} }
} }
private class RoomListStateObserver: SlidingSyncListStateObserver { private class RoomListStateObserver: RoomListLoadingStateListener {
private let onUpdateClosure: (SlidingSyncListLoadingState) -> Void private let onUpdateClosure: (RoomListLoadingState) -> Void
init(_ onUpdateClosure: @escaping (SlidingSyncListLoadingState) -> Void) { init(_ onUpdateClosure: @escaping (RoomListLoadingState) -> Void) {
self.onUpdateClosure = onUpdateClosure self.onUpdateClosure = onUpdateClosure
} }
func didReceiveUpdate(newState: SlidingSyncListLoadingState) { func onUpdate(state: RoomListLoadingState) {
onUpdateClosure(newState) onUpdateClosure(state)
} }
} }

View File

@ -20,9 +20,25 @@ import MatrixRustSDK
enum RoomSummaryProviderState { enum RoomSummaryProviderState {
case notLoaded case notLoaded
case preloaded case loaded(totalNumberOfRooms: UInt)
case partiallyLoaded
case fullyLoaded var isLoaded: Bool {
switch self {
case .loaded:
return true
default:
return false
}
}
var totalNumberOfRooms: UInt? {
switch self {
case .loaded(let totalNumberOfRooms):
return totalNumberOfRooms
default:
return nil
}
}
} }
enum RoomSummary: CustomStringConvertible { enum RoomSummary: CustomStringConvertible {
@ -61,19 +77,15 @@ enum RoomSummary: CustomStringConvertible {
} }
protocol RoomSummaryProviderProtocol { protocol RoomSummaryProviderProtocol {
typealias EntriesFunction = (RoomListEntriesListener) async throws -> RoomListEntriesResult
typealias LoadingStateFunction = (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult
/// Publishes the currently available room summaries /// Publishes the currently available room summaries
var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { get } var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { get }
/// Publishes the current state the summary provider is finding itself in /// Publishes the current state the summary provider is finding itself in
var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> { get } var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> { get }
/// A separate subscription method is needed instead of running this in the constructor because the invites list is added later on the Rust side. /// This is outside of the constructor because the invites list is added later on the Rust side.
/// Wanted to be able to build the InvitesSummaryProvider directly instead of having to inform the HomeScreenViewModel about it later /// Wanted to be able to build the InvitesSummaryProvider directly instead of having to inform the HomeScreenViewModel about it later
func subscribeIfNecessary(entriesFunction: EntriesFunction, func setRoomList(_ roomList: RoomList)
entriesLoadingStateFunction: LoadingStateFunction?) async
func updateVisibleRange(_ range: Range<Int>) func updateVisibleRange(_ range: Range<Int>)
} }

View File

@ -44,7 +44,7 @@ include:
packages: packages:
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.0.81-alpha exactVersion: 1.0.82-alpha
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
DesignKit: DesignKit:
path: DesignKit path: DesignKit