Room list fixes (#431)

* Fixes #396 - Avoid duplicates in room list, cleanup logic throughout

* Re-enabled the allRoomsView with a hard 500 room limit and reduced the diffing collection time.

* Fixes #385, #411 - Filter out spaces, invites and tombstoned rooms from the room list

* Various tweaks following code review
This commit is contained in:
Stefan Ceriu
2023-01-10 17:34:42 +02:00
committed by GitHub
parent edc06bf662
commit 3d1266ca25
9 changed files with 151 additions and 107 deletions
@@ -141,7 +141,7 @@ class BugReportService: BugReportServiceProtocol {
}
return result
} // swiftlint: enable function_body_length
}
// MARK: - Private
@@ -72,6 +72,7 @@ class ClientProxy: ClientProxyProtocol {
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
// swiftlint:disable:next function_body_length
init(client: ClientProtocol,
backgroundTaskService: BackgroundTaskServiceProtocol) async {
self.client = client
@@ -80,15 +81,29 @@ class ClientProxy: ClientProxyProtocol {
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: 30)
.requiredState(requiredState: [RequiredState(key: "m.room.avatar", value: ""),
RequiredState(key: "m.room.encryption", value: "")])
.timelineLimit(limit: 20)
.requiredState(requiredState: requiredState)
.filters(filters: filters)
.name(name: "CurrentlyVisibleRooms")
.syncMode(mode: .selective)
.addRange(from: 0, to: 20)
@@ -96,16 +111,17 @@ class ClientProxy: ClientProxyProtocol {
let allRoomsView = try SlidingSyncViewBuilder()
.noTimelineLimit()
.requiredState(requiredState: [RequiredState(key: "m.room.avatar", value: ""),
RequiredState(key: "m.room.encryption", value: "")])
.requiredState(requiredState: requiredState)
.filters(filters: filters)
.name(name: "AllRooms")
.syncMode(mode: .growingFullSync)
.batchSize(batchSize: 20)
.batchSize(batchSize: 100)
.roomLimit(limit: 500)
.build()
let slidingSync = try slidingSyncBuilder
.addView(v: visibleRoomsView)
// .addView(v: allRoomsView) // FIXME: Intentionally disabled as it doesn't work properly
.addView(v: allRoomsView)
.withCommonExtensions()
.coldCache(name: "ElementX")
.build()
@@ -43,12 +43,16 @@ class MockClientProxy: ClientProxyProtocol {
func restartSync() { }
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }),
let displayName = room.asFilled?.name else {
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
return nil
}
return MockRoomProxy(displayName: displayName)
switch room {
case .empty:
return MockRoomProxy(displayName: "Empty room")
case .filled(let details), .invalidated(let details):
return MockRoomProxy(displayName: details.name)
}
}
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
@@ -71,6 +71,6 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
lastMessage: try? AttributedString(markdown: "**@mock:client.com**: T-bone beef ribs bacon"),
lastMessageTimestamp: .distantPast,
unreadNotificationCount: 0)),
.empty(id: "3")
.empty
]
}
@@ -56,7 +56,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
.store(in: &cancellables)
slidingSyncViewProxy.diffPublisher
.collect(.byTime(serialDispatchQueue, 0.25))
.collect(.byTime(serialDispatchQueue, 0.1))
.sink { [weak self] in self?.updateRoomsWithDiffs($0) }
.store(in: &cancellables)
}
@@ -65,15 +65,16 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
guard statePublisher.value == .live else {
return
}
var changes = [CollectionDifference<RoomSummary>.Change]()
for identifier in identifiers {
guard let index = rooms.firstIndex(where: { $0.id == identifier }) else {
guard let index = rooms.firstIndex(where: { $0.id == identifier }),
let roomListEntry = slidingSyncViewProxy.currentRoomsList().first(where: { $0.id == identifier }) else {
continue
}
let oldRoom = rooms[index]
let newRoom = buildRoomSummaryForIdentifier(identifier)
let newRoom = buildRoomSummaryForIdentifier(identifier, invalidated: roomListEntry.isInvalidated)
changes.append(.remove(offset: index, element: oldRoom, associatedWith: nil))
changes.append(.insert(offset: index, element: newRoom, associatedWith: nil))
@@ -120,15 +121,11 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
MXLog.info("Finished applying diffs")
}
private func buildEmptyRoomSummary(forIdentifier identifier: String = UUID().uuidString) -> RoomSummary {
.empty(id: identifier)
}
private func buildRoomSummaryForIdentifier(_ identifier: String) -> RoomSummary {
private func buildRoomSummaryForIdentifier(_ identifier: String, invalidated: Bool) -> RoomSummary {
guard let room = try? slidingSyncViewProxy.roomForIdentifier(identifier) else {
MXLog.error("Failed finding room with id: \(identifier)")
return buildEmptyRoomSummary(forIdentifier: identifier)
return .empty
}
var attributedLastMessage: AttributedString?
@@ -145,23 +142,25 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
lastMessageTimestamp = lastMessage.timestamp
}
return .filled(details: RoomSummaryDetails(id: room.roomId(),
name: room.name() ?? room.roomId(),
isDirect: room.isDm() ?? false,
avatarURLString: room.fullRoom()?.avatarUrl(),
lastMessage: attributedLastMessage,
lastMessageTimestamp: lastMessageTimestamp,
unreadNotificationCount: UInt(room.unreadNotifications().notificationCount())))
let details = RoomSummaryDetails(id: room.roomId(),
name: room.name() ?? room.roomId(),
isDirect: room.isDm() ?? false,
avatarURLString: room.fullRoom()?.avatarUrl(),
lastMessage: attributedLastMessage,
lastMessageTimestamp: lastMessageTimestamp,
unreadNotificationCount: UInt(room.unreadNotifications().notificationCount()))
return invalidated ? .invalidated(details: details) : .filled(details: details)
}
private func buildSummaryForRoomListEntry(_ entry: RoomListEntry) -> RoomSummary {
switch entry {
case .empty:
return buildEmptyRoomSummary()
return .empty
case .filled(let roomId):
return buildRoomSummaryForIdentifier(roomId)
return buildRoomSummaryForIdentifier(roomId, invalidated: false)
case .invalidated(let roomId):
return buildRoomSummaryForIdentifier(roomId)
return buildRoomSummaryForIdentifier(roomId, invalidated: true)
}
}
@@ -236,3 +235,23 @@ extension RoomSummaryProviderState {
}
}
}
extension MatrixRustSDK.RoomListEntry {
var id: String? {
switch self {
case .empty:
return nil
case .invalidated(let roomId), .filled(let roomId):
return roomId
}
}
var isInvalidated: Bool {
switch self {
case .invalidated:
return true
default:
return false
}
}
}
@@ -16,6 +16,7 @@
import Combine
import Foundation
import MatrixRustSDK
enum RoomSummaryProviderState {
case cold
@@ -24,25 +25,18 @@ enum RoomSummaryProviderState {
case live
}
enum RoomSummary: Identifiable {
case empty(id: String)
enum RoomSummary {
case empty
case filled(details: RoomSummaryDetails)
case invalidated(details: RoomSummaryDetails)
var id: String {
var id: String? {
switch self {
case .empty(let id):
return id
case .filled(let summary):
return summary.id
}
}
var asFilled: RoomSummaryDetails? {
guard case let .filled(details) = self else {
case .empty:
return nil
case .invalidated(let details), .filled(let details):
return details.id
}
return details
}
}