Fixes #978 - Message forwarding support

* Add support for message forwarding

* Move the TimelineProvider within the RoomProxy to avoid it going out of sync and have it automatically register for timeline updates on init

* Implement single selection form button style for message forwarding

* Add searching and filter out the source room from the list

* Fixed emoji screen initial search bar height

* Add view models tests

* Add changelog

* Update strings

* Tweaks following code review
This commit is contained in:
Stefan Ceriu
2023-06-21 12:10:14 +03:00
committed by GitHub
parent f624add018
commit 2ae8f471c6
36 changed files with 800 additions and 140 deletions
+47 -30
View File
@@ -46,6 +46,11 @@ class RoomProxy: RoomProxyProtocol {
updatesSubject.eraseToAnyPublisher()
}
var innerTimelineProvider: RoomTimelineProviderProtocol!
var timelineProvider: RoomTimelineProviderProtocol {
innerTimelineProvider
}
deinit {
Task { @MainActor [roomTimelineObservationToken, roomListItem] in
roomTimelineObservationToken?.cancel()
@@ -59,6 +64,30 @@ class RoomProxy: RoomProxyProtocol {
self.roomListItem = roomListItem
self.room = room
self.backgroundTaskService = backgroundTaskService
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.defaultTimelineLimit))
roomListItem.subscribe(settings: settings)
let timelineListener = RoomTimelineListener { [weak self] timelineDiff in
self?.updatesSubject.send(timelineDiff)
}
self.timelineListener = timelineListener
let result = room.addTimelineListener(listener: timelineListener)
roomTimelineObservationToken = result.itemsStream
innerTimelineProvider = RoomTimelineProvider(currentItems: result.items, updatePublisher: updatesPublisher)
Task {
await fetchMembers()
await updateMembers()
}
}
lazy var id: String = room.id()
@@ -162,36 +191,6 @@ class RoomProxy: RoomProxyProtocol {
}
}
func registerTimelineListenerIfNeeded() -> Result<[TimelineItem], RoomProxyError> {
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.defaultTimelineLimit))
roomListItem.subscribe(settings: settings)
let timelineListener = RoomTimelineListener { [weak self] timelineDiff in
self?.updatesSubject.send(timelineDiff)
}
self.timelineListener = timelineListener
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> {
do {
try await Task.dispatch(on: .global()) {
@@ -219,6 +218,24 @@ class RoomProxy: RoomProxyProtocol {
}
}
}
func messageEventContent(for eventID: String) -> RoomMessageEventContent? {
try? room.getTimelineEventContentByEventId(eventId: eventID)
}
func sendMessageEventContent(_ messageContent: RoomMessageEventContent) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let transactionId = genTransactionId()
return await Task.dispatch(on: userInitiatedDispatchQueue) {
self.room.send(msg: messageContent, txnId: transactionId)
return .success(())
}
}
func sendMessage(_ message: String, inReplyTo eventID: String? = nil) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
@@ -20,7 +20,6 @@ import MatrixRustSDK
enum RoomProxyError: Error {
case noMoreMessagesToBackPaginate
case roomListenerAlreadyRegistered
case failedPaginatingBackwards
case failedRetrievingMemberAvatarURL
case failedRetrievingMemberDisplayName
@@ -73,22 +72,23 @@ protocol RoomProxyProtocol {
var activeMembersCount: Int { get }
/// Publishes the room's updates.
/// The publisher starts publishing after the first call to `registerTimelineListenerIfNeeded()`
/// The thread on which this publisher sends the output isn't defined.
var updatesPublisher: AnyPublisher<TimelineDiff, Never> { get }
var timelineProvider: RoomTimelineProviderProtocol { get }
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError>
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
/// Registers a timeline listener if not registered already.
/// Updates for this object will be published on the `updatesPublisher` publisher.
func registerTimelineListenerIfNeeded() -> Result<[TimelineItem], RoomProxyError>
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError>
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError>
func messageEventContent(for eventID: String) -> RoomMessageEventContent?
func sendMessageEventContent(_ messageContent: RoomMessageEventContent) async -> Result<Void, RoomProxyError>
func sendMessage(_ message: String, inReplyTo eventID: String?) async -> Result<Void, RoomProxyError>
func sendReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError>
@@ -19,7 +19,6 @@ import Foundation
import MatrixRustSDK
class RoomTimelineProvider: RoomTimelineProviderProtocol {
private let roomProxy: RoomProxyProtocol
private var cancellables = Set<AnyCancellable>()
private let serialDispatchQueue: DispatchQueue
@@ -35,27 +34,15 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
}
}
init(roomProxy: RoomProxyProtocol) {
self.roomProxy = roomProxy
init(currentItems: [TimelineItem], updatePublisher: AnyPublisher<TimelineDiff, Never>) {
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
itemProxies = []
itemProxies = currentItems.map(TimelineItemProxy.init)
roomProxy
.updatesPublisher
updatePublisher
.collect(.byTime(serialDispatchQueue, 0.1))
.sink { [weak self] in self?.updateItemsWithDiffs($0) }
.store(in: &cancellables)
switch roomProxy.registerTimelineListenerIfNeeded() {
case let .success(items):
itemProxies = items.map(TimelineItemProxy.init)
MXLog.verbose("Added timeline listener, current items (\(items.count)) : \(items.map(\.debugIdentifier))")
case .failure(.roomListenerAlreadyRegistered):
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)")
}
}
// MARK: - Private
@@ -68,7 +68,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
.init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
}
func retryDecryption(for sessionID: String) async { }
// MARK: - UI Test signalling
@@ -43,17 +43,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
init(userId: String,
roomProxy: RoomProxyProtocol,
timelineProvider: RoomTimelineProviderProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) {
self.userId = userId
self.timelineProvider = timelineProvider
self.roomProxy = roomProxy
timelineProvider = roomProxy.timelineProvider
self.timelineItemFactory = timelineItemFactory
self.mediaProvider = mediaProvider
self.roomProxy = roomProxy
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
self.timelineProvider
timelineProvider
.itemsPublisher
.receive(on: serialDispatchQueue)
.sink { [weak self] _ in
@@ -23,7 +23,6 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol {
RoomTimelineController(userId: userId,
roomProxy: roomProxy,
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider)
}