mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 03:06:37 +00:00
SDK Bump + Back pagination state listener (#1274)
* backPagination improved * sdk bump * removed unused line * Update ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * cancelling the TaskHandle on deallocation --------- Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
@@ -184,7 +184,6 @@
|
||||
43F35A7E5703D64DB0519C59 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD469F7513574341181F7EAA /* ServerSelectionScreen.swift */; };
|
||||
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; };
|
||||
44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */; };
|
||||
447E8580A0A2569E32529E17 /* MockRoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */; };
|
||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
|
||||
46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */; };
|
||||
46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565F1B2B300597C616B37888 /* FullscreenDialog.swift */; };
|
||||
@@ -1142,7 +1141,6 @@
|
||||
8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenModels.swift; sourceTree = "<group>"; };
|
||||
8C8616254EE40CA8BA5E9BC2 /* VideoRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
||||
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = "<group>"; };
|
||||
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||
@@ -3502,7 +3500,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
190EC7285D3CFEF0D3011BCF /* GeoURI.swift */,
|
||||
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */,
|
||||
66F2402D738694F98729A441 /* RoomTimelineProvider.swift */,
|
||||
095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */,
|
||||
00B62EE933FC3D5651AF4607 /* TimelineEventProxy.swift */,
|
||||
@@ -4300,7 +4297,6 @@
|
||||
2352C541AF857241489756FF /* MockRoomSummaryProvider.swift in Sources */,
|
||||
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */,
|
||||
158A2D528CC78C4E7A8ED608 /* MockRoomTimelineControllerFactory.swift in Sources */,
|
||||
447E8580A0A2569E32529E17 /* MockRoomTimelineProvider.swift in Sources */,
|
||||
B721125D17A0BA86794F29FB /* MockServerSelectionScreenState.swift in Sources */,
|
||||
AF2ABA2794E376B64104C964 /* MockSoftLogoutScreenState.swift in Sources */,
|
||||
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */,
|
||||
@@ -5207,7 +5203,7 @@
|
||||
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = "1.0.95-alpha";
|
||||
version = "1.0.96-alpha";
|
||||
};
|
||||
};
|
||||
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
|
||||
|
||||
@@ -111,8 +111,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
||||
"state" : {
|
||||
"revision" : "822c8b33dc6da7eb0951890af0197f633095188a",
|
||||
"version" : "1.0.95-alpha"
|
||||
"revision" : "8bc8015237083035cb5f4a00d1eedb9ebbbb83c6",
|
||||
"version" : "1.0.96-alpha"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1075,6 +1075,20 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
class RoomTimelineProviderMock: RoomTimelineProviderProtocol {
|
||||
var updatePublisher: AnyPublisher<TimelineProviderUpdate, Never> {
|
||||
get { return underlyingUpdatePublisher }
|
||||
set(value) { underlyingUpdatePublisher = value }
|
||||
}
|
||||
var underlyingUpdatePublisher: AnyPublisher<TimelineProviderUpdate, Never>!
|
||||
var itemProxies: [TimelineItemProxy] = []
|
||||
var backPaginationState: BackPaginationStatus {
|
||||
get { return underlyingBackPaginationState }
|
||||
set(value) { underlyingBackPaginationState = value }
|
||||
}
|
||||
var underlyingBackPaginationState: BackPaginationStatus!
|
||||
|
||||
}
|
||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
||||
get { return underlyingCallbacks }
|
||||
|
||||
@@ -651,31 +651,6 @@ class SDKClientMock: SDKClientProtocol {
|
||||
setPusherIdentifiersKindAppDisplayNameDeviceDisplayNameProfileTagLangReceivedInvocations.append((identifiers: identifiers, kind: kind, appDisplayName: appDisplayName, deviceDisplayName: deviceDisplayName, profileTag: profileTag, lang: lang))
|
||||
try setPusherIdentifiersKindAppDisplayNameDeviceDisplayNameProfileTagLangClosure?(`identifiers`, `kind`, `appDisplayName`, `deviceDisplayName`, `profileTag`, `lang`)
|
||||
}
|
||||
//MARK: - `slidingSync`
|
||||
|
||||
public var slidingSyncIdThrowableError: Error?
|
||||
public var slidingSyncIdCallsCount = 0
|
||||
public var slidingSyncIdCalled: Bool {
|
||||
return slidingSyncIdCallsCount > 0
|
||||
}
|
||||
public var slidingSyncIdReceivedId: String?
|
||||
public var slidingSyncIdReceivedInvocations: [String] = []
|
||||
public var slidingSyncIdReturnValue: SlidingSyncBuilder!
|
||||
public var slidingSyncIdClosure: ((String) throws -> SlidingSyncBuilder)?
|
||||
|
||||
public func `slidingSync`(`id`: String) throws -> SlidingSyncBuilder {
|
||||
if let error = slidingSyncIdThrowableError {
|
||||
throw error
|
||||
}
|
||||
slidingSyncIdCallsCount += 1
|
||||
slidingSyncIdReceivedId = id
|
||||
slidingSyncIdReceivedInvocations.append(`id`)
|
||||
if let slidingSyncIdClosure = slidingSyncIdClosure {
|
||||
return try slidingSyncIdClosure(`id`)
|
||||
} else {
|
||||
return slidingSyncIdReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - `unignoreUser`
|
||||
|
||||
public var unignoreUserUserIdThrowableError: Error?
|
||||
|
||||
@@ -34,7 +34,9 @@ class RoomProxy: RoomProxyProtocol {
|
||||
private(set) var displayName: String?
|
||||
|
||||
private var roomTimelineObservationToken: TaskHandle?
|
||||
private var backPaginationStateObservationToken: TaskHandle?
|
||||
|
||||
private let backPaginationStateSubject = PassthroughSubject<BackPaginationStatus, Never>()
|
||||
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> {
|
||||
membersSubject.eraseToAnyPublisher()
|
||||
@@ -50,9 +52,10 @@ class RoomProxy: RoomProxyProtocol {
|
||||
var timelineProvider: RoomTimelineProviderProtocol {
|
||||
innerTimelineProvider
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
roomTimelineObservationToken?.cancel()
|
||||
backPaginationStateObservationToken?.cancel()
|
||||
roomListItem.unsubscribe()
|
||||
}
|
||||
|
||||
@@ -79,9 +82,13 @@ class RoomProxy: RoomProxyProtocol {
|
||||
|
||||
let result = await room.addTimelineListener(listener: timelineListener)
|
||||
roomTimelineObservationToken = result.itemsStream
|
||||
|
||||
subscribeToBackpagination()
|
||||
|
||||
innerTimelineProvider = RoomTimelineProvider(currentItems: result.items, updatePublisher: updatesPublisher)
|
||||
|
||||
innerTimelineProvider = RoomTimelineProvider(currentItems: result.items,
|
||||
updatePublisher: updatesPublisher,
|
||||
backPaginationStatePublisher: backPaginationStateSubject.eraseToAnyPublisher())
|
||||
|
||||
Task {
|
||||
await fetchMembers()
|
||||
await updateMembers()
|
||||
@@ -651,9 +658,20 @@ class RoomProxy: RoomProxyProtocol {
|
||||
private func update(displayName: String) {
|
||||
self.displayName = displayName
|
||||
}
|
||||
|
||||
private func subscribeToBackpagination() {
|
||||
let listener = RoomBackpaginationStatusListener { [weak self] status in
|
||||
self?.backPaginationStateSubject.send(status)
|
||||
}
|
||||
do {
|
||||
backPaginationStateObservationToken = try room.subscribeToBackPaginationStatus(listener: listener)
|
||||
} catch {
|
||||
MXLog.error("Failed to subscribe to back pagination state with error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RoomTimelineListener: TimelineListener {
|
||||
private final class RoomTimelineListener: TimelineListener {
|
||||
private let onUpdateClosure: (TimelineDiff) -> Void
|
||||
|
||||
init(_ onUpdateClosure: @escaping (TimelineDiff) -> Void) {
|
||||
@@ -665,7 +683,7 @@ private class RoomTimelineListener: TimelineListener {
|
||||
}
|
||||
}
|
||||
|
||||
private class UploadProgressListener: ProgressWatcher {
|
||||
private final class UploadProgressListener: ProgressWatcher {
|
||||
private let onUpdateClosure: (Double) -> Void
|
||||
|
||||
init(_ onUpdateClosure: @escaping (Double) -> Void) {
|
||||
@@ -678,3 +696,15 @@ private class UploadProgressListener: ProgressWatcher {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class RoomBackpaginationStatusListener: BackPaginationStatusListener {
|
||||
private let onUpdateClosure: (BackPaginationStatus) -> Void
|
||||
|
||||
init(_ onUpdateClosure: @escaping (BackPaginationStatus) -> Void) {
|
||||
self.onUpdateClosure = onUpdateClosure
|
||||
}
|
||||
|
||||
func onUpdate(status: BackPaginationStatus) {
|
||||
onUpdateClosure(status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +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 MatrixRustSDK
|
||||
|
||||
struct MockRoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
var itemsPublisher = CurrentValuePublisher<[TimelineItemProxy], Never>([])
|
||||
|
||||
private var itemProxies = [TimelineItemProxy]()
|
||||
}
|
||||
@@ -21,31 +21,41 @@ import MatrixRustSDK
|
||||
class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private let serialDispatchQueue: DispatchQueue
|
||||
|
||||
private let itemsSubject = CurrentValueSubject<[TimelineItemProxy], Never>([])
|
||||
|
||||
var itemsPublisher: CurrentValuePublisher<[TimelineItemProxy], Never> {
|
||||
itemsSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private var itemProxies: [TimelineItemProxy] {
|
||||
didSet {
|
||||
itemsSubject.send(itemProxies)
|
||||
}
|
||||
}
|
||||
|
||||
init(currentItems: [TimelineItem], updatePublisher: AnyPublisher<TimelineDiff, Never>) {
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
|
||||
itemProxies = currentItems.map(TimelineItemProxy.init)
|
||||
private let backPaginationStateSubject = CurrentValueSubject<BackPaginationStatus, Never>(.idle)
|
||||
var backPaginationState: BackPaginationStatus {
|
||||
backPaginationStateSubject.value
|
||||
}
|
||||
|
||||
private let itemProxiesSubject: CurrentValueSubject<[TimelineItemProxy], Never>
|
||||
var itemProxies: [TimelineItemProxy] {
|
||||
itemProxiesSubject.value
|
||||
}
|
||||
|
||||
var updatePublisher: AnyPublisher<TimelineProviderUpdate, Never> {
|
||||
itemProxiesSubject
|
||||
.combineLatest(backPaginationStateSubject)
|
||||
.map(TimelineProviderUpdate.init)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(currentItems: [TimelineItem],
|
||||
updatePublisher: AnyPublisher<TimelineDiff, Never>,
|
||||
backPaginationStatePublisher: AnyPublisher<BackPaginationStatus, Never>) {
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>(currentItems.map(TimelineItemProxy.init))
|
||||
|
||||
// Manually call it here as the didSet doesn't work from constructors
|
||||
itemsSubject.send(itemProxies)
|
||||
|
||||
itemProxiesSubject.send(itemProxies)
|
||||
|
||||
updatePublisher
|
||||
.collect(.byTime(serialDispatchQueue, 0.1))
|
||||
.sink { [weak self] in self?.updateItemsWithDiffs($0) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
backPaginationStatePublisher
|
||||
.sink { [weak self] in self?.backPaginationStateSubject.send($0) }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -59,7 +69,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
|
||||
MXLog.verbose("Received timeline diff")
|
||||
|
||||
itemProxies = diffs
|
||||
let items = diffs
|
||||
.reduce(itemProxies) { currentItems, diff in
|
||||
guard let collectionDiff = buildDiff(from: diff, on: currentItems) else {
|
||||
MXLog.error("Failed building CollectionDifference from \(diff)")
|
||||
@@ -73,6 +83,8 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
|
||||
return updatedItems
|
||||
}
|
||||
|
||||
itemProxiesSubject.send(items)
|
||||
|
||||
MXLog.verbose("Finished applying diffs, current items (\(itemProxies.count)) : \(itemProxies.map(\.debugIdentifier))")
|
||||
}
|
||||
@@ -186,12 +198,8 @@ private extension VirtualTimelineItem {
|
||||
switch self {
|
||||
case .dayDivider(let timestamp):
|
||||
return "DayDiviver(\(timestamp))"
|
||||
case .loadingIndicator:
|
||||
return "LoadingIndicator"
|
||||
case .readMarker:
|
||||
return "ReadMarker"
|
||||
case .timelineStart:
|
||||
return "TimelineStart"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,17 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineProviderProtocol {
|
||||
var itemsPublisher: CurrentValuePublisher<[TimelineItemProxy], Never> { get }
|
||||
import MatrixRustSDK
|
||||
|
||||
struct TimelineProviderUpdate {
|
||||
let items: [TimelineItemProxy]
|
||||
let backPaginationState: BackPaginationStatus
|
||||
}
|
||||
|
||||
@MainActor
|
||||
// sourcery: AutoMockable
|
||||
protocol RoomTimelineProviderProtocol {
|
||||
var updatePublisher: AnyPublisher<TimelineProviderUpdate, Never> { get }
|
||||
var itemProxies: [TimelineItemProxy] { get }
|
||||
var backPaginationState: BackPaginationStatus { get }
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
|
||||
timelineProvider
|
||||
.itemsPublisher
|
||||
.updatePublisher
|
||||
.receive(on: serialDispatchQueue)
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
@@ -183,7 +183,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
// Handle this parallel to the timeline items so we're not forced
|
||||
// to bundle the Rust side objects within them
|
||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
|
||||
for timelineItemProxy in timelineProvider.itemsPublisher.value {
|
||||
for timelineItemProxy in timelineProvider.itemProxies {
|
||||
switch timelineItemProxy {
|
||||
case .event(let item):
|
||||
if item.id == itemID {
|
||||
@@ -245,7 +245,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
var isBackPaginating = false
|
||||
var lastEncryptedHistoryItemIndex: Int?
|
||||
|
||||
let collapsibleChunks = timelineProvider.itemsPublisher.value.groupBy { isItemCollapsible($0) }
|
||||
let collapsibleChunks = timelineProvider.itemProxies.groupBy { isItemCollapsible($0) }
|
||||
|
||||
for (index, collapsibleChunk) in collapsibleChunks.enumerated() {
|
||||
let isLastItem = index == collapsibleChunks.indices.last
|
||||
@@ -294,6 +294,17 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
// Remove everything up to the last encrypted history item.
|
||||
// It only contains encrypted messages, state changes and date separators.
|
||||
newTimelineItems.removeFirst(lastEncryptedHistoryItemIndex)
|
||||
} else {
|
||||
// Otherwise check if we need to add anything to the top of the timeline.
|
||||
switch timelineProvider.backPaginationState {
|
||||
case .timelineStartReached:
|
||||
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.displayName ?? roomProxy.name)
|
||||
newTimelineItems.insert(timelineStart, at: 0)
|
||||
case .paginating:
|
||||
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(), at: 0)
|
||||
case .idle:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
timelineItems = newTimelineItems
|
||||
@@ -330,10 +341,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return SeparatorRoomTimelineItem(id: identifier, text: dateString)
|
||||
case .readMarker:
|
||||
return ReadMarkerRoomTimelineItem()
|
||||
case .loadingIndicator:
|
||||
return PaginationIndicatorRoomTimelineItem()
|
||||
case .timelineStart:
|
||||
return TimelineStartRoomTimelineItem(name: roomProxy.displayName ?? roomProxy.name)
|
||||
}
|
||||
case .unknown:
|
||||
return nil
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ include:
|
||||
packages:
|
||||
MatrixRustSDK:
|
||||
url: https://github.com/matrix-org/matrix-rust-components-swift
|
||||
exactVersion: 1.0.95-alpha
|
||||
exactVersion: 1.0.96-alpha
|
||||
# path: ../matrix-rust-sdk
|
||||
DesignKit:
|
||||
path: DesignKit
|
||||
|
||||
Reference in New Issue
Block a user