From 14397288ebd8d75be5e1255faea6f2eb4ddad5d3 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 13 Jan 2023 17:09:37 +0200 Subject: [PATCH] Various sliding sync tweaks (#446) * Refactor SS configuration and add (unfinished) support for adding views dynamically. * Implement pop and clear support on the room summary provider * Register views against sliding sync * Read invalidated vislbeRoomsSummaryProvider from the allRoomSummaryProvider * Switch SS window range setting from the ScrollViewAdapter to a publisher debounce * Tweak allRoomsView addition: switch from listening the visibleRoomsView's state to when it publishes the first diff update * Cleanup client delegate and sliding sync observers and lifecycle * Bump the RustSDK to 1.0.30-alpha * Reuse startSync within restartSync --- ElementX.xcodeproj/project.pbxproj | 20 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Screens/HomeScreen/HomeScreenModels.swift | 2 +- .../HomeScreen/HomeScreenViewModel.swift | 39 ++-- .../Screens/HomeScreen/View/HomeScreen.swift | 41 ++-- .../Sources/Services/Client/ClientProxy.swift | 212 +++++++++++------- .../Services/Client/ClientProxyProtocol.swift | 2 - .../Services/Client/MockClientProxy.swift | 2 - .../Client/SlidingSyncViewProxy.swift | 9 +- .../RoomSummary/MockRoomSummaryProvider.swift | 2 +- .../RoomSummary/RoomSummaryProvider.swift | 15 +- .../RoomSummaryProviderProtocol.swift | 2 +- project.yml | 2 +- 13 files changed, 215 insertions(+), 137 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 1113bcfa7..8552c2f96 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -173,7 +173,6 @@ 5B8B51CEC4717AF487794685 /* NotificationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B490675B8E31423AF116BDA /* NotificationServiceProxy.swift */; }; 5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; }; 5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; }; - 5D1C6D4E3583700DF7A64834 /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D619626288AAB513B7620BB6 /* UITestsSignalling.swift */; }; 5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; }; 5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */; }; 5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; }; @@ -217,6 +216,7 @@ 7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */; }; 702694459B649B9D3A3C34F8 /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9212AE02CBDD692C56A879F /* TimelineTableViewController.swift */; }; 70558528EF68CAAEF09972D5 /* RoomTimelineItemFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96ED747FF90332EA1333C22 /* RoomTimelineItemFixtures.swift */; }; + 706289B086B0A6B0C211763F /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; }; 706F79A39BDB32F592B8C2C7 /* UIKitBackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */; }; 7096FA3AC218D914E88BFB70 /* AggregratedReaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15BE37BE2FB86E00C8D150A /* AggregratedReaction.swift */; }; 719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40C19719687984FD9478FBE /* Task.swift */; }; @@ -258,6 +258,7 @@ 81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36C0A6D59717193F49EA986 /* UserSessionTests.swift */; }; 829062DD3C3F7016FE1A6476 /* RoomDetailsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */; }; 83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */; }; + 84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; }; 85AFBB433AD56704A880F8A0 /* FramePreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */; }; 86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */; }; 8691186F9B99BCDDB7CACDD8 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */; }; @@ -461,7 +462,6 @@ F6E860FF7B18B81DF43B30B8 /* EncryptedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3FA7C8D4EF2B1873C180ED7 /* EncryptedRoomTimelineItem.swift */; }; F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; }; F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; }; - F98333D53D85B0E029FDCA12 /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D619626288AAB513B7620BB6 /* UITestsSignalling.swift */; }; F9981191DC408AED537C1749 /* MediaProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12C9E0B61A77C7F0EE7918C /* MediaProxy.swift */; }; F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DE8DC9B3FBA402117DC4C49F /* Kingfisher */; }; F9F6D2883BBEBB9A3789A137 /* OnboardingViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A941F289F6AB876BA3361A /* OnboardingViewModelTests.swift */; }; @@ -802,7 +802,7 @@ 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; 8ED2D2F6A137A95EA50413BE /* UserNotificationControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationControllerProtocol.swift; sourceTree = ""; }; 8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummaryProvider.swift; sourceTree = ""; }; 8FC26871038FB0E4AAE22605 /* apple_emojis_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = ""; }; @@ -903,6 +903,7 @@ B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; B7E035C6AC137C9392D98814 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Localizable.strings; sourceTree = ""; }; + B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsSignalling.swift; sourceTree = ""; }; B80D1901BA0B095E27793EDE /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenCoordinator.swift; sourceTree = ""; }; B8347789959986B374DB25DD /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sq; path = sq.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -965,7 +966,6 @@ D3D455BC2423D911A62ACFB2 /* NSELogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSELogger.swift; sourceTree = ""; }; D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineDeliveryStatusView.swift; sourceTree = ""; }; - D619626288AAB513B7620BB6 /* UITestsSignalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsSignalling.swift; sourceTree = ""; }; D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = ""; }; D67CBAFA48ED0B6FCE74F88F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; }; D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = ""; }; @@ -1015,7 +1015,7 @@ EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = ""; }; EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; @@ -1253,8 +1253,8 @@ children = ( 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */, CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */, - D619626288AAB513B7620BB6 /* UITestsSignalling.swift */, D751BB69BB7C38FD247517B4 /* UITestsRootCoordinator.swift */, + B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */, ); path = UITests; sourceTree = ""; @@ -3193,9 +3193,9 @@ 706F79A39BDB32F592B8C2C7 /* UIKitBackgroundTask.swift in Sources */, 3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */, D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */, - 5D1C6D4E3583700DF7A64834 /* UITestsSignalling.swift in Sources */, E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */, 086C2FA7750378EB2BFD0BEE /* UITestsRootCoordinator.swift in Sources */, + 706289B086B0A6B0C211763F /* UITestsSignalling.swift in Sources */, 071A017E415AD378F2961B11 /* URL.swift in Sources */, 392D0519E5597A538BFB2CAB /* UnsupportedRoomTimelineItem.swift in Sources */, E1F446C6B78A3A0FEA15079C /* UnsupportedRoomTimelineView.swift in Sources */, @@ -3243,7 +3243,7 @@ B3357B00F1AA930E54F76609 /* Strings.swift in Sources */, C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */, 9A47B7EFE3793760EEF68FFE /* UITestScreenIdentifier.swift in Sources */, - F98333D53D85B0E029FDCA12 /* UITestsSignalling.swift in Sources */, + 84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */, B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */, 588411C8FD72B2A2DFE5F7DE /* XCUIElement.swift in Sources */, ); @@ -3905,7 +3905,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = "1.0.28-alpha"; + version = "1.0.30-alpha"; }; }; 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b32ed1bcf..1c31cd0d9 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "d8bd9bc10423fe68f54c89811950ca269af9da9e", - "version" : "1.0.28-alpha" + "revision" : "08db830ef3f0ab24f39a95705179713ea5382f9c", + "version" : "1.0.30-alpha" } }, { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index fbc771af3..6026d1695 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -39,7 +39,7 @@ enum HomeScreenViewAction { case userMenu(action: HomeScreenViewUserMenuAction) case verifySession case skipSessionVerification - case updatedVisibleItemIdentifiers(Set) + case updatedVisibleItemRange(Range) } enum HomeScreenRoomListMode { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 3955d5204..0fdfa9bae 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -20,11 +20,17 @@ import SwiftUI typealias HomeScreenViewModelType = StateStoreViewModel class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol { + enum Constants { + static let slidingWindowBoundsPadding = 5 + } + private let userSession: UserSessionProtocol private let visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? private let allRoomsSummaryProvider: RoomSummaryProviderProtocol? private let attributedStringBuilder: AttributedStringBuilderProtocol + private let visibleItemRangePublisher = CurrentValueSubject, Never>(0..<0) + var callback: ((HomeScreenViewModelAction) -> Void)? // MARK: - Setup @@ -53,6 +59,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } .store(in: &cancellables) + visibleItemRangePublisher + .debounce(for: 0.1, scheduler: RunLoop.main) + .removeDuplicates() + .sink { range in + self.updateVisibleRange(range) + } + .store(in: &cancellables) + Task { if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() { if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .home)) { @@ -136,8 +150,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol callback?(.presentSessionVerificationScreen) case .skipSessionVerification: state.showSessionVerificationBanner = false - case .updatedVisibleItemIdentifiers(let identifiers): - updateVisibleRange(visibleItemIdentifiers: identifiers) + case .updatedVisibleItemRange(let range): + visibleItemRangePublisher.send(range) } } @@ -185,7 +199,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol for (index, summary) in visibleRoomsSummaryProvider.roomListPublisher.value.enumerated() { switch summary { - case .empty: + case .empty, .invalidated: guard let allRoomsRoomSummary = allRoomsSummaryProvider?.roomListPublisher.value[safe: index] else { rooms.append(HomeScreenRoom.placeholder()) continue @@ -201,9 +215,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol case .filled(let details): let room = buildRoom(with: details, invalidated: false) rooms.append(room) - case .invalidated(let details): - let room = buildRoom(with: details, invalidated: true) - rooms.append(room) } } @@ -229,19 +240,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol avatar: avatarImage) } - private func updateVisibleRange(visibleItemIdentifiers items: Set) { - let result = items.compactMap { itemIdentifier in - state.rooms.firstIndex { $0.id == itemIdentifier } - }.sorted() + private func updateVisibleRange(_ range: Range) { + guard !range.isEmpty else { return } - guard !result.isEmpty else { + guard let visibleRoomsSummaryProvider else { + MXLog.error("Visible rooms summary provider unavailable") return } - guard let lowerBound = result.first, let upperBound = result.last else { - return - } + let lowerBound = max(0, range.lowerBound - Constants.slidingWindowBoundsPadding) + let upperBound = min(Int(visibleRoomsSummaryProvider.countPublisher.value), range.upperBound + Constants.slidingWindowBoundsPadding) - visibleRoomsSummaryProvider?.updateVisibleRange(lowerBound...upperBound) + visibleRoomsSummaryProvider.updateVisibleRange(lowerBound..() - @State private var scrollViewAdapter = ScrollViewAdapter() - @ObservedObject var context: HomeScreenViewModel.Context + @State private var showingLogoutConfirmation = false + @State private var visibleItemIdentifiers = Set() { + didSet { + if visibleItemIdentifiers != oldValue { + updateVisibleRange() + } + } + } + var body: some View { ScrollView { if context.viewState.showSessionVerificationBanner { @@ -62,10 +67,6 @@ struct HomeScreen: View { .disableAutocorrection(true) } } - .introspectScrollView { scrollView in - guard scrollView != scrollViewAdapter.scrollView else { return } - scrollViewAdapter.scrollView = scrollView - } .disabled(context.viewState.roomListMode == .skeletons) .animation(.elementDefault, value: context.viewState.showSessionVerificationBanner) .animation(.elementDefault, value: context.viewState.roomListMode) @@ -76,14 +77,6 @@ struct HomeScreen: View { userMenuButton } } - .onReceive(scrollViewAdapter.isScrolling) { isScrolling in - guard context.viewState.bindings.searchQuery.isEmpty, - !isScrolling else { - return - } - - context.send(viewAction: .updatedVisibleItemIdentifiers(visibleItemIdentifiers)) - } } @ViewBuilder @@ -191,6 +184,22 @@ struct HomeScreen: View { private func signOut() { context.send(viewAction: .userMenu(action: .signOut)) } + + private func updateVisibleRange() { + let result = visibleItemIdentifiers.compactMap { itemIdentifier in + context.viewState.rooms.firstIndex { $0.id == itemIdentifier } + }.sorted() + + guard !result.isEmpty else { + return + } + + guard let firstIndex = result.first, let lastIndex = result.last else { + return + } + + context.send(viewAction: .updatedVisibleItemRange(firstIndex..() + private var visibleRoomsViewProxyStateObservationToken: AnyCancellable? + deinit { // These need to be inlined instead of using stopSync() // as we can't call async methods safely from deinit @@ -72,77 +74,16 @@ class ClientProxy: ClientProxyProtocol { let callbacks = PassthroughSubject() - // swiftlint:disable:next function_body_length - init(client: ClientProtocol, - backgroundTaskService: BackgroundTaskServiceProtocol) async { + init(client: ClientProtocol, backgroundTaskService: BackgroundTaskServiceProtocol) async { self.client = client self.backgroundTaskService = backgroundTaskService - clientQueue = .init(label: "ClientProxyQueue", - attributes: .concurrent) - mediaProxy = MediaProxy(client: client, - clientQueue: clientQueue) + clientQueue = .init(label: "ClientProxyQueue", attributes: .concurrent) + + mediaProxy = MediaProxy(client: client, clientQueue: clientQueue) - await Task.dispatch(on: clientQueue) { - do { - let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString) - - let requiredState = [RequiredState(key: "m.room.avatar", value: ""), - RequiredState(key: "m.room.encryption", value: "")] - - let filters = SlidingSyncRequestListFilters(isDm: nil, - spaces: [], - isEncrypted: nil, - isInvite: false, - isTombstoned: false, - roomTypes: [], - notRoomTypes: ["m.space"], - roomNameLike: nil, - tags: [], - notTags: []) - - let visibleRoomsView = try SlidingSyncViewBuilder() - .timelineLimit(limit: 20) - .requiredState(requiredState: requiredState) - .filters(filters: filters) - .name(name: "CurrentlyVisibleRooms") - .syncMode(mode: .selective) - .addRange(from: 0, to: 20) - .build() - - let allRoomsView = try SlidingSyncViewBuilder() - .noTimelineLimit() - .requiredState(requiredState: requiredState) - .filters(filters: filters) - .name(name: "AllRooms") - .syncMode(mode: .growingFullSync) - .batchSize(batchSize: 100) - .roomLimit(limit: 500) - .build() - - let slidingSync = try slidingSyncBuilder - .addView(v: visibleRoomsView) - .addView(v: allRoomsView) - .withCommonExtensions() - .coldCache(name: "ElementX") - .build() - - let visibleRoomsViewProxy = SlidingSyncViewProxy(clientProxy: self, slidingSync: slidingSync, slidingSyncView: visibleRoomsView) - - let allRoomsViewProxy = SlidingSyncViewProxy(clientProxy: self, slidingSync: slidingSync, slidingSyncView: allRoomsView) - - self.visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: visibleRoomsViewProxy, - roomMessageFactory: RoomMessageFactory()) - - self.allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: allRoomsViewProxy, - roomMessageFactory: RoomMessageFactory()) - - self.slidingSync = slidingSync - } catch { - MXLog.error("Failed configuring sliding sync with error: \(error)") - } - } - client.setDelegate(delegate: WeakClientProxyWrapper(clientProxy: self)) + + configureSlidingSync() } var userIdentifier: String { @@ -181,24 +122,16 @@ class ClientProxy: ClientProxyProtocol { } func startSync() { - guard !client.isSoftLogout() else { + guard !client.isSoftLogout(), slidingSyncObserverToken == nil else { return } - slidingSync?.setObserver(observer: WeakClientProxyWrapper(clientProxy: self)) slidingSyncObserverToken = slidingSync?.sync() } func stopSync() { - client.setDelegate(delegate: nil) - slidingSyncObserverToken?.cancel() - slidingSync?.setObserver(observer: nil) - } - - func restartSync() { - slidingSyncObserverToken?.cancel() - slidingSyncObserverToken = slidingSync?.sync() + slidingSyncObserverToken = nil } func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? { @@ -297,6 +230,125 @@ class ClientProxy: ClientProxyProtocol { } // MARK: Private + + private func restartSync() { + stopSync() + startSync() + } + + private func configureSlidingSync() { + guard slidingSync == nil else { + fatalError("This shouldn't be called more than once") + } + + do { + let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString) + + let slidingSync = try slidingSyncBuilder + .withCommonExtensions() + .coldCache(name: "ElementX") + .build() + + slidingSync.setObserver(observer: WeakClientProxyWrapper(clientProxy: self)) + + self.slidingSync = slidingSync + + configureSlidingSyncViews(slidingSync: slidingSync) + + guard let visibleRoomsSlidingSyncView else { + MXLog.error("Visible rooms sliding sync view unavailable") + return + } + + registerSlidingSyncView(visibleRoomsSlidingSyncView) + + } catch { + MXLog.error("Failed building sliding sync with error: \(error)") + } + } + + // swiftlint:disable:next function_body_length + private func configureSlidingSyncViews(slidingSync: SlidingSyncProtocol) { + guard visibleRoomsSlidingSyncView == nil, + allRoomsSlidingSyncView == nil else { + fatalError("This shouldn't be called more than once") + } + + let requiredState = [RequiredState(key: "m.room.avatar", value: ""), + RequiredState(key: "m.room.encryption", value: "")] + + let filters = SlidingSyncRequestListFilters(isDm: nil, + spaces: [], + isEncrypted: nil, + isInvite: false, + isTombstoned: false, + roomTypes: [], + notRoomTypes: ["m.space"], + roomNameLike: nil, + tags: [], + notTags: []) + + do { + let visibleRoomsView = try SlidingSyncViewBuilder() + .timelineLimit(limit: 20) + .requiredState(requiredState: requiredState) + .filters(filters: filters) + .name(name: "CurrentlyVisibleRooms") + .syncMode(mode: .selective) + .addRange(from: 0, to: 20) + .build() + + let allRoomsView = try SlidingSyncViewBuilder() + .noTimelineLimit() + .requiredState(requiredState: requiredState) + .filters(filters: filters) + .name(name: "AllRooms") + .syncMode(mode: .growingFullSync) + .batchSize(batchSize: 100) + .roomLimit(limit: 500) + .build() + + let visibleRoomsViewProxy = SlidingSyncViewProxy(slidingSync: slidingSync, slidingSyncView: visibleRoomsView) + + let allRoomsViewProxy = SlidingSyncViewProxy(slidingSync: slidingSync, slidingSyncView: allRoomsView) + + visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: visibleRoomsViewProxy, + roomMessageFactory: RoomMessageFactory()) + + allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: allRoomsViewProxy, + roomMessageFactory: RoomMessageFactory()) + + visibleRoomsViewProxy.visibleRangeUpdatePublisher.sink { [weak self] in + self?.restartSync() + } + .store(in: &cancellables) + + visibleRoomsViewProxyStateObservationToken = visibleRoomsViewProxy.diffPublisher.sink { [weak self] _ in + self?.registerAllRoomSlidingSyncView() + self?.visibleRoomsViewProxyStateObservationToken = nil + } + + visibleRoomsSlidingSyncView = visibleRoomsView + allRoomsSlidingSyncView = allRoomsView + + } catch { + MXLog.error("Failed building sliding sync views with error: \(error)") + } + } + + private func registerAllRoomSlidingSyncView() { + guard let allRoomsSlidingSyncView else { + MXLog.error("All rooms sliding sync view unavailable") + return + } + + registerSlidingSyncView(allRoomsSlidingSyncView) + } + + private func registerSlidingSyncView(_ view: SlidingSyncView) { + _ = slidingSync?.addView(view: view) + restartSync() + } private func roomTupleForIdentifier(_ identifier: String) -> (SlidingSyncRoom?, Room?) { do { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index a105d1145..ede21e330 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -79,8 +79,6 @@ protocol ClientProxyProtocol: AnyObject, MediaProxyProtocol { func stopSync() - func restartSync() - func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? func loadUserDisplayName() async -> Result diff --git a/ElementX/Sources/Services/Client/MockClientProxy.swift b/ElementX/Sources/Services/Client/MockClientProxy.swift index 6d36255e4..c47ab0f05 100644 --- a/ElementX/Sources/Services/Client/MockClientProxy.swift +++ b/ElementX/Sources/Services/Client/MockClientProxy.swift @@ -40,8 +40,6 @@ class MockClientProxy: ClientProxyProtocol { func stopSync() { } - func restartSync() { } - func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? { guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else { return nil diff --git a/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift b/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift index ade1955db..839ab6785 100644 --- a/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift +++ b/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift @@ -51,7 +51,6 @@ private class SlidingSyncViewObserver: SlidingSyncViewRoomListObserver, SlidingS } class SlidingSyncViewProxy { - private weak var clientProxy: ClientProxyProtocol? private let slidingSync: SlidingSyncProtocol private let slidingSyncView: SlidingSyncViewProtocol @@ -64,6 +63,7 @@ class SlidingSyncViewProxy { let diffPublisher = PassthroughSubject() let statePublisher = PassthroughSubject() let countPublisher = PassthroughSubject() + let visibleRangeUpdatePublisher = PassthroughSubject() deinit { listUpdateObserverToken?.cancel() @@ -71,8 +71,7 @@ class SlidingSyncViewProxy { countUpdateObserverToken?.cancel() } - init(clientProxy: ClientProxyProtocol, slidingSync: SlidingSyncProtocol, slidingSyncView: SlidingSyncViewProtocol) { - self.clientProxy = clientProxy + init(slidingSync: SlidingSyncProtocol, slidingSyncView: SlidingSyncViewProtocol) { self.slidingSync = slidingSync self.slidingSyncView = slidingSyncView @@ -103,11 +102,11 @@ class SlidingSyncViewProxy { try slidingSync.getRoom(roomId: identifier) } - func updateVisibleRange(_ range: ClosedRange) { + func updateVisibleRange(_ range: Range) { MXLog.info("Setting sliding sync view range to \(range)") slidingSyncView.setRange(start: UInt32(range.lowerBound), end: UInt32(range.upperBound)) - clientProxy?.restartSync() + visibleRangeUpdatePublisher.send(()) } } diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift index 4a14e9d80..d97d8e198 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift @@ -46,7 +46,7 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol { func updateRoomsWithIdentifiers(_ identifiers: [String]) { } - func updateVisibleRange(_ range: ClosedRange) { } + func updateVisibleRange(_ range: Range) { } // MARK: - Private diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index b6b58a077..372bb4646 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -68,7 +68,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } } - func updateVisibleRange(_ range: ClosedRange) { + func updateVisibleRange(_ range: Range) { slidingSyncViewProxy.updateVisibleRange(range) } @@ -181,6 +181,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } } + // swiftlint:disable:next cyclomatic_complexity private func buildDiff(from diff: SlidingSyncViewRoomsListDiff, on rooms: [RoomSummary]) -> CollectionDifference? { var changes = [CollectionDifference.Change]() @@ -216,6 +217,18 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { for (index, value) in values.enumerated() { changes.append(.insert(offset: index, element: buildSummaryForRoomListEntry(value), associatedWith: nil)) } + case .clear: + MXLog.verbose("Clear all items, current total count: \(rooms.count)") + for (index, value) in rooms.enumerated() { + changes.append(.remove(offset: index, element: value, associatedWith: nil)) + } + case .pop: + MXLog.verbose("Pop, current total count: \(rooms.count)") + guard let value = rooms.last else { + fatalError() + } + + changes.append(.remove(offset: rooms.count - 1, element: value, associatedWith: nil)) } return CollectionDifference(changes) diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift index 0613ec760..f622f7077 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift @@ -55,5 +55,5 @@ protocol RoomSummaryProviderProtocol { /// - Parameter identifiers: the identifiers for the rooms that have changed func updateRoomsWithIdentifiers(_ identifiers: [String]) - func updateVisibleRange(_ range: ClosedRange) + func updateVisibleRange(_ range: Range) } diff --git a/project.yml b/project.yml index 925e7919c..d23a78ec5 100644 --- a/project.yml +++ b/project.yml @@ -40,7 +40,7 @@ include: packages: MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.0.28-alpha + exactVersion: 1.0.30-alpha # path: ../matrix-rust-sdk DesignKit: path: ./