Skip to content

Commit

Permalink
New SlidingSync flow (#373)
Browse files Browse the repository at this point in the history
* Switch back to using slidingSync rooms for timeline listeners

* Expose 2 types of slidingSync views from the clientProxy and combine their results for the roomList

* Fix breaking api changes

* Remove sender mxids from the room list (until rust provides resolved display names)

* Bump RustSDK to v1.0.22-alpha

* Rename originServerTs to timestamp throughout

* Simplified sliding sync view list merging

* Rollback some SS changes as things still don't work properly

* Revert "Switch back to using slidingSync rooms for timeline listeners"

This reverts commit 1d6a6c0.
  • Loading branch information
stefanceriu authored Dec 16, 2022
1 parent 6f974a4 commit ab10278
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 76 deletions.
2 changes: 1 addition & 1 deletion ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3759,7 +3759,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = {
kind = exactVersion;
version = "0.0.9-demo";
version = "1.0.22-alpha";
};
};
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : {
"revision" : "2645cfb64f3255f299a63af280b5172a6dd6602c",
"version" : "0.0.9-demo"
"revision" : "65d6f8a51367d5c411c1fec39402907f588fcd67",
"version" : "1.0.22-alpha"
}
},
{
Expand Down
82 changes: 54 additions & 28 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState, Hom

class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
private let userSession: UserSessionProtocol
private let roomSummaryProvider: RoomSummaryProviderProtocol?
private let visibleRoomsSummaryProvider: RoomSummaryProviderProtocol?
private let allRoomsSummaryProvider: RoomSummaryProviderProtocol?
private let attributedStringBuilder: AttributedStringBuilderProtocol
private var roomsForIdentifiers = [String: HomeScreenRoom]()

Expand All @@ -32,9 +33,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
// swiftlint:disable:next function_body_length
init(userSession: UserSessionProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) {
self.userSession = userSession
roomSummaryProvider = userSession.clientProxy.roomSummaryProvider
self.attributedStringBuilder = attributedStringBuilder

visibleRoomsSummaryProvider = userSession.clientProxy.visibleRoomsSummaryProvider
allRoomsSummaryProvider = userSession.clientProxy.allRoomsSummaryProvider

super.init(initialViewState: HomeScreenViewState(userID: userSession.userID))

userSession.callbacks
Expand Down Expand Up @@ -65,14 +68,15 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}
}

guard let roomSummaryProvider else {
guard let visibleRoomsSummaryProvider, let allRoomsSummaryProvider else {
MXLog.error("Room summary provider unavailable")
return
}

Publishers.CombineLatest3(roomSummaryProvider.statePublisher,
roomSummaryProvider.countPublisher,
roomSummaryProvider.roomListPublisher)
// Combine all 3 publishers to correctly compute the screen state
Publishers.CombineLatest3(visibleRoomsSummaryProvider.statePublisher,
visibleRoomsSummaryProvider.countPublisher,
visibleRoomsSummaryProvider.roomListPublisher)
.receive(on: DispatchQueue.main)
.sink { [weak self] state, totalCount, rooms in
if state != .live {
Expand All @@ -89,7 +93,16 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}
.store(in: &cancellables)

roomSummaryProvider.roomListPublisher
// 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
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.updateRooms()
Expand Down Expand Up @@ -127,12 +140,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
// MARK: - Private

private func loadDataForRoomIdentifier(_ identifier: String) {
guard let roomSummaryProvider else {
guard let visibleRoomsSummaryProvider else {
MXLog.error("Room summary provider unavailable")
return
}

guard let roomSummary = roomSummaryProvider.roomListPublisher.value.first(where: { $0.asFilled?.id == identifier })?.asFilled,
guard let roomSummary = visibleRoomsSummaryProvider.roomListPublisher.value.first(where: { $0.asFilled?.id == identifier })?.asFilled,
let roomIndex = state.rooms.firstIndex(where: { $0.id == identifier }) else {
return
}
Expand All @@ -153,36 +166,31 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}
}

/// 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 roomSummaryProvider else {
guard let visibleRoomsSummaryProvider else {
MXLog.error("Room summary provider unavailable")
return
}

var rooms = [HomeScreenRoom]()
var newRoomsForIdentifiers = [String: HomeScreenRoom]()

for summary in roomSummaryProvider.roomListPublisher.value {
for (index, summary) in visibleRoomsSummaryProvider.roomListPublisher.value.enumerated() {
switch summary {
case .empty(let id):
rooms.append(HomeScreenRoom.placeholder(id: id))
case .filled(let summary):
let oldRoom = roomsForIdentifiers[summary.id]

let avatarImage = userSession.mediaProvider.imageFromURLString(summary.avatarURLString, avatarSize: .room(on: .home))

var timestamp: String?
if let lastMessageTimestamp = summary.lastMessageTimestamp {
timestamp = lastMessageTimestamp.formatted(date: .omitted, time: .shortened)
guard case let .filled(summary) = allRoomsSummaryProvider?.roomListPublisher.value[safe: index] else {
rooms.append(HomeScreenRoom.placeholder(id: id))
continue
}

let room = HomeScreenRoom(id: summary.id,
name: summary.name,
hasUnreads: summary.unreadNotificationCount > 0,
timestamp: timestamp ?? oldRoom?.timestamp,
lastMessage: summary.lastMessage ?? oldRoom?.lastMessage,
avatar: avatarImage ?? oldRoom?.avatar)

let room = buildRoomForSummary(summary)
rooms.append(room)
newRoomsForIdentifiers[summary.id] = room
case .filled(let summary):
let room = buildRoomForSummary(summary)
rooms.append(room)
newRoomsForIdentifiers[summary.id] = room
}
Expand All @@ -192,6 +200,24 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
roomsForIdentifiers = newRoomsForIdentifiers
}

private func buildRoomForSummary(_ summary: RoomSummaryDetails) -> HomeScreenRoom {
let oldRoom = roomsForIdentifiers[summary.id]

let avatarImage = userSession.mediaProvider.imageFromURLString(summary.avatarURLString, avatarSize: .room(on: .home))

var timestamp: String?
if let lastMessageTimestamp = summary.lastMessageTimestamp {
timestamp = lastMessageTimestamp.formatted(date: .omitted, time: .shortened)
}

return HomeScreenRoom(id: summary.id,
name: summary.name,
hasUnreads: summary.unreadNotificationCount > 0,
timestamp: timestamp ?? oldRoom?.timestamp,
lastMessage: summary.lastMessage ?? oldRoom?.lastMessage,
avatar: avatarImage ?? oldRoom?.avatar)
}

private func updateVisibleRange(visibleItemIdentifiers items: Set<String>) {
let result = items.compactMap { itemIdentifier in
state.rooms.firstIndex { $0.id == itemIdentifier }
Expand All @@ -205,6 +231,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return
}

userSession.clientProxy.roomSummaryProvider?.updateVisibleRange(lowerBound...upperBound)
visibleRoomsSummaryProvider?.updateVisibleRange(lowerBound...upperBound)
}
}
34 changes: 26 additions & 8 deletions ElementX/Sources/Services/Client/ClientProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ class ClientProxy: ClientProxyProtocol {
private var slidingSyncObserverToken: StoppableSpawn?
private var slidingSync: SlidingSync?

var roomSummaryProvider: RoomSummaryProviderProtocol?
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol?

var allRoomsSummaryProvider: RoomSummaryProviderProtocol?

deinit {
// These need to be inlined instead of using stopSync()
Expand All @@ -83,25 +85,40 @@ class ClientProxy: ClientProxyProtocol {
do {
let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString)

let slidingSyncView = try SlidingSyncViewBuilder()
let visibleRoomsView = try SlidingSyncViewBuilder()
.timelineLimit(limit: 10)
.requiredState(requiredState: [RequiredState(key: "m.room.avatar", value: ""),
RequiredState(key: "m.room.encryption", value: "")])
.name(name: "HomeScreenView")
.name(name: "CurrentlyVisibleRooms")
.syncMode(mode: .selective)
.addRange(from: 0, to: 20)
.build()

let allRoomsView = try SlidingSyncViewBuilder()
.noTimelineLimit()
.requiredState(requiredState: [RequiredState(key: "m.room.avatar", value: ""),
RequiredState(key: "m.room.encryption", value: "")])
.name(name: "AllRooms")
.syncMode(mode: .growingFullSync)
.batchSize(batchSize: 20)
.build()

let slidingSync = try slidingSyncBuilder
.addView(v: slidingSyncView)
.addView(v: visibleRoomsView)
// .addView(v: allRoomsView) // FIXME: Intentionally disabled as it doesn't work properly
.withCommonExtensions()
.coldCache(name: "ElementX")
.build()

let slidingSyncViewProxy = SlidingSyncViewProxy(clientProxy: self, slidingSync: slidingSync, slidingSyncView: slidingSyncView)
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.roomSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: slidingSyncViewProxy,
roomMessageFactory: RoomMessageFactory())
self.allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: allRoomsViewProxy,
roomMessageFactory: RoomMessageFactory())

self.slidingSync = slidingSync
} catch {
Expand Down Expand Up @@ -286,7 +303,8 @@ class ClientProxy: ClientProxyProtocol {
}

fileprivate func didReceiveSlidingSyncUpdate(summary: UpdateSummary) {
roomSummaryProvider?.updateRoomsWithIdentifiers(summary.rooms)
visibleRoomsSummaryProvider?.updateRoomsWithIdentifiers(summary.rooms)
allRoomsSummaryProvider?.updateRoomsWithIdentifiers(summary.rooms)

callbacks.send(.receivedSyncUpdate)
}
Expand Down
4 changes: 3 additions & 1 deletion ElementX/Sources/Services/Client/ClientProxyProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ protocol ClientProxyProtocol: AnyObject, MediaProxyProtocol {

var restorationToken: RestorationToken? { get }

var roomSummaryProvider: RoomSummaryProviderProtocol? { get }
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? { get }

var allRoomsSummaryProvider: RoomSummaryProviderProtocol? { get }

func startSync()

Expand Down
6 changes: 4 additions & 2 deletions ElementX/Sources/Services/Client/MockClientProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ class MockClientProxy: ClientProxyProtocol {
let homeserver = ""
let restorationToken: RestorationToken? = nil

var roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()

var allRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()

internal init(userIdentifier: String, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) {
self.userIdentifier = userIdentifier
self.roomSummaryProvider = roomSummaryProvider
visibleRoomsSummaryProvider = roomSummaryProvider
}

func startSync() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ protocol RoomMessageProtocol {
var id: String { get }
var body: String { get }
var sender: String { get }
var originServerTs: Date { get }
var timestamp: Date { get }
}
4 changes: 2 additions & 2 deletions ElementX/Sources/Services/Room/RoomMessageFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ struct SomeRoomMessage: RoomMessageProtocol {
let id: String
let body: String
let sender: String
let originServerTs: Date
let timestamp: Date
}

struct RoomMessageFactory: RoomMessageFactoryProtocol {
func buildRoomMessageFrom(_ eventItemProxy: EventTimelineItemProxy) -> RoomMessageProtocol {
SomeRoomMessage(id: eventItemProxy.id,
body: eventItemProxy.body ?? "",
sender: eventItemProxy.sender,
originServerTs: eventItemProxy.originServerTs)
timestamp: eventItemProxy.timestamp)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {

func updateRoomsWithIdentifiers(_ identifiers: [String]) {
#warning("This is a valid check but Rust doesn't set it correctly for selective ranged syncs")
// guard statePublisher.value == .live else {
// return
// }
// guard statePublisher.value == .live else {
// return
// }

var changes = [CollectionDifference<RoomSummary>.Change]()
for identifier in identifiers {
Expand Down Expand Up @@ -136,11 +136,14 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
var lastMessageTimestamp: Date?
if let latestRoomMessage = room.latestRoomMessage() {
let lastMessage = roomMessageFactory.buildRoomMessageFrom(EventTimelineItemProxy(item: latestRoomMessage))
if let lastMessageSender = try? AttributedString(markdown: "**\(lastMessage.sender)**") {
// Don't include the message body in the markdown otherwise it makes tappable links.
attributedLastMessage = lastMessageSender + ": " + AttributedString(lastMessage.body)
}
lastMessageTimestamp = lastMessage.originServerTs

#warning("Intentionally remove the sender mxid from the room list for now")
// if let lastMessageSender = try? AttributedString(markdown: "**\(lastMessage.sender)**") {
// // Don't include the message body in the markdown otherwise it makes tappable links.
// attributedLastMessage = lastMessageSender + ": " + AttributedString(lastMessage.body)
// }
attributedLastMessage = AttributedString(lastMessage.body)
lastMessageTimestamp = lastMessage.timestamp
}

return .filled(details: RoomSummaryDetails(id: room.roomId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ struct MessageTimelineItem<Content: MessageContentProtocol> {
case .transactionId:
return .sending
case .eventId:
return .sent(elapsedTime: Date().timeIntervalSince1970 - originServerTs.timeIntervalSince1970)
return .sent(elapsedTime: Date().timeIntervalSince1970 - timestamp.timeIntervalSince1970)
}
}

Expand Down Expand Up @@ -77,12 +77,8 @@ struct MessageTimelineItem<Content: MessageContentProtocol> {
item.sender()
}

var originServerTs: Date {
if let timestamp = item.originServerTs() {
return Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
} else {
return .now
}
var timestamp: Date {
Date(timeIntervalSince1970: TimeInterval(item.timestamp() / 1000))
}
}

Expand Down
8 changes: 2 additions & 6 deletions ElementX/Sources/Services/Timeline/TimelineItemProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,8 @@ struct EventTimelineItemProxy: CustomDebugStringConvertible {
item.reactions()
}

var originServerTs: Date {
if let timestamp = item.originServerTs() {
return Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
} else {
return .now
}
var timestamp: Date {
Date(timeIntervalSince1970: TimeInterval(item.timestamp() / 1000))
}

// MARK: - CustomDebugStringConvertible
Expand Down
Loading

0 comments on commit ab10278

Please sign in to comment.