Add support for the new RoomList API (#1092)
* Add support for the new RoomList API Squashed commits: [8c2625a] Re-enable invites [339e59b5] Add automatic syncing restarts after entering the terminated state for whatever reason [b5498e96] Reimplemented room list state management and usage [8154b0cf] Fix visible rooms range setting after scrolling stops [5155c44d] Hook up initial loading idicator and session verification banner to room list service states [700a5a2e] Enable back room subscriptions, timeline listeners and unread notifications / counts [9df383bc] Enable last messages and timestamps, they don't crash on release builds [7c4da9da] Add back certain fields now that the "full room" is available [9f6534a7] Adopt interfaces to new RoomList API, untested as requests don't go to the right URL * Tweaks following code review * Bump the RustSDK to v1.0.76-alpha
This commit is contained in:
@@ -355,7 +355,6 @@
|
||||
8B1D5CE017EEC734CF5FE130 /* Encodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260004737C573A56FA01E86E /* Encodable.swift */; };
|
||||
8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */; };
|
||||
8B76191B9DDD1AC90A6E3A35 /* MediaFileHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */; };
|
||||
8B7771E319436E542412A22C /* SlidingSyncListProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074DA547928E85183066DB4A /* SlidingSyncListProxy.swift */; };
|
||||
8BC8EF6705A78946C1F22891 /* SoftLogoutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */; };
|
||||
8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
|
||||
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
|
||||
@@ -442,7 +441,6 @@
|
||||
A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */; };
|
||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||
A7F55D1C2A3B639A009D8E93 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */; };
|
||||
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
||||
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
|
||||
@@ -621,6 +619,7 @@
|
||||
E9F148072F9513EC2272AA21 /* SessionVerificationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */; };
|
||||
EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
|
||||
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; };
|
||||
EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */; };
|
||||
EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */; };
|
||||
EAC6FE2CD4F50A43068ADCD8 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
|
||||
EAF2B3E6C6AEC4AD3A8BD454 /* RoomMemberDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */; };
|
||||
@@ -747,7 +746,6 @@
|
||||
05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenModels.swift; sourceTree = "<group>"; };
|
||||
074DA547928E85183066DB4A /* SlidingSyncListProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingSyncListProxy.swift; sourceTree = "<group>"; };
|
||||
077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = "<group>"; };
|
||||
07E65E613F057697A1A0BC03 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = "<group>"; };
|
||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = "<group>"; };
|
||||
@@ -1124,7 +1122,6 @@
|
||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||
A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = "<group>"; };
|
||||
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
|
||||
A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = "<group>"; };
|
||||
@@ -1160,6 +1157,7 @@
|
||||
B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||
B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenModels.swift; sourceTree = "<group>"; };
|
||||
B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = "<group>"; };
|
||||
B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
@@ -1744,6 +1742,13 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = MessageForwardingScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
337015ADFBA3AB96660DB3A6 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1908,6 +1913,7 @@
|
||||
52BD6ED18E2EB61E28C340AD /* AttributedString.swift */,
|
||||
B6E89E530A8E92EC44301CA1 /* Bundle.swift */,
|
||||
A9FAFE1C2149E6AC8156ED2B /* Collection.swift */,
|
||||
B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */,
|
||||
2141693488CE5446BB391964 /* Date.swift */,
|
||||
BFDCAC6CAAD65A2C24EA9C4B /* Dictionary.swift */,
|
||||
260004737C573A56FA01E86E /* Encodable.swift */,
|
||||
@@ -1930,7 +1936,6 @@
|
||||
AE40D4A5DD857AC16EED945A /* URLSession.swift */,
|
||||
897DF5E9A70CE05A632FC8AF /* UTType.swift */,
|
||||
E992D7B8BE54B2AB454613AF /* XCUIElement.swift */,
|
||||
A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -2458,7 +2463,6 @@
|
||||
18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */,
|
||||
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */,
|
||||
3F40F48279322E504153AB0D /* MockClientProxy.swift */,
|
||||
074DA547928E85183066DB4A /* SlidingSyncListProxy.swift */,
|
||||
);
|
||||
path = Client;
|
||||
sourceTree = "<group>";
|
||||
@@ -3150,6 +3154,7 @@
|
||||
948DD12A5533BE1BC260E437 /* LocationSharing */,
|
||||
87E2774157D9C4894BCFF3F8 /* MediaPickerScreen */,
|
||||
23605DD08620BE6558242469 /* MediaUploadPreviewScreen */,
|
||||
3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */,
|
||||
3F38EAC92E2281990E65DAF2 /* OnboardingScreen */,
|
||||
A448A3A8F764174C60CD0CA1 /* Other */,
|
||||
5970F275D6014548DCED6106 /* ReportContentScreen */,
|
||||
@@ -3909,6 +3914,7 @@
|
||||
9FAF6DA7E8E85C9699757764 /* CollapsibleRoomTimelineView.swift in Sources */,
|
||||
0DC815CA24E1BD7F408F37D3 /* CollapsibleTimelineItem.swift in Sources */,
|
||||
663E198678778F7426A9B27D /* Collection.swift in Sources */,
|
||||
EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */,
|
||||
AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */,
|
||||
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */,
|
||||
564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */,
|
||||
@@ -4191,7 +4197,6 @@
|
||||
A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */,
|
||||
8922219C5C934C4155E8CA50 /* SharedUserDefaultsKeys.swift in Sources */,
|
||||
274CE3C986841D15FD530BF5 /* ShimmerModifier.swift in Sources */,
|
||||
8B7771E319436E542412A22C /* SlidingSyncListProxy.swift in Sources */,
|
||||
8BC8EF6705A78946C1F22891 /* SoftLogoutScreen.swift in Sources */,
|
||||
A3A7A05E8F9B7EB0E1A09A2A /* SoftLogoutScreenCoordinator.swift in Sources */,
|
||||
F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */,
|
||||
@@ -4209,7 +4214,6 @@
|
||||
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */,
|
||||
1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */,
|
||||
DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */,
|
||||
A7F55D1C2A3B639A009D8E93 /* ConfirmationDialog.swift in Sources */,
|
||||
5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */,
|
||||
C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */,
|
||||
F32B271F60531BE92C6E62A1 /* StickerRoomTimelineView.swift in Sources */,
|
||||
@@ -4944,7 +4948,7 @@
|
||||
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = "1.0.75-alpha";
|
||||
version = "1.0.76-alpha";
|
||||
};
|
||||
};
|
||||
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"identity" : "compound-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vector-im/compound-ios.git",
|
||||
"location" : "https://github.com/vector-im/compound-ios",
|
||||
"state" : {
|
||||
"revision" : "d59c317362beba940baa43d6aacdd357e208048d"
|
||||
}
|
||||
@@ -111,8 +111,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
||||
"state" : {
|
||||
"revision" : "c6e794f305a34453ed081be131b2eea719154bc5",
|
||||
"version" : "1.0.75-alpha"
|
||||
"revision" : "06e154e94138e93fd4609471e67fb56f2daef472",
|
||||
"version" : "1.0.76-alpha"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -404,23 +404,6 @@ class SDKClientMock: SDKClientProtocol {
|
||||
restoreSessionSessionReceivedInvocations.append(`session`)
|
||||
try restoreSessionSessionClosure?(`session`)
|
||||
}
|
||||
//MARK: - `rooms`
|
||||
|
||||
public var roomsCallsCount = 0
|
||||
public var roomsCalled: Bool {
|
||||
return roomsCallsCount > 0
|
||||
}
|
||||
public var roomsReturnValue: [Room]!
|
||||
public var roomsClosure: (() -> [Room])?
|
||||
|
||||
public func `rooms`() -> [Room] {
|
||||
roomsCallsCount += 1
|
||||
if let roomsClosure = roomsClosure {
|
||||
return roomsClosure()
|
||||
} else {
|
||||
return roomsReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - `roomList`
|
||||
|
||||
public var roomListThrowableError: Error?
|
||||
@@ -442,6 +425,23 @@ class SDKClientMock: SDKClientProtocol {
|
||||
return roomListReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - `rooms`
|
||||
|
||||
public var roomsCallsCount = 0
|
||||
public var roomsCalled: Bool {
|
||||
return roomsCallsCount > 0
|
||||
}
|
||||
public var roomsReturnValue: [Room]!
|
||||
public var roomsClosure: (() -> [Room])?
|
||||
|
||||
public func `rooms`() -> [Room] {
|
||||
roomsCallsCount += 1
|
||||
if let roomsClosure = roomsClosure {
|
||||
return roomsClosure()
|
||||
} else {
|
||||
return roomsReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - `searchUsers`
|
||||
|
||||
public var searchUsersSearchTermLimitThrowableError: Error?
|
||||
|
||||
@@ -21,9 +21,8 @@ typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState, Hom
|
||||
|
||||
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
|
||||
private let userSession: UserSessionProtocol
|
||||
private let visibleRoomsSummaryProvider: RoomSummaryProviderProtocol?
|
||||
private let allRoomsSummaryProvider: RoomSummaryProviderProtocol?
|
||||
private let invitesSummaryProvider: RoomSummaryProviderProtocol?
|
||||
private let roomSummaryProvider: RoomSummaryProviderProtocol?
|
||||
private let inviteSummaryProvider: RoomSummaryProviderProtocol?
|
||||
private let attributedStringBuilder: AttributedStringBuilderProtocol
|
||||
|
||||
private var visibleItemRangeObservationToken: AnyCancellable?
|
||||
@@ -36,9 +35,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
self.userSession = userSession
|
||||
self.attributedStringBuilder = attributedStringBuilder
|
||||
|
||||
visibleRoomsSummaryProvider = userSession.clientProxy.visibleRoomsSummaryProvider
|
||||
allRoomsSummaryProvider = userSession.clientProxy.allRoomsSummaryProvider
|
||||
invitesSummaryProvider = userSession.clientProxy.invitesSummaryProvider
|
||||
roomSummaryProvider = userSession.clientProxy.roomSummaryProvider
|
||||
inviteSummaryProvider = userSession.clientProxy.inviteSummaryProvider
|
||||
|
||||
super.init(initialViewState: HomeScreenViewState(userID: userSession.userID),
|
||||
imageProvider: userSession.mediaProvider)
|
||||
@@ -61,21 +59,19 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
guard let visibleRoomsSummaryProvider, let allRoomsSummaryProvider, let invitesSummaryProvider else {
|
||||
guard let roomSummaryProvider, let inviteSummaryProvider else {
|
||||
MXLog.error("Room summary provider unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
// Combine all 3 publishers to correctly compute the screen state
|
||||
Publishers.CombineLatest3(visibleRoomsSummaryProvider.statePublisher,
|
||||
visibleRoomsSummaryProvider.countPublisher,
|
||||
visibleRoomsSummaryProvider.roomListPublisher)
|
||||
Publishers.CombineLatest(roomSummaryProvider.statePublisher,
|
||||
roomSummaryProvider.roomListPublisher)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] roomSummaryProviderState, totalCount, rooms in
|
||||
.sink { [weak self] state, rooms in
|
||||
guard let self else { return }
|
||||
|
||||
let isLoadingData = roomSummaryProviderState != .fullyLoaded && (totalCount == 0 || rooms.count < totalCount)
|
||||
let hasNoRooms = roomSummaryProviderState == .fullyLoaded && totalCount == 0
|
||||
let isLoadingData = state == .notLoaded
|
||||
let hasNoRooms = (state == .fullyLoaded && rooms.count == 0)
|
||||
|
||||
var roomListMode = self.state.roomListMode
|
||||
if isLoadingData {
|
||||
@@ -93,9 +89,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
self.state.roomListMode = roomListMode
|
||||
|
||||
MXLog.info("Received visibleRoomsSummaryProvider update, setting view room list mode to \"\(self.state.roomListMode)\"")
|
||||
if roomListMode == .skeletons {
|
||||
MXLog.info("roomSummaryProviderState: \(roomSummaryProviderState). rooms.count: \(rooms.count) / totalCount: \(totalCount)")
|
||||
}
|
||||
|
||||
// Delay user profile detail loading until after the initial room list loads
|
||||
if roomListMode == .rooms {
|
||||
@@ -112,16 +105,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
// Listen to changes from both roomSummaryProviders and update state accordingly
|
||||
|
||||
visibleRoomsSummaryProvider.roomListPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.updateRooms()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
allRoomsSummaryProvider.roomListPublisher
|
||||
roomSummaryProvider.roomListPublisher
|
||||
.dropFirst(1) // We don't care about its initial value
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
@@ -129,11 +113,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
// Wait for the all rooms view to receive its first update before installing
|
||||
// dynamic timeline modifiers
|
||||
self?.installDynamicTimelineLimitModifiers()
|
||||
self?.installListRangeModifiers()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
invitesSummaryProvider.roomListPublisher
|
||||
inviteSummaryProvider.roomListPublisher
|
||||
.combineLatest(ServiceLocator.shared.settings.$seenInvites)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] summaries, readInvites in
|
||||
@@ -196,15 +180,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// We want the timeline limit to be set to 1 while scrolling the list so that last messages load up fast. We also want to set that back to 20 when the scrolling
|
||||
/// stops to load room history. Also we don't want this to be setup before the initial sync is over so we only call it when the allRoomsSummaryProvider
|
||||
/// first receives some changes
|
||||
private func installDynamicTimelineLimitModifiers() {
|
||||
private func installListRangeModifiers() {
|
||||
guard visibleItemRangeObservationToken == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
visibleItemRangeObservationToken = visibleItemRangePublisher
|
||||
.filter { $0.isScrolling == false }
|
||||
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
|
||||
.removeDuplicates(by: { $0.isScrolling == $1.isScrolling && $0.range == $1.range })
|
||||
.sink { [weak self] value in
|
||||
@@ -215,15 +197,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
return
|
||||
}
|
||||
|
||||
self.updateVisibleRange(value.range, timelineLimit: value.isScrolling ? SlidingSyncConstants.lastMessageTimelineLimit : SlidingSyncConstants.timelinePrecachingTimelineLimit)
|
||||
self.updateVisibleRange(value.range)
|
||||
}
|
||||
}
|
||||
|
||||
/// This method will update all view state rooms by merging the data from both summary providers
|
||||
/// If a room is empty in the visible room summary provider it will try to get it from the allRooms one
|
||||
/// This ensures that we show as many room details as possible without loading up timelines
|
||||
private func updateRooms() {
|
||||
guard let visibleRoomsSummaryProvider else {
|
||||
guard let roomSummaryProvider else {
|
||||
MXLog.error("Room summary provider unavailable")
|
||||
return
|
||||
}
|
||||
@@ -231,50 +210,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
MXLog.info("Updating rooms")
|
||||
|
||||
var rooms = [HomeScreenRoom]()
|
||||
var createdRoomIdentifiers = [String: Bool]()
|
||||
|
||||
// This works around duplicated room list items which happens because the 2 different ss lists used
|
||||
// update at different times. That will be fixed once we move this logic to the Rust side
|
||||
func appendRoom(_ room: HomeScreenRoom, allRoomsProvider: Bool) {
|
||||
guard createdRoomIdentifiers[room.id] == nil else {
|
||||
MXLog.error("Built duplicated room for identifier: \(room.id). AllRoomsSummaryProvider: \(allRoomsProvider). Ignoring")
|
||||
return
|
||||
}
|
||||
|
||||
createdRoomIdentifiers[room.id] = true
|
||||
rooms.append(room)
|
||||
}
|
||||
|
||||
// Try merging together results from both the visibleRoomsSummaryProvider and the allRoomsSummaryProvider
|
||||
// Empty or invalidated items in the visibleRoomsSummaryProvider might have more details in the allRoomsSummaryProvider
|
||||
// If items are unavailable in the allRoomsSummaryProvider (hasn't be added to SS yet / cold cache) then use what's available
|
||||
for (index, summary) in visibleRoomsSummaryProvider.roomListPublisher.value.enumerated() {
|
||||
for summary in roomSummaryProvider.roomListPublisher.value {
|
||||
switch summary {
|
||||
case .empty:
|
||||
rooms.append(HomeScreenRoom.placeholder())
|
||||
case .filled(let details):
|
||||
let room = buildRoom(with: details, invalidated: false, isLoading: false)
|
||||
appendRoom(room, allRoomsProvider: false)
|
||||
case .empty, .invalidated:
|
||||
// Try getting details from the allRoomsSummaryProvider
|
||||
guard let allRoomsRoomSummary = allRoomsSummaryProvider?.roomListPublisher.value[safe: index] else {
|
||||
if case let .invalidated(details) = summary {
|
||||
let room = buildRoom(with: details, invalidated: true, isLoading: false)
|
||||
appendRoom(room, allRoomsProvider: true)
|
||||
} else {
|
||||
rooms.append(HomeScreenRoom.placeholder())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch allRoomsRoomSummary {
|
||||
case .empty:
|
||||
rooms.append(HomeScreenRoom.placeholder())
|
||||
case .filled(let details):
|
||||
let room = buildRoom(with: details, invalidated: false, isLoading: true)
|
||||
appendRoom(room, allRoomsProvider: true)
|
||||
case .invalidated(let details):
|
||||
let room = buildRoom(with: details, invalidated: true, isLoading: true)
|
||||
appendRoom(room, allRoomsProvider: true)
|
||||
}
|
||||
let room = buildRoom(with: details)
|
||||
rooms.append(room)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,29 +226,27 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
MXLog.info("Finished updating rooms")
|
||||
}
|
||||
|
||||
private func buildRoom(with details: RoomSummaryDetails, invalidated: Bool, isLoading: Bool) -> HomeScreenRoom {
|
||||
let identifier = invalidated ? "invalidated-" + details.id : details.id
|
||||
|
||||
return HomeScreenRoom(id: identifier,
|
||||
roomId: details.id,
|
||||
name: details.name,
|
||||
hasUnreads: details.unreadNotificationCount > 0,
|
||||
timestamp: details.lastMessageFormattedTimestamp,
|
||||
lastMessage: .init(attributedString: details.lastMessage, isLoading: isLoading),
|
||||
avatarURL: details.avatarURL)
|
||||
private func buildRoom(with details: RoomSummaryDetails) -> HomeScreenRoom {
|
||||
HomeScreenRoom(id: details.id,
|
||||
roomId: details.id,
|
||||
name: details.name,
|
||||
hasUnreads: details.unreadNotificationCount > 0,
|
||||
timestamp: details.lastMessageFormattedTimestamp,
|
||||
lastMessage: .init(attributedString: details.lastMessage, isLoading: false),
|
||||
avatarURL: details.avatarURL)
|
||||
}
|
||||
|
||||
private func updateVisibleRange(_ range: Range<Int>, timelineLimit: UInt) {
|
||||
private func updateVisibleRange(_ range: Range<Int>) {
|
||||
guard !range.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let visibleRoomsSummaryProvider else {
|
||||
guard let roomSummaryProvider else {
|
||||
MXLog.error("Visible rooms summary provider unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
visibleRoomsSummaryProvider.updateVisibleRange(range, timelineLimit: timelineLimit)
|
||||
roomSummaryProvider.updateVisibleRange(range)
|
||||
}
|
||||
|
||||
private static let leaveRoomLoadingID = "LeaveRoomLoading"
|
||||
|
||||
@@ -177,7 +177,7 @@ struct HomeScreenRoomCell_Previews: PreviewProvider {
|
||||
switch summary {
|
||||
case .empty:
|
||||
return nil
|
||||
case .filled(let details), .invalidated(let details):
|
||||
case .filled(let details):
|
||||
return HomeScreenRoom(id: UUID().uuidString,
|
||||
roomId: details.id,
|
||||
name: details.name,
|
||||
|
||||
@@ -52,17 +52,17 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel
|
||||
userSession.clientProxy
|
||||
}
|
||||
|
||||
private var invitesSummaryProvider: RoomSummaryProviderProtocol? {
|
||||
clientProxy.invitesSummaryProvider
|
||||
private var inviteSummaryProvider: RoomSummaryProviderProtocol? {
|
||||
clientProxy.inviteSummaryProvider
|
||||
}
|
||||
|
||||
private func setupSubscriptions() {
|
||||
guard let invitesSummaryProvider else {
|
||||
guard let inviteSummaryProvider else {
|
||||
MXLog.error("Room summary provider unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
invitesSummaryProvider.roomListPublisher
|
||||
inviteSummaryProvider.roomListPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] roomSummaries in
|
||||
guard let self else { return }
|
||||
|
||||
@@ -78,8 +78,8 @@ private extension InvitesScreenViewModel {
|
||||
|
||||
static let someInvite: InvitesScreenViewModel = {
|
||||
let clientProxy = MockClientProxy(userID: "@userid:example.com")
|
||||
clientProxy.invitesSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||
clientProxy.visibleRoomsSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||
clientProxy.inviteSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||
clientProxy.roomSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||
let userSession = MockUserSession(clientProxy: clientProxy,
|
||||
mediaProvider: MockMediaProvider())
|
||||
let regularViewModel = InvitesScreenViewModel(userSession: userSession)
|
||||
|
||||
@@ -31,7 +31,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
private let timelineController: RoomTimelineControllerProtocol
|
||||
private unowned let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private var loadingTask: Task<Void, Never>?
|
||||
|
||||
|
||||
init(timelineController: RoomTimelineControllerProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
roomProxy: RoomProxyProtocol,
|
||||
|
||||
@@ -19,7 +19,7 @@ import Foundation
|
||||
import MatrixRustSDK
|
||||
import UIKit
|
||||
|
||||
private class WeakClientProxyWrapper: ClientDelegate, SlidingSyncObserver {
|
||||
private class WeakClientProxyWrapper: ClientDelegate {
|
||||
private weak var clientProxy: ClientProxy?
|
||||
|
||||
init(clientProxy: ClientProxy) {
|
||||
@@ -37,13 +37,6 @@ private class WeakClientProxyWrapper: ClientDelegate, SlidingSyncObserver {
|
||||
MXLog.info("The session has updated tokens.")
|
||||
clientProxy?.updateRestorationToken()
|
||||
}
|
||||
|
||||
// MARK: - SlidingSyncDelegate
|
||||
|
||||
func didReceiveSyncUpdate(summary: UpdateSummary) {
|
||||
MXLog.info("Received sliding sync update")
|
||||
clientProxy?.didReceiveSlidingSyncUpdate(summary: summary)
|
||||
}
|
||||
}
|
||||
|
||||
class ClientProxy: ClientProxyProtocol {
|
||||
@@ -52,22 +45,13 @@ class ClientProxy: ClientProxyProtocol {
|
||||
private var sessionVerificationControllerProxy: SessionVerificationControllerProxy?
|
||||
private let mediaLoader: MediaLoaderProtocol
|
||||
private let clientQueue: DispatchQueue
|
||||
|
||||
private var roomListService: RoomList?
|
||||
private var roomListSyncTaskHandle: TaskHandle?
|
||||
private var roomListStateUpdateTaskHandle: TaskHandle?
|
||||
|
||||
private var slidingSyncObserverToken: TaskHandle?
|
||||
private var slidingSync: SlidingSyncProtocol?
|
||||
private var slidingSyncTasks = [TaskHandle?]()
|
||||
|
||||
var visibleRoomsListBuilder: SlidingSyncListBuilder?
|
||||
var visibleRoomsListProxy: SlidingSyncListProxy?
|
||||
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol?
|
||||
|
||||
var allRoomsListBuilder: SlidingSyncListBuilder?
|
||||
var allRoomsListProxy: SlidingSyncListProxy?
|
||||
var allRoomsSummaryProvider: RoomSummaryProviderProtocol?
|
||||
|
||||
var invitesListBuilder: SlidingSyncListBuilder?
|
||||
var invitesListProxy: SlidingSyncListProxy?
|
||||
var invitesSummaryProvider: RoomSummaryProviderProtocol?
|
||||
var roomSummaryProvider: RoomSummaryProviderProtocol?
|
||||
var inviteSummaryProvider: RoomSummaryProviderProtocol?
|
||||
|
||||
private let roomListRecencyOrderingAllowedEventTypes = ["m.room.message", "m.room.encrypted", "m.sticker"]
|
||||
|
||||
@@ -87,8 +71,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
// These need to be inlined instead of using stopSync()
|
||||
// as we can't call async methods safely from deinit
|
||||
client.setDelegate(delegate: nil)
|
||||
slidingSyncObserverToken?.cancel()
|
||||
slidingSync?.setObserver(observer: nil)
|
||||
roomListSyncTaskHandle?.cancel()
|
||||
}
|
||||
|
||||
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
|
||||
@@ -103,7 +86,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
let delegate = WeakClientProxyWrapper(clientProxy: self)
|
||||
client.setDelegate(delegate: delegate)
|
||||
|
||||
configureSlidingSync()
|
||||
await configureRoomListService()
|
||||
|
||||
loadUserAvatarURLFromCache()
|
||||
}
|
||||
@@ -140,7 +123,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
|
||||
var isSyncing: Bool {
|
||||
slidingSyncObserverToken != nil
|
||||
roomListSyncTaskHandle != nil
|
||||
}
|
||||
|
||||
func startSync() {
|
||||
@@ -149,18 +132,13 @@ class ClientProxy: ClientProxyProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
slidingSyncObserverToken = slidingSync?.sync()
|
||||
roomListSyncTaskHandle = roomListService?.sync()
|
||||
}
|
||||
|
||||
func stopSync() {
|
||||
MXLog.info("Stopping sync")
|
||||
slidingSyncObserverToken = nil
|
||||
|
||||
do {
|
||||
try slidingSync?.stopSync()
|
||||
} catch {
|
||||
MXLog.error("Failed stopping sync with error: \(error). Ignore this error if ran just before the app being suspended")
|
||||
}
|
||||
roomListSyncTaskHandle?.cancel()
|
||||
roomListSyncTaskHandle = nil
|
||||
}
|
||||
|
||||
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
||||
@@ -229,7 +207,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
private func waitForRoomSummary(with result: Result<String, ClientProxyError>, name: String?) async -> Result<String, ClientProxyError> {
|
||||
guard case .success(let roomId) = result else { return result }
|
||||
let runner = ExpiringTaskRunner { [weak self] in
|
||||
guard let roomLists = self?.allRoomsSummaryProvider?.roomListPublisher.values else {
|
||||
guard let roomLists = self?.roomSummaryProvider?.roomListPublisher.values else {
|
||||
return
|
||||
}
|
||||
// for every list of summaries, we check if we have a room summary with matching ID and name (if present)
|
||||
@@ -249,42 +227,42 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
|
||||
// Try fetching the room from the cold cache (if available) first
|
||||
var (slidingSyncRoom, room) = await Task.dispatch(on: clientQueue) {
|
||||
var (roomListItem, room) = await Task.dispatch(on: clientQueue) {
|
||||
self.roomTupleForIdentifier(identifier)
|
||||
}
|
||||
|
||||
if let slidingSyncRoom, let room {
|
||||
return await RoomProxy(slidingSyncRoom: slidingSyncRoom,
|
||||
if let roomListItem, let room {
|
||||
return await RoomProxy(roomListItem: roomListItem,
|
||||
room: room,
|
||||
backgroundTaskService: backgroundTaskService)
|
||||
}
|
||||
|
||||
// Else wait for the visible rooms list to go into fully loaded
|
||||
|
||||
guard let visibleRoomsSummaryProvider else {
|
||||
MXLog.error("Visible rooms summary provider not setup yet")
|
||||
guard let roomSummaryProvider else {
|
||||
MXLog.error("Rooms summary provider not setup yet")
|
||||
return nil
|
||||
}
|
||||
|
||||
if visibleRoomsSummaryProvider.statePublisher.value != .fullyLoaded {
|
||||
_ = await visibleRoomsSummaryProvider.statePublisher.values.first(where: { $0 == .fullyLoaded })
|
||||
if roomSummaryProvider.statePublisher.value != .fullyLoaded {
|
||||
_ = await roomSummaryProvider.statePublisher.values.first(where: { $0 == .fullyLoaded })
|
||||
}
|
||||
|
||||
(slidingSyncRoom, room) = await Task.dispatch(on: clientQueue) {
|
||||
(roomListItem, room) = await Task.dispatch(on: clientQueue) {
|
||||
self.roomTupleForIdentifier(identifier)
|
||||
}
|
||||
|
||||
guard let slidingSyncRoom else {
|
||||
MXLog.error("Invalid slidingSyncRoom for identifier \(identifier)")
|
||||
|
||||
guard let roomListItem else {
|
||||
MXLog.error("Invalid roomListItem for identifier \(identifier)")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let room else {
|
||||
MXLog.error("Invalid slidingSyncRoom fullRoom for identifier \(identifier)")
|
||||
MXLog.error("Invalid roomListItem fullRoom for identifier \(identifier)")
|
||||
return nil
|
||||
}
|
||||
|
||||
return await RoomProxy(slidingSyncRoom: slidingSyncRoom,
|
||||
|
||||
return await RoomProxy(roomListItem: roomListItem,
|
||||
room: room,
|
||||
backgroundTaskService: backgroundTaskService)
|
||||
}
|
||||
@@ -378,6 +356,11 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func restartSync() {
|
||||
stopSync()
|
||||
startSync()
|
||||
}
|
||||
|
||||
private func loadUserAvatarURLFromCache() {
|
||||
loadCachedAvatarURLTask = Task {
|
||||
@@ -394,210 +377,59 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func configureSlidingSync() {
|
||||
guard slidingSync == nil else {
|
||||
private func configureRoomListService() async {
|
||||
guard roomListService == nil else {
|
||||
fatalError("This shouldn't be called more than once")
|
||||
}
|
||||
|
||||
do {
|
||||
let slidingSyncBuilder = try client.slidingSync(id: "ElementX")
|
||||
let roomListService = try client.roomList()
|
||||
roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
|
||||
guard let self else { return }
|
||||
MXLog.info("Received room list update: \(state)")
|
||||
|
||||
if state == .allRooms || state == .carryOn {
|
||||
self.callbacks.send(.receivedSyncUpdate)
|
||||
}
|
||||
|
||||
#warning("Not great, not terrible (ツ)")
|
||||
if state == .terminated {
|
||||
self.restartSync()
|
||||
}
|
||||
|
||||
if state == .allRooms {
|
||||
Task {
|
||||
// Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms
|
||||
await self.inviteSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.invites(listener:),
|
||||
// This state function is wrong but it's not used
|
||||
entriesLoadingStateFunction: roomListService.entriesLoadingState(listener:))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// List observers need to be setup before calling build() on the SlidingSyncBuilder otherwise
|
||||
// cold cache state and count updates will be lost
|
||||
buildAndConfigureVisibleRoomsSlidingSyncList()
|
||||
buildAndConfigureAllRoomsSlidingSyncList()
|
||||
buildAndConfigureInvitesSlidingSyncList()
|
||||
|
||||
guard let visibleRoomsListBuilder else {
|
||||
MXLog.error("Visible rooms sliding sync view unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
// Add the visibleRoomsSlidingSyncList here so that it can take advantage of the SS builder cold cache
|
||||
// We will still register the allRoomsSlidingSyncList later, and than will have no cache
|
||||
var slidingSyncBuilderWithExtension = slidingSyncBuilder
|
||||
.addList(listBuilder: visibleRoomsListBuilder)
|
||||
.withCommonExtensions()
|
||||
|
||||
if !ServiceLocator.shared.settings.readReceiptsEnabled {
|
||||
slidingSyncBuilderWithExtension = slidingSyncBuilderWithExtension.withoutReceiptExtension()
|
||||
}
|
||||
|
||||
let slidingSync = try slidingSyncBuilderWithExtension.build()
|
||||
|
||||
// Don't forget to update the view proxies after building the slidingSync
|
||||
visibleRoomsListProxy?.setSlidingSync(slidingSync: slidingSync)
|
||||
allRoomsListProxy?.setSlidingSync(slidingSync: slidingSync)
|
||||
invitesListProxy?.setSlidingSync(slidingSync: slidingSync)
|
||||
|
||||
// Build the room summary providers later so the sliding sync view proxies are up to date and the
|
||||
// currentRoomList is populated with the data from the cold cache
|
||||
buildRoomSummaryProviders()
|
||||
|
||||
slidingSync.setObserver(observer: WeakClientProxyWrapper(clientProxy: self))
|
||||
|
||||
self.slidingSync = slidingSync
|
||||
} catch {
|
||||
MXLog.error("Failed building sliding sync with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func buildAndConfigureVisibleRoomsSlidingSyncList() {
|
||||
guard visibleRoomsListBuilder == nil else {
|
||||
fatalError("This shouldn't be called more than once")
|
||||
}
|
||||
|
||||
let listName = "CurrentlyVisibleRooms"
|
||||
|
||||
let visibleRoomsListProxy = SlidingSyncListProxy(name: listName)
|
||||
|
||||
let visibleRoomsListBuilder = SlidingSyncListBuilder(name: listName)
|
||||
.timelineLimit(limit: UInt32(SlidingSyncConstants.initialTimelineLimit)) // Starts off with zero to quickly load rooms, then goes to 1 while scrolling to quickly load last messages and 20 when the scrolling stops to load room history
|
||||
.requiredState(requiredState: slidingSyncRequiredState)
|
||||
.filters(filters: slidingSyncFilters)
|
||||
.syncModeSelective(selectiveModeBuilder: .init().addRange(start: 0, endInclusive: 20))
|
||||
.bumpEventTypes(bumpEventTypes: roomListRecencyOrderingAllowedEventTypes)
|
||||
.onceBuilt(callback: visibleRoomsListProxy)
|
||||
|
||||
self.visibleRoomsListBuilder = visibleRoomsListBuilder
|
||||
self.visibleRoomsListProxy = visibleRoomsListProxy
|
||||
|
||||
// The allRoomsSlidingSyncList will be registered as soon as the visibleRoomsSlidingSyncList receives its first update
|
||||
visibleRoomsListProxyStateObservationToken = visibleRoomsListProxy.statePublisher.sink { [weak self] state in
|
||||
guard state == .fullyLoaded else {
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.info("Visible rooms view received first update, configuring views post initial sync")
|
||||
self?.configureViewsPostInitialSync()
|
||||
self?.visibleRoomsListProxyStateObservationToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func buildAndConfigureAllRoomsSlidingSyncList() {
|
||||
guard allRoomsListBuilder == nil else {
|
||||
fatalError("This shouldn't be called more than once")
|
||||
}
|
||||
|
||||
let listName = "AllRooms"
|
||||
|
||||
let allRoomsListProxy = SlidingSyncListProxy(name: listName)
|
||||
|
||||
let allRoomsListBuilder = SlidingSyncListBuilder(name: listName)
|
||||
.noTimelineLimit()
|
||||
.requiredState(requiredState: slidingSyncRequiredState)
|
||||
.filters(filters: slidingSyncFilters)
|
||||
.syncModeGrowing(batchSize: 100, maximumNumberOfRoomsToFetch: nil)
|
||||
.bumpEventTypes(bumpEventTypes: roomListRecencyOrderingAllowedEventTypes)
|
||||
.onceBuilt(callback: allRoomsListProxy)
|
||||
|
||||
self.allRoomsListBuilder = allRoomsListBuilder
|
||||
self.allRoomsListProxy = allRoomsListProxy
|
||||
}
|
||||
|
||||
private func buildAndConfigureInvitesSlidingSyncList() {
|
||||
guard invitesListBuilder == nil else {
|
||||
fatalError("This shouldn't be called more than once")
|
||||
}
|
||||
|
||||
let listName = "Invites"
|
||||
|
||||
let invitesListProxy = SlidingSyncListProxy(name: "Invites")
|
||||
|
||||
let invitesListBuilder = SlidingSyncListBuilder(name: listName)
|
||||
.noTimelineLimit()
|
||||
.requiredState(requiredState: slidingSyncInvitesRequiredState)
|
||||
.filters(filters: slidingSyncInviteFilters)
|
||||
.syncModeGrowing(batchSize: 100, maximumNumberOfRoomsToFetch: nil)
|
||||
.onceBuilt(callback: invitesListProxy)
|
||||
|
||||
self.invitesListBuilder = invitesListBuilder
|
||||
self.invitesListProxy = invitesListProxy
|
||||
}
|
||||
|
||||
private func buildRoomSummaryProviders() {
|
||||
guard visibleRoomsSummaryProvider == nil, allRoomsSummaryProvider == nil, invitesSummaryProvider == nil else {
|
||||
fatalError("This shouldn't be called more than once")
|
||||
}
|
||||
|
||||
guard let visibleRoomsListProxy, let allRoomsListProxy, let invitesListProxy else {
|
||||
MXLog.error("Sliding sync view proxies unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncListProxy: visibleRoomsListProxy,
|
||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
||||
name: visibleRoomsListProxy.name)
|
||||
|
||||
allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncListProxy: allRoomsListProxy,
|
||||
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
||||
name: allRoomsListProxy.name)
|
||||
|
||||
invitesSummaryProvider = RoomSummaryProvider(slidingSyncListProxy: invitesListProxy,
|
||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
||||
name: invitesListProxy.name)
|
||||
}
|
||||
|
||||
private lazy var slidingSyncRequiredState = [RequiredState(key: "m.room.avatar", value: ""),
|
||||
RequiredState(key: "m.room.encryption", value: ""),
|
||||
RequiredState(key: "m.room.power_levels", value: "")]
|
||||
|
||||
private lazy var slidingSyncInvitesRequiredState = [RequiredState(key: "m.room.avatar", value: ""),
|
||||
RequiredState(key: "m.room.encryption", value: ""),
|
||||
RequiredState(key: "m.room.member", value: "$ME"),
|
||||
RequiredState(key: "m.room.canonical_alias", value: "")]
|
||||
|
||||
private lazy var slidingSyncFilters = SlidingSyncRequestListFilters(isDm: nil,
|
||||
spaces: [],
|
||||
isEncrypted: nil,
|
||||
isInvite: false,
|
||||
isTombstoned: false,
|
||||
roomTypes: [],
|
||||
notRoomTypes: ["m.space"],
|
||||
roomNameLike: nil,
|
||||
tags: [],
|
||||
notTags: [])
|
||||
|
||||
private lazy var slidingSyncInviteFilters = SlidingSyncRequestListFilters(isDm: nil,
|
||||
spaces: [],
|
||||
isEncrypted: nil,
|
||||
isInvite: true,
|
||||
isTombstoned: false,
|
||||
roomTypes: [],
|
||||
notRoomTypes: ["m.space"],
|
||||
roomNameLike: nil,
|
||||
tags: [],
|
||||
notTags: [])
|
||||
|
||||
private func configureViewsPostInitialSync() {
|
||||
if let visibleRoomsListProxy {
|
||||
MXLog.info("Setting visible rooms view timeline limit to \(SlidingSyncConstants.lastMessageTimelineLimit)")
|
||||
visibleRoomsListProxy.updateVisibleRange(nil, timelineLimit: SlidingSyncConstants.lastMessageTimelineLimit)
|
||||
} else {
|
||||
MXLog.error("Visible rooms sliding sync view unavailable")
|
||||
}
|
||||
|
||||
if let allRoomsListBuilder {
|
||||
MXLog.info("Registering all rooms view")
|
||||
slidingSyncTasks.append(slidingSync?.addList(listBuilder: allRoomsListBuilder))
|
||||
} else {
|
||||
MXLog.error("All rooms sliding sync view unavailable")
|
||||
}
|
||||
|
||||
if let invitesListBuilder {
|
||||
MXLog.info("Registering invites view")
|
||||
slidingSyncTasks.append(slidingSync?.addList(listBuilder: invitesListBuilder))
|
||||
} else {
|
||||
MXLog.error("Invites sliding sync view unavailable")
|
||||
name: "AllRooms")
|
||||
|
||||
await roomSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.entries(listener:),
|
||||
entriesLoadingStateFunction: roomListService.entriesLoadingState(listener:))
|
||||
|
||||
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
||||
name: "Invites")
|
||||
|
||||
self.roomListService = roomListService
|
||||
} catch {
|
||||
MXLog.error("Failed building room list service with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func roomTupleForIdentifier(_ identifier: String) -> (SlidingSyncRoom?, Room?) {
|
||||
private func roomTupleForIdentifier(_ identifier: String) -> (RoomListItem?, Room?) {
|
||||
do {
|
||||
let slidingSyncRoom = try slidingSync?.getRoom(roomId: identifier)
|
||||
let fullRoom = slidingSyncRoom?.fullRoom()
|
||||
|
||||
return (slidingSyncRoom, fullRoom)
|
||||
let roomListItem = try roomListService?.room(roomId: identifier)
|
||||
let fullRoom = roomListItem?.fullRoom()
|
||||
|
||||
return (roomListItem, fullRoom)
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room with identifier: \(identifier)")
|
||||
return (nil, nil)
|
||||
@@ -611,10 +443,6 @@ class ClientProxy: ClientProxyProtocol {
|
||||
fileprivate func didReceiveAuthError(isSoftLogout: Bool) {
|
||||
callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout))
|
||||
}
|
||||
|
||||
fileprivate func didReceiveSlidingSyncUpdate(summary: UpdateSummary) {
|
||||
callbacks.send(.receivedSyncUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
extension ClientProxy: MediaLoaderProtocol {
|
||||
@@ -630,3 +458,15 @@ extension ClientProxy: MediaLoaderProtocol {
|
||||
try await mediaLoader.loadMediaFileForSource(source, body: body)
|
||||
}
|
||||
}
|
||||
|
||||
private class RoomListStateListenerProxy: RoomListStateListener {
|
||||
private let onUpdateClosure: (RoomListState) -> Void
|
||||
|
||||
init(_ onUpdateClosure: @escaping (RoomListState) -> Void) {
|
||||
self.onUpdateClosure = onUpdateClosure
|
||||
}
|
||||
|
||||
func onUpdate(state: RoomListState) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ enum ClientProxyError: Error {
|
||||
}
|
||||
|
||||
enum SlidingSyncConstants {
|
||||
static let initialTimelineLimit: UInt = 0
|
||||
static let lastMessageTimelineLimit: UInt = 1
|
||||
static let timelinePrecachingTimelineLimit: UInt = 20
|
||||
static let defaultTimelineLimit: UInt = 20
|
||||
}
|
||||
|
||||
/// This struct represents the configuration that we are using to register the application through Pusher to Sygnal
|
||||
@@ -77,11 +75,9 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
|
||||
var restorationToken: RestorationToken? { get }
|
||||
|
||||
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? { get }
|
||||
var roomSummaryProvider: RoomSummaryProviderProtocol? { get }
|
||||
|
||||
var allRoomsSummaryProvider: RoomSummaryProviderProtocol? { get }
|
||||
|
||||
var invitesSummaryProvider: RoomSummaryProviderProtocol? { get }
|
||||
var inviteSummaryProvider: RoomSummaryProviderProtocol? { get }
|
||||
|
||||
var isSyncing: Bool { get }
|
||||
|
||||
|
||||
@@ -26,11 +26,9 @@ class MockClientProxy: ClientProxyProtocol {
|
||||
let homeserver = ""
|
||||
let restorationToken: RestorationToken? = nil
|
||||
|
||||
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
||||
var roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
||||
|
||||
var allRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
||||
|
||||
var invitesSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
||||
var inviteSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
||||
|
||||
var avatarURLPublisher: AnyPublisher<URL?, Never> { Empty().eraseToAnyPublisher() }
|
||||
|
||||
@@ -38,7 +36,7 @@ class MockClientProxy: ClientProxyProtocol {
|
||||
|
||||
internal init(userID: String, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) {
|
||||
self.userID = userID
|
||||
visibleRoomsSummaryProvider = roomSummaryProvider
|
||||
self.roomSummaryProvider = roomSummaryProvider
|
||||
}
|
||||
|
||||
func loadUserAvatarURL() async { }
|
||||
@@ -72,14 +70,14 @@ class MockClientProxy: ClientProxyProtocol {
|
||||
return roomForIdentifierMocks[identifier]
|
||||
}
|
||||
|
||||
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
|
||||
guard let room = roomSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch room {
|
||||
case .empty:
|
||||
return RoomProxyMock(with: .init(displayName: "Empty room"))
|
||||
case .filled(let details), .invalidated(let details):
|
||||
case .filled(let details):
|
||||
return RoomProxyMock(with: .init(displayName: details.name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +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 Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
private class SlidingSyncListObserver: SlidingSyncListRoomListObserver, SlidingSyncListStateObserver, SlidingSyncListRoomsCountObserver {
|
||||
/// Publishes room list diffs as they come in through sliding sync
|
||||
let roomListDiffPublisher = PassthroughSubject<SlidingSyncListRoomsListDiff, Never>()
|
||||
|
||||
/// Publishes the current state of sliding sync, such as whether its catching up or live.
|
||||
let stateUpdatePublisher = CurrentValueSubject<SlidingSyncState, Never>(.notLoaded)
|
||||
|
||||
/// Publishes the number of available rooms
|
||||
let countUpdatePublisher = CurrentValueSubject<UInt, Never>(0)
|
||||
|
||||
private let name: String
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
// MARK: - SlidingSyncListRoomListObserver
|
||||
|
||||
func didReceiveUpdate(diff: SlidingSyncListRoomsListDiff) {
|
||||
MXLog.verbose("\(name): Received room diff")
|
||||
roomListDiffPublisher.send(diff)
|
||||
}
|
||||
|
||||
// MARK: - SlidingSyncListStateObserver
|
||||
|
||||
func didReceiveUpdate(newState: SlidingSyncState) {
|
||||
MXLog.info("\(name): Updated state: \(newState)")
|
||||
stateUpdatePublisher.send(newState)
|
||||
}
|
||||
|
||||
// MARK: - SlidingSyncListRoomsCountObserver
|
||||
|
||||
func didReceiveUpdate(count: UInt32) {
|
||||
MXLog.info("\(name): Updated room count: \(count)")
|
||||
countUpdatePublisher.send(UInt(count))
|
||||
}
|
||||
}
|
||||
|
||||
class SlidingSyncListProxy: SlidingSyncListOnceBuilt {
|
||||
let name: String
|
||||
private var slidingSync: SlidingSyncProtocol?
|
||||
private var slidingSyncList: SlidingSyncListProtocol?
|
||||
|
||||
private var listUpdateObserverToken: TaskHandle?
|
||||
private var stateUpdateObserverToken: TaskHandle?
|
||||
private var countUpdateObserverToken: TaskHandle?
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
let diffPublisher = PassthroughSubject<SlidingSyncListRoomsListDiff, Never>()
|
||||
let statePublisher = PassthroughSubject<SlidingSyncState, Never>()
|
||||
let countPublisher = PassthroughSubject<UInt, Never>()
|
||||
|
||||
deinit {
|
||||
listUpdateObserverToken?.cancel()
|
||||
stateUpdateObserverToken?.cancel()
|
||||
countUpdateObserverToken?.cancel()
|
||||
}
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func setSlidingSync(slidingSync: SlidingSyncProtocol) {
|
||||
self.slidingSync = slidingSync
|
||||
}
|
||||
|
||||
func currentRoomsList() -> [RoomListEntry] {
|
||||
guard let slidingSyncList else {
|
||||
return []
|
||||
}
|
||||
|
||||
return slidingSyncList.currentRoomList()
|
||||
}
|
||||
|
||||
func roomForIdentifier(_ identifier: String) throws -> SlidingSyncRoomProtocol? {
|
||||
guard let slidingSync else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try slidingSync.getRoom(roomId: identifier)
|
||||
}
|
||||
|
||||
func updateVisibleRange(_ range: Range<Int>?, timelineLimit: UInt?) {
|
||||
if let range {
|
||||
MXLog.info("Setting '\(name)' list range to \(range)")
|
||||
slidingSyncList?.setSyncMode(builder: .init().addRange(start: UInt32(range.lowerBound), endInclusive: UInt32(range.upperBound)))
|
||||
}
|
||||
|
||||
if let timelineLimit {
|
||||
MXLog.info("Setting '\(name)' list timeline limit to \(timelineLimit)")
|
||||
slidingSyncList?.setTimelineLimit(value: UInt32(timelineLimit))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SlidingSyncListOnceBuilt
|
||||
|
||||
func updateList(list: MatrixRustSDK.SlidingSyncList) -> MatrixRustSDK.SlidingSyncList {
|
||||
slidingSyncList = list
|
||||
|
||||
let slidingSyncListObserver = SlidingSyncListObserver(name: name)
|
||||
|
||||
slidingSyncListObserver.stateUpdatePublisher
|
||||
.subscribe(statePublisher)
|
||||
.store(in: &cancellables)
|
||||
|
||||
slidingSyncListObserver.countUpdatePublisher
|
||||
.subscribe(countPublisher)
|
||||
.store(in: &cancellables)
|
||||
|
||||
slidingSyncListObserver.roomListDiffPublisher
|
||||
.subscribe(diffPublisher)
|
||||
.store(in: &cancellables)
|
||||
|
||||
listUpdateObserverToken = list.observeRoomList(observer: slidingSyncListObserver)
|
||||
stateUpdateObserverToken = list.observeState(observer: slidingSyncListObserver)
|
||||
countUpdateObserverToken = list.observeRoomsCount(observer: slidingSyncListObserver)
|
||||
|
||||
return list
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ struct MediaUploadingPreprocessor {
|
||||
mimetype: thumbnailResult.mimeType,
|
||||
size: thumbnailSize)
|
||||
|
||||
let videoInfo = VideoInfo(duration: UInt64(result.duration),
|
||||
let videoInfo = VideoInfo(duration: result.duration,
|
||||
height: UInt64(result.height),
|
||||
width: UInt64(result.width),
|
||||
mimetype: result.mimeType,
|
||||
@@ -223,7 +223,7 @@ struct MediaUploadingPreprocessor {
|
||||
return .failure(.failedProcessingAudio)
|
||||
}
|
||||
|
||||
let audioInfo = AudioInfo(duration: UInt64(durationInSeconds * 1000), size: fileSize, mimetype: mimeType)
|
||||
let audioInfo = AudioInfo(duration: durationInSeconds * 1000, size: fileSize, mimetype: mimeType)
|
||||
return .success(.audio(audioURL: url, audioInfo: audioInfo))
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import UIKit
|
||||
import MatrixRustSDK
|
||||
|
||||
class RoomProxy: RoomProxyProtocol {
|
||||
private let slidingSyncRoom: SlidingSyncRoomProtocol
|
||||
private let roomListItem: RoomListItemProtocol
|
||||
private let room: RoomProtocol
|
||||
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
||||
private let backgroundTaskName = "SendRoomEvent"
|
||||
@@ -47,16 +47,16 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
|
||||
deinit {
|
||||
Task { @MainActor [roomTimelineObservationToken, slidingSyncRoom] in
|
||||
Task { @MainActor [roomTimelineObservationToken, roomListItem] in
|
||||
roomTimelineObservationToken?.cancel()
|
||||
slidingSyncRoom.unsubscribeFromRoom()
|
||||
roomListItem.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
init(slidingSyncRoom: SlidingSyncRoomProtocol,
|
||||
init(roomListItem: RoomListItemProtocol,
|
||||
room: RoomProtocol,
|
||||
backgroundTaskService: BackgroundTaskServiceProtocol) {
|
||||
self.slidingSyncRoom = slidingSyncRoom
|
||||
self.roomListItem = roomListItem
|
||||
self.room = room
|
||||
self.backgroundTaskService = backgroundTaskService
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class RoomProxy: RoomProxyProtocol {
|
||||
lazy var id: String = room.id()
|
||||
|
||||
var name: String? {
|
||||
slidingSyncRoom.name()
|
||||
roomListItem.name()
|
||||
}
|
||||
|
||||
var topic: String? {
|
||||
@@ -104,7 +104,7 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
|
||||
var hasUnreadNotifications: Bool {
|
||||
slidingSyncRoom.hasUnreadNotifications()
|
||||
roomListItem.hasUnreadNotifications()
|
||||
}
|
||||
|
||||
var avatarURL: URL? {
|
||||
@@ -166,33 +166,30 @@ class RoomProxy: RoomProxyProtocol {
|
||||
guard timelineListener == nil else {
|
||||
return .failure(.roomListenerAlreadyRegistered)
|
||||
}
|
||||
|
||||
|
||||
let settings = RoomSubscription(requiredState: [RequiredState(key: "m.room.name", value: ""),
|
||||
RequiredState(key: "m.room.topic", value: ""),
|
||||
RequiredState(key: "m.room.avatar", value: ""),
|
||||
RequiredState(key: "m.room.canonical_alias", value: ""),
|
||||
RequiredState(key: "m.room.join_rules", value: "")],
|
||||
timelineLimit: UInt32(SlidingSyncConstants.timelinePrecachingTimelineLimit))
|
||||
slidingSyncRoom.subscribeToRoom(settings: settings)
|
||||
|
||||
timelineLimit: UInt32(SlidingSyncConstants.defaultTimelineLimit))
|
||||
roomListItem.subscribe(settings: settings)
|
||||
|
||||
let timelineListener = RoomTimelineListener { [weak self] timelineDiff in
|
||||
self?.updatesSubject.send(timelineDiff)
|
||||
}
|
||||
|
||||
|
||||
self.timelineListener = timelineListener
|
||||
|
||||
if let result = try? slidingSyncRoom.addTimelineListener(listener: timelineListener) {
|
||||
roomTimelineObservationToken = result.taskHandle
|
||||
|
||||
Task {
|
||||
await fetchMembers()
|
||||
await updateMembers()
|
||||
}
|
||||
return .success(result.items)
|
||||
} else {
|
||||
self.timelineListener = nil
|
||||
return .failure(.failedAddingTimelineListener)
|
||||
let result = room.addTimelineListener(listener: timelineListener)
|
||||
roomTimelineObservationToken = result.itemsStream
|
||||
|
||||
Task {
|
||||
await fetchMembers()
|
||||
await updateMembers()
|
||||
}
|
||||
|
||||
return .success(result.items)
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError> {
|
||||
@@ -591,7 +588,7 @@ private class RoomTimelineListener: TimelineListener {
|
||||
self.onUpdateClosure = onUpdateClosure
|
||||
}
|
||||
|
||||
func onUpdate(update: TimelineDiff) {
|
||||
onUpdateClosure(update)
|
||||
func onUpdate(diff: TimelineDiff) {
|
||||
onUpdateClosure(diff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum MockRoomSummaryProviderState {
|
||||
case loading
|
||||
@@ -25,7 +26,6 @@ enum MockRoomSummaryProviderState {
|
||||
class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
let roomListPublisher: CurrentValuePublisher<[RoomSummary], Never>
|
||||
let statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never>
|
||||
let countPublisher: CurrentValuePublisher<UInt, Never>
|
||||
|
||||
convenience init() {
|
||||
self.init(state: .loading)
|
||||
@@ -36,15 +36,16 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
case .loading:
|
||||
roomListPublisher = .init([])
|
||||
statePublisher = .init(.notLoaded)
|
||||
countPublisher = .init(0)
|
||||
case .loaded(let rooms):
|
||||
roomListPublisher = .init(rooms)
|
||||
statePublisher = .init(.fullyLoaded)
|
||||
countPublisher = .init(UInt(rooms.count))
|
||||
}
|
||||
}
|
||||
|
||||
func updateVisibleRange(_ range: Range<Int>, timelineLimit: UInt) { }
|
||||
func subscribeIfNecessary(entriesFunction: (RoomListEntriesListener) async throws -> RoomListEntriesResult,
|
||||
entriesLoadingStateFunction: (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult) { }
|
||||
|
||||
func updateVisibleRange(_ range: Range<Int>) { }
|
||||
}
|
||||
|
||||
extension Array where Element == RoomSummary {
|
||||
|
||||
@@ -19,17 +19,22 @@ import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
private let slidingSyncListProxy: SlidingSyncListProxy
|
||||
private let serialDispatchQueue: DispatchQueue
|
||||
private let roomListService: RoomListProtocol
|
||||
private let eventStringBuilder: RoomEventStringBuilder
|
||||
private let name: String
|
||||
|
||||
private let serialDispatchQueue: DispatchQueue
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var listUpdatesTaskHandle: TaskHandle?
|
||||
private var stateUpdatesTaskHandle: TaskHandle?
|
||||
|
||||
private let roomListSubject = CurrentValueSubject<[RoomSummary], Never>([])
|
||||
private let stateSubject = CurrentValueSubject<RoomSummaryProviderState, Never>(.notLoaded)
|
||||
private let countSubject = CurrentValueSubject<UInt, Never>(0)
|
||||
|
||||
private let diffPublisher = PassthroughSubject<RoomListEntriesUpdate, Never>()
|
||||
|
||||
var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> {
|
||||
roomListSubject.asCurrentValuePublisher()
|
||||
}
|
||||
@@ -37,10 +42,6 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> {
|
||||
stateSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
var countPublisher: CurrentValuePublisher<UInt, Never> {
|
||||
countSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private var rooms: [RoomSummary] = [] {
|
||||
didSet {
|
||||
@@ -48,47 +49,75 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
init(slidingSyncListProxy: SlidingSyncListProxy, eventStringBuilder: RoomEventStringBuilder, name: String) {
|
||||
self.slidingSyncListProxy = slidingSyncListProxy
|
||||
init(roomListService: RoomListProtocol,
|
||||
eventStringBuilder: RoomEventStringBuilder,
|
||||
name: String) {
|
||||
self.roomListService = roomListService
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .utility)
|
||||
self.eventStringBuilder = eventStringBuilder
|
||||
self.name = name
|
||||
|
||||
rooms = slidingSyncListProxy.currentRoomsList().map { roomListEntry in
|
||||
buildSummaryForRoomListEntry(roomListEntry)
|
||||
}
|
||||
|
||||
roomListSubject.send(rooms) // didSet not called from initialisers
|
||||
|
||||
slidingSyncListProxy.statePublisher
|
||||
.map(RoomSummaryProviderState.init)
|
||||
.subscribe(stateSubject)
|
||||
.store(in: &cancellables)
|
||||
|
||||
slidingSyncListProxy.countPublisher
|
||||
.subscribe(countSubject)
|
||||
.store(in: &cancellables)
|
||||
|
||||
slidingSyncListProxy.diffPublisher
|
||||
diffPublisher
|
||||
.collect(.byTime(serialDispatchQueue, 0.025))
|
||||
.sink { [weak self] in self?.updateRoomsWithDiffs($0) }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func updateVisibleRange(_ range: Range<Int>, timelineLimit: UInt) {
|
||||
slidingSyncListProxy.updateVisibleRange(range, timelineLimit: timelineLimit)
|
||||
func subscribeIfNecessary(entriesFunction: (RoomListEntriesListener) async throws -> RoomListEntriesResult,
|
||||
entriesLoadingStateFunction: (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult) async {
|
||||
guard listUpdatesTaskHandle == nil, stateUpdatesTaskHandle == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let listUpdatesSubscriptionResult = try await entriesFunction(RoomListEntriesListenerProxy { [weak self] update in
|
||||
guard let self else { return }
|
||||
MXLog.verbose("\(name): Received list update")
|
||||
diffPublisher.send(update)
|
||||
})
|
||||
|
||||
listUpdatesTaskHandle = listUpdatesSubscriptionResult.entriesStream
|
||||
|
||||
rooms = listUpdatesSubscriptionResult.entries.map { roomListEntry in
|
||||
buildSummaryForRoomListEntry(roomListEntry)
|
||||
}
|
||||
|
||||
let stateUpdatesSubscriptionResult = try await entriesLoadingStateFunction(RoomListStateObserver { [weak self] state in
|
||||
guard let self else { return }
|
||||
MXLog.info("\(name): Received state update: \(state)")
|
||||
stateSubject.send(RoomSummaryProviderState(slidingSyncState: state))
|
||||
})
|
||||
|
||||
stateSubject.send(RoomSummaryProviderState(slidingSyncState: stateUpdatesSubscriptionResult.entriesLoadingState))
|
||||
|
||||
stateUpdatesTaskHandle = stateUpdatesSubscriptionResult.entriesLoadingStateStream
|
||||
|
||||
} catch {
|
||||
MXLog.error("Failed setting up room list entry listener with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func updateVisibleRange(_ range: Range<Int>) {
|
||||
Task {
|
||||
do {
|
||||
MXLog.info("\(name): Setting visible range to \(range)")
|
||||
try await roomListService.applyInput(input: .viewport(ranges: [.init(start: UInt32(range.lowerBound), endInclusive: UInt32(range.upperBound))]))
|
||||
} catch {
|
||||
MXLog.error("Failed updating visible range with error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
fileprivate func updateRoomsWithDiffs(_ diffs: [SlidingSyncListRoomsListDiff]) {
|
||||
fileprivate func updateRoomsWithDiffs(_ diffs: [RoomListEntriesUpdate]) {
|
||||
let span = MXLog.createSpan("\(name).process_room_list_diffs")
|
||||
span.enter()
|
||||
defer {
|
||||
span.exit()
|
||||
}
|
||||
|
||||
MXLog.info("\(name): Received \(diffs.count) diffs, current room list \(rooms.compactMap { $0.id ?? "Empty" })")
|
||||
MXLog.verbose("\(name): Received \(diffs.count) diffs, current room list \(rooms.compactMap { $0.id ?? "Empty" })")
|
||||
|
||||
rooms = diffs
|
||||
.reduce(rooms) { currentItems, diff in
|
||||
@@ -107,11 +136,11 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
|
||||
detectDuplicatesInRoomList(rooms)
|
||||
|
||||
MXLog.info("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })")
|
||||
MXLog.verbose("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })")
|
||||
}
|
||||
|
||||
private func buildRoomSummaryForIdentifier(_ identifier: String, invalidated: Bool) -> RoomSummary {
|
||||
guard let room = try? slidingSyncListProxy.roomForIdentifier(identifier) else {
|
||||
private func buildRoomSummaryForIdentifier(_ identifier: String) -> RoomSummary {
|
||||
guard let roomListItem = try? roomListService.room(roomId: identifier) else {
|
||||
MXLog.error("\(name): Failed finding room with id: \(identifier)")
|
||||
return .empty
|
||||
}
|
||||
@@ -119,30 +148,24 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
var attributedLastMessage: AttributedString?
|
||||
var lastMessageFormattedTimestamp: String?
|
||||
|
||||
// Dispatch onto another queue otherwise the rust method latestRoomMessage crashes.
|
||||
// This will be fixed when we get async uniffi support.
|
||||
DispatchQueue.global(qos: .userInitiated).sync {
|
||||
if let latestRoomMessage = room.latestRoomMessage() {
|
||||
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage)
|
||||
lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal()
|
||||
attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage)
|
||||
}
|
||||
if let latestRoomMessage = roomListItem.latestEvent() {
|
||||
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage)
|
||||
lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal()
|
||||
attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage)
|
||||
}
|
||||
|
||||
let fullRoom = room.fullRoom()
|
||||
let avatarURL = fullRoom?.avatarUrl().flatMap(URL.init(string:))
|
||||
let canonicalAlias = fullRoom?.canonicalAlias()
|
||||
|
||||
let details = RoomSummaryDetails(id: room.roomId(),
|
||||
name: room.name() ?? room.roomId(),
|
||||
isDirect: fullRoom?.isDirect() ?? room.isDm() ?? false,
|
||||
avatarURL: avatarURL,
|
||||
let room = roomListItem.fullRoom()
|
||||
|
||||
let details = RoomSummaryDetails(id: roomListItem.id(),
|
||||
name: roomListItem.name() ?? room.id(),
|
||||
isDirect: room.isDirect(),
|
||||
avatarURL: room.avatarUrl().flatMap(URL.init(string:)),
|
||||
lastMessage: attributedLastMessage,
|
||||
lastMessageFormattedTimestamp: lastMessageFormattedTimestamp,
|
||||
unreadNotificationCount: UInt(room.unreadNotifications().notificationCount()),
|
||||
canonicalAlias: canonicalAlias)
|
||||
|
||||
return invalidated ? .invalidated(details: details) : .filled(details: details)
|
||||
unreadNotificationCount: UInt(roomListItem.unreadNotifications().notificationCount()),
|
||||
canonicalAlias: room.canonicalAlias())
|
||||
|
||||
return .filled(details: details)
|
||||
}
|
||||
|
||||
private func buildSummaryForRoomListEntry(_ entry: RoomListEntry) -> RoomSummary {
|
||||
@@ -150,48 +173,48 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
case .empty:
|
||||
return .empty
|
||||
case .filled(let roomId):
|
||||
return buildRoomSummaryForIdentifier(roomId, invalidated: false)
|
||||
return buildRoomSummaryForIdentifier(roomId)
|
||||
case .invalidated(let roomId):
|
||||
return buildRoomSummaryForIdentifier(roomId, invalidated: true)
|
||||
return buildRoomSummaryForIdentifier(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||
private func buildDiff(from diff: SlidingSyncListRoomsListDiff, on rooms: [RoomSummary]) -> CollectionDifference<RoomSummary>? {
|
||||
private func buildDiff(from diff: RoomListEntriesUpdate, on rooms: [RoomSummary]) -> CollectionDifference<RoomSummary>? {
|
||||
var changes = [CollectionDifference<RoomSummary>.Change]()
|
||||
|
||||
switch diff {
|
||||
case .pushFront(let value):
|
||||
MXLog.info("\(name): Push Front \(value.debugIdentifier)")
|
||||
MXLog.verbose("\(name): Push Front \(value.debugIdentifier)")
|
||||
let summary = buildSummaryForRoomListEntry(value)
|
||||
changes.append(.insert(offset: 0, element: summary, associatedWith: nil))
|
||||
case .pushBack(let value):
|
||||
MXLog.info("\(name): Push Back \(value.debugIdentifier)")
|
||||
MXLog.verbose("\(name): Push Back \(value.debugIdentifier)")
|
||||
let summary = buildSummaryForRoomListEntry(value)
|
||||
changes.append(.insert(offset: rooms.count, element: summary, associatedWith: nil))
|
||||
case .append(values: let values):
|
||||
let debugIdentifiers = values.map(\.debugIdentifier)
|
||||
MXLog.info("\(name): Append \(debugIdentifiers)")
|
||||
MXLog.verbose("\(name): Append \(debugIdentifiers)")
|
||||
for (index, value) in values.enumerated() {
|
||||
let summary = buildSummaryForRoomListEntry(value)
|
||||
changes.append(.insert(offset: rooms.count + index, element: summary, associatedWith: nil))
|
||||
}
|
||||
case .set(let index, let value):
|
||||
MXLog.info("\(name): Update \(value.debugIdentifier) at \(index)")
|
||||
MXLog.verbose("\(name): Update \(value.debugIdentifier) at \(index)")
|
||||
let summary = buildSummaryForRoomListEntry(value)
|
||||
changes.append(.remove(offset: Int(index), element: summary, associatedWith: nil))
|
||||
changes.append(.insert(offset: Int(index), element: summary, associatedWith: nil))
|
||||
case .insert(let index, let value):
|
||||
MXLog.info("\(name): Insert at \(value.debugIdentifier) at \(index)")
|
||||
MXLog.verbose("\(name): Insert at \(value.debugIdentifier) at \(index)")
|
||||
let summary = buildSummaryForRoomListEntry(value)
|
||||
changes.append(.insert(offset: Int(index), element: summary, associatedWith: nil))
|
||||
case .remove(let index):
|
||||
let summary = rooms[Int(index)]
|
||||
MXLog.info("\(name): Remove \(summary.id ?? "") from \(index)")
|
||||
MXLog.verbose("\(name): Remove \(summary.id ?? "") from \(index)")
|
||||
changes.append(.remove(offset: Int(index), element: summary, associatedWith: nil))
|
||||
case .reset(let values):
|
||||
let debugIdentifiers = values.map(\.debugIdentifier)
|
||||
MXLog.info("\(name): Replace all items with \(debugIdentifiers)")
|
||||
MXLog.verbose("\(name): Replace all items with \(debugIdentifiers)")
|
||||
for (index, summary) in rooms.enumerated() {
|
||||
changes.append(.remove(offset: index, element: summary, associatedWith: nil))
|
||||
}
|
||||
@@ -200,16 +223,16 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
changes.append(.insert(offset: index, element: buildSummaryForRoomListEntry(value), associatedWith: nil))
|
||||
}
|
||||
case .clear:
|
||||
MXLog.info("\(name): Clear all items")
|
||||
MXLog.verbose("\(name): Clear all items")
|
||||
for (index, value) in rooms.enumerated() {
|
||||
changes.append(.remove(offset: index, element: value, associatedWith: nil))
|
||||
}
|
||||
case .popFront:
|
||||
MXLog.info("\(name): Pop Front")
|
||||
MXLog.verbose("\(name): Pop Front")
|
||||
let summary = rooms[0]
|
||||
changes.append(.remove(offset: 0, element: summary, associatedWith: nil))
|
||||
case .popBack:
|
||||
MXLog.info("\(name): Pop Back")
|
||||
MXLog.verbose("\(name): Pop Back")
|
||||
guard let value = rooms.last else {
|
||||
fatalError()
|
||||
}
|
||||
@@ -241,7 +264,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
}
|
||||
|
||||
extension RoomSummaryProviderState {
|
||||
init(slidingSyncState: SlidingSyncState) {
|
||||
init(slidingSyncState: SlidingSyncListLoadingState) {
|
||||
switch slidingSyncState {
|
||||
case .notLoaded:
|
||||
self = .notLoaded
|
||||
@@ -276,3 +299,27 @@ extension MatrixRustSDK.RoomListEntry {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: SlidingSyncListStateObserver {
|
||||
private let onUpdateClosure: (SlidingSyncListLoadingState) -> Void
|
||||
|
||||
init(_ onUpdateClosure: @escaping (SlidingSyncListLoadingState) -> Void) {
|
||||
self.onUpdateClosure = onUpdateClosure
|
||||
}
|
||||
|
||||
func didReceiveUpdate(newState: SlidingSyncListLoadingState) {
|
||||
onUpdateClosure(newState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,12 @@ enum RoomSummaryProviderState {
|
||||
enum RoomSummary: CustomStringConvertible {
|
||||
case empty
|
||||
case filled(details: RoomSummaryDetails)
|
||||
case invalidated(details: RoomSummaryDetails)
|
||||
|
||||
var id: String? {
|
||||
switch self {
|
||||
case .empty:
|
||||
return nil
|
||||
case .invalidated(let details), .filled(let details):
|
||||
case .filled(let details):
|
||||
return details.id
|
||||
}
|
||||
}
|
||||
@@ -43,7 +42,7 @@ enum RoomSummary: CustomStringConvertible {
|
||||
switch self {
|
||||
case .empty:
|
||||
return nil
|
||||
case .invalidated(let details), .filled(let details):
|
||||
case .filled(let details):
|
||||
return details.name
|
||||
}
|
||||
}
|
||||
@@ -52,8 +51,6 @@ enum RoomSummary: CustomStringConvertible {
|
||||
switch self {
|
||||
case .empty:
|
||||
return "\(String(describing: Self.self)): Empty"
|
||||
case .invalidated(let details):
|
||||
return "\(String(describing: Self.self)): Invalidated(\(details.id))"
|
||||
case .filled(let details):
|
||||
return "\(String(describing: Self.self)): Filled(\(details.id))"
|
||||
}
|
||||
@@ -67,8 +64,10 @@ protocol RoomSummaryProviderProtocol {
|
||||
/// Publishes the current state the summary provider is finding itself in
|
||||
var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> { get }
|
||||
|
||||
/// Publishes the total number of rooms
|
||||
var countPublisher: CurrentValuePublisher<UInt, Never> { get }
|
||||
|
||||
func updateVisibleRange(_ range: Range<Int>, timelineLimit: UInt)
|
||||
/// A separate subscription method is needed instead of running this in 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
|
||||
func subscribeIfNecessary(entriesFunction: (RoomListEntriesListener) async throws -> RoomListEntriesResult,
|
||||
entriesLoadingStateFunction: (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult) async
|
||||
|
||||
func updateVisibleRange(_ range: Range<Int>)
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
switch roomProxy.registerTimelineListenerIfNeeded() {
|
||||
case let .success(items):
|
||||
itemProxies = items.map(TimelineItemProxy.init)
|
||||
MXLog.info("Added timeline listener, current items (\(items.count)) : \(items.map(\.debugIdentifier))")
|
||||
MXLog.verbose("Added timeline listener, current items (\(items.count)) : \(items.map(\.debugIdentifier))")
|
||||
case .failure(.roomListenerAlreadyRegistered):
|
||||
MXLog.info("Listener already registered for the room: \(roomProxy.id)")
|
||||
MXLog.verbose("Listener already registered for the room: \(roomProxy.id)")
|
||||
case .failure:
|
||||
let roomID = roomProxy.id
|
||||
MXLog.error("Failed adding timeline listener on room with identifier: \(roomID)")
|
||||
@@ -67,7 +67,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
span.exit()
|
||||
}
|
||||
|
||||
MXLog.info("Received timeline diff")
|
||||
MXLog.verbose("Received timeline diff")
|
||||
|
||||
itemProxies = diffs
|
||||
.reduce(itemProxies) { currentItems, diff in
|
||||
@@ -84,7 +84,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
return updatedItems
|
||||
}
|
||||
|
||||
MXLog.info("Finished applying diffs, current items (\(itemProxies.count)) : \(itemProxies.map(\.debugIdentifier))")
|
||||
MXLog.verbose("Finished applying diffs, current items (\(itemProxies.count)) : \(itemProxies.map(\.debugIdentifier))")
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||
@@ -95,45 +95,45 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
case .pushFront:
|
||||
guard let item = diff.pushFront() else { fatalError() }
|
||||
|
||||
MXLog.info("Push Front: \(item.debugIdentifier)")
|
||||
MXLog.verbose("Push Front: \(item.debugIdentifier)")
|
||||
let itemProxy = TimelineItemProxy(item: item)
|
||||
changes.append(.insert(offset: 0, element: itemProxy, associatedWith: nil))
|
||||
case .pushBack:
|
||||
guard let item = diff.pushBack() else { fatalError() }
|
||||
|
||||
MXLog.info("Push Back \(item.debugIdentifier)")
|
||||
MXLog.verbose("Push Back \(item.debugIdentifier)")
|
||||
let itemProxy = TimelineItemProxy(item: item)
|
||||
changes.append(.insert(offset: Int(itemProxies.count), element: itemProxy, associatedWith: nil))
|
||||
case .insert:
|
||||
guard let update = diff.insert() else { fatalError() }
|
||||
|
||||
MXLog.info("Insert \(update.item.debugIdentifier) at \(update.index)")
|
||||
MXLog.verbose("Insert \(update.item.debugIdentifier) at \(update.index)")
|
||||
let itemProxy = TimelineItemProxy(item: update.item)
|
||||
changes.append(.insert(offset: Int(update.index), element: itemProxy, associatedWith: nil))
|
||||
case .append:
|
||||
guard let items = diff.append() else { fatalError() }
|
||||
|
||||
MXLog.info("Append \(items.map(\.debugIdentifier))")
|
||||
MXLog.verbose("Append \(items.map(\.debugIdentifier))")
|
||||
for (index, item) in items.enumerated() {
|
||||
changes.append(.insert(offset: Int(itemProxies.count) + index, element: TimelineItemProxy(item: item), associatedWith: nil))
|
||||
}
|
||||
case .set:
|
||||
guard let update = diff.set() else { fatalError() }
|
||||
|
||||
MXLog.info("Set \(update.item.debugIdentifier) at index \(update.index)")
|
||||
MXLog.verbose("Set \(update.item.debugIdentifier) at index \(update.index)")
|
||||
let itemProxy = TimelineItemProxy(item: update.item)
|
||||
changes.append(.remove(offset: Int(update.index), element: itemProxy, associatedWith: nil))
|
||||
changes.append(.insert(offset: Int(update.index), element: itemProxy, associatedWith: nil))
|
||||
case .popFront:
|
||||
guard let itemProxy = itemProxies.first else { fatalError() }
|
||||
|
||||
MXLog.info("Pop Front \(itemProxy.debugIdentifier)")
|
||||
MXLog.verbose("Pop Front \(itemProxy.debugIdentifier)")
|
||||
|
||||
changes.append(.remove(offset: 0, element: itemProxy, associatedWith: nil))
|
||||
case .popBack:
|
||||
guard let itemProxy = itemProxies.last else { fatalError() }
|
||||
|
||||
MXLog.info("Pop Back \(itemProxy.debugIdentifier)")
|
||||
MXLog.verbose("Pop Back \(itemProxy.debugIdentifier)")
|
||||
|
||||
changes.append(.remove(offset: itemProxies.count - 1, element: itemProxy, associatedWith: nil))
|
||||
case .remove:
|
||||
@@ -141,18 +141,18 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
|
||||
let itemProxy = itemProxies[Int(index)]
|
||||
|
||||
MXLog.info("Remove \(itemProxy.debugIdentifier) at: \(index)")
|
||||
MXLog.verbose("Remove \(itemProxy.debugIdentifier) at: \(index)")
|
||||
|
||||
changes.append(.remove(offset: Int(index), element: itemProxy, associatedWith: nil))
|
||||
case .clear:
|
||||
MXLog.info("Clear all items")
|
||||
MXLog.verbose("Clear all items")
|
||||
for (index, itemProxy) in itemProxies.enumerated() {
|
||||
changes.append(.remove(offset: index, element: itemProxy, associatedWith: nil))
|
||||
}
|
||||
case .reset:
|
||||
guard let items = diff.reset() else { fatalError() }
|
||||
|
||||
MXLog.info("Replace all items with \(items.map(\.debugIdentifier))")
|
||||
MXLog.verbose("Replace all items with \(items.map(\.debugIdentifier))")
|
||||
for (index, itemProxy) in itemProxies.enumerated() {
|
||||
changes.append(.remove(offset: index, element: itemProxy, associatedWith: nil))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import UniformTypeIdentifiers
|
||||
|
||||
struct AudioRoomTimelineItemContent: Hashable {
|
||||
let body: String
|
||||
let duration: UInt64
|
||||
let duration: TimeInterval
|
||||
let source: MediaSourceProxy?
|
||||
let contentType: UTType?
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import UniformTypeIdentifiers
|
||||
|
||||
struct VideoRoomTimelineItemContent: Hashable {
|
||||
let body: String
|
||||
let duration: UInt64
|
||||
let duration: TimeInterval
|
||||
let source: MediaSourceProxy?
|
||||
let thumbnailSource: MediaSourceProxy?
|
||||
var width: CGFloat?
|
||||
|
||||
@@ -472,7 +472,7 @@ class MockScreen: Identifiable {
|
||||
clientProxy.roomForIdentifierMocks["someAwesomeRoomId1"] = .init(with: .init(displayName: "First room", inviter: .mockCharlie))
|
||||
clientProxy.roomForIdentifierMocks["someAwesomeRoomId2"] = .init(with: .init(displayName: "Second room", inviter: .mockCharlie))
|
||||
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||
clientProxy.invitesSummaryProvider = summaryProvider
|
||||
clientProxy.inviteSummaryProvider = summaryProvider
|
||||
|
||||
let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
@@ -484,7 +484,7 @@ class MockScreen: Identifiable {
|
||||
clientProxy.roomForIdentifierMocks["someAwesomeRoomId1"] = .init(with: .init(displayName: "First room", inviter: .mockCharlie))
|
||||
clientProxy.roomForIdentifierMocks["someAwesomeRoomId2"] = .init(with: .init(displayName: "Second room", inviter: .mockCharlie))
|
||||
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||
clientProxy.invitesSummaryProvider = summaryProvider
|
||||
clientProxy.inviteSummaryProvider = summaryProvider
|
||||
|
||||
let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
|
||||
@@ -78,8 +78,8 @@ class InvitesScreenViewModelTests: XCTestCase {
|
||||
private func setupViewModel(roomSummaries: [RoomSummary]? = nil) {
|
||||
if let roomSummaries {
|
||||
let summaryProvider = MockRoomSummaryProvider(state: .loaded(roomSummaries))
|
||||
clientProxy.invitesSummaryProvider = summaryProvider
|
||||
clientProxy.visibleRoomsSummaryProvider = summaryProvider
|
||||
clientProxy.inviteSummaryProvider = summaryProvider
|
||||
clientProxy.roomSummaryProvider = summaryProvider
|
||||
}
|
||||
|
||||
viewModel = InvitesScreenViewModel(userSession: userSession)
|
||||
|
||||
@@ -347,7 +347,7 @@ class LoggingTests: XCTestCase {
|
||||
let pointer = Unmanaged.passRetained(NSURL(fileURLWithPath: "/tmp/file")).toOpaque()
|
||||
let imageMessage = ImageMessageContent(body: "ImageString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil)
|
||||
let videoMessage = VideoMessageContent(body: "VideoString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil)
|
||||
let fileMessage = FileMessageContent(body: "FileString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil)
|
||||
let fileMessage = FileMessageContent(body: "FileString", filename: "FileName", source: MediaSource(unsafeFromRawPointer: pointer), info: nil)
|
||||
|
||||
// When logging that value
|
||||
XCTAssert(MXLogger.logFiles.isEmpty)
|
||||
|
||||
@@ -37,7 +37,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase {
|
||||
XCTAssertEqual(audioURL.lastPathComponent, "test_audio.mp3")
|
||||
|
||||
XCTAssertEqual(audioInfo.mimetype, "audio/mpeg")
|
||||
XCTAssertEqual(audioInfo.duration, 27252)
|
||||
XCTAssertEqual(floor(audioInfo.duration ?? 0), 27252)
|
||||
XCTAssertEqual(audioInfo.size, 764_176)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase {
|
||||
XCTAssertEqual(videoInfo.size, 1_431_959)
|
||||
XCTAssertEqual(videoInfo.width, 1280)
|
||||
XCTAssertEqual(videoInfo.height, 720)
|
||||
XCTAssertEqual(videoInfo.duration, 30483)
|
||||
XCTAssertEqual(floor(videoInfo.duration ?? 0), 30483)
|
||||
|
||||
XCTAssertNotNil(videoInfo.thumbnailInfo)
|
||||
XCTAssertEqual(videoInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
||||
|
||||
@@ -44,7 +44,7 @@ include:
|
||||
packages:
|
||||
MatrixRustSDK:
|
||||
url: https://github.com/matrix-org/matrix-rust-components-swift
|
||||
exactVersion: 1.0.75-alpha
|
||||
exactVersion: 1.0.76-alpha
|
||||
# path: ../matrix-rust-sdk
|
||||
DesignKit:
|
||||
path: DesignKit
|
||||
|
||||
Reference in New Issue
Block a user