mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Mark rooms as read on push and pop. (#462)
We don't have visibility into timeline item read status so can't do this continuously yet. * Simplify RoomTimelineProvider's responsibilities.
This commit is contained in:
@@ -64,6 +64,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
func stop() {
|
||||
viewModel?.context.send(viewAction: .markRoomAsRead)
|
||||
viewModel?.stop()
|
||||
viewModel = nil
|
||||
parameters = nil
|
||||
@@ -106,7 +107,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
MXLog.debug("Selected \(emoji) for \(itemId)")
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
Task {
|
||||
await timelineController.sendReaction(emoji, for: itemId)
|
||||
await timelineController.sendReaction(emoji, to: itemId)
|
||||
}
|
||||
case .dismiss:
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
|
||||
@@ -42,6 +42,8 @@ enum RoomScreenViewAction {
|
||||
case sendReaction(key: String, eventID: String)
|
||||
case cancelReply
|
||||
case cancelEdit
|
||||
/// Mark the entire room as read - this is heavy handed as a starting point for now.
|
||||
case markRoomAsRead
|
||||
}
|
||||
|
||||
struct RoomScreenViewState: BindableState {
|
||||
|
||||
@@ -41,7 +41,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
self.timelineViewFactory = timelineViewFactory
|
||||
self.mediaProvider = mediaProvider
|
||||
|
||||
super.init(initialViewState: RoomScreenViewState(roomId: timelineController.roomId,
|
||||
super.init(initialViewState: RoomScreenViewState(roomId: timelineController.roomID,
|
||||
roomTitle: roomName ?? "Unknown room 💥",
|
||||
roomAvatar: nil,
|
||||
bindings: .init(composerText: "", composerFocused: false)))
|
||||
@@ -109,13 +109,16 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
case .sendMessage:
|
||||
await sendCurrentMessage()
|
||||
case .sendReaction(let emoji, let itemId):
|
||||
await timelineController.sendReaction(emoji, for: itemId)
|
||||
await timelineController.sendReaction(emoji, to: itemId)
|
||||
case .displayEmojiPicker(let itemId):
|
||||
callback?(.displayEmojiPicker(itemId: itemId))
|
||||
case .cancelReply:
|
||||
state.composerMode = .default
|
||||
case .cancelEdit:
|
||||
state.composerMode = .default
|
||||
state.bindings.composerText = ""
|
||||
case .markRoomAsRead:
|
||||
await markRoomAsRead()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +137,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func markRoomAsRead() async {
|
||||
_ = await timelineController.markRoomAsRead()
|
||||
}
|
||||
|
||||
private func itemTapped(with itemId: String) async {
|
||||
state.showLoading = true
|
||||
@@ -171,9 +178,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
switch currentComposerState {
|
||||
case .reply(let itemId, _):
|
||||
await timelineController.sendReply(currentMessage, to: itemId)
|
||||
await timelineController.sendMessage(currentMessage, inReplyTo: itemId)
|
||||
case .edit(let originalItemId):
|
||||
await timelineController.editMessage(currentMessage, of: originalItemId)
|
||||
await timelineController.editMessage(currentMessage, original: originalItemId)
|
||||
default:
|
||||
await timelineController.sendMessage(currentMessage)
|
||||
}
|
||||
@@ -250,7 +257,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
state.bindings.composerText = "> \(item.text)"
|
||||
case .copyPermalink:
|
||||
do {
|
||||
let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: item.id, roomIdentifier: timelineController.roomId)
|
||||
let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: item.id, roomIdentifier: timelineController.roomID)
|
||||
UIPasteboard.general.url = permalink
|
||||
} catch {
|
||||
displayError(.alert(ElementL10n.roomTimelinePermalinkCreationFailure))
|
||||
@@ -263,12 +270,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
state.bindings.composerFocused = true
|
||||
state.composerMode = .reply(id: item.id, displayName: item.sender.displayName ?? item.sender.id)
|
||||
case .viewSource:
|
||||
let debugDescription = timelineController.debugDescriptionFor(item.id)
|
||||
let debugDescription = timelineController.debugDescription(for: item.id)
|
||||
MXLog.info(debugDescription)
|
||||
state.bindings.debugInfo = .init(title: "Timeline item", content: debugDescription)
|
||||
case .retryDecryption(let sessionId):
|
||||
Task {
|
||||
await timelineController.retryDecryption(forSessionId: sessionId)
|
||||
await timelineController.retryDecryption(for: sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ struct RoomScreen: View {
|
||||
.overlay { loadingIndicator }
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
.sheet(item: $context.debugInfo) { DebugScreen(info: $0) }
|
||||
.task {
|
||||
// Give a couple of seconds for items to load and to see them.
|
||||
try? await Task.sleep(for: .seconds(2))
|
||||
|
||||
guard !Task.isCancelled else { return }
|
||||
context.send(viewAction: .markRoomAsRead)
|
||||
}
|
||||
}
|
||||
|
||||
var timeline: some View {
|
||||
|
||||
@@ -29,6 +29,7 @@ struct MockRoomProxy: RoomProxyProtocol {
|
||||
var isPublic = Bool.random()
|
||||
var isEncrypted = Bool.random()
|
||||
var isTombstoned = Bool.random()
|
||||
var hasUnreadNotifications = Bool.random()
|
||||
var members: [RoomMemberProxy]?
|
||||
|
||||
let timelineProvider: RoomTimelineProviderProtocol = MockRoomTimelineProvider()
|
||||
@@ -58,16 +59,20 @@ struct MockRoomProxy: RoomProxyProtocol {
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedPaginatingBackwards)
|
||||
}
|
||||
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingReadReceipt)
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||
func sendMessage(_ message: String, inReplyTo eventID: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError> {
|
||||
func sendReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError> {
|
||||
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
@@ -82,5 +87,5 @@ struct MockRoomProxy: RoomProxyProtocol {
|
||||
return .failure(.failedRetrievingMembers)
|
||||
}
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async { }
|
||||
func retryDecryption(for sessionID: String) async { }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class RoomProxy: RoomProxyProtocol {
|
||||
private let slidingSyncRoom: SlidingSyncRoomProtocol
|
||||
private let room: RoomProtocol
|
||||
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
||||
private let backgroundTaskName = "SendRoomEvent"
|
||||
|
||||
private let serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.serial")
|
||||
|
||||
@@ -82,6 +83,10 @@ class RoomProxy: RoomProxyProtocol {
|
||||
room.isTombstoned()
|
||||
}
|
||||
|
||||
var hasUnreadNotifications: Bool {
|
||||
slidingSyncRoom.hasUnreadNotifications()
|
||||
}
|
||||
|
||||
var avatarURL: URL? {
|
||||
room.avatarUrl().flatMap(URL.init(string:))
|
||||
}
|
||||
@@ -156,18 +161,34 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
return await Task.dispatch(on: serialDispatchQueue) {
|
||||
do {
|
||||
try self.room.sendReadReceipt(eventId: eventID)
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedSendingReadReceipt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo eventID: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
let transactionId = genTransactionId()
|
||||
|
||||
return await Task.dispatch(on: .global()) {
|
||||
return await Task.dispatch(on: serialDispatchQueue) {
|
||||
do {
|
||||
if let inReplyToEventId {
|
||||
try self.room.sendReply(msg: message, inReplyToEventId: inReplyToEventId, txnId: transactionId)
|
||||
if let eventID {
|
||||
try self.room.sendReply(msg: message, inReplyToEventId: eventID, txnId: transactionId)
|
||||
} else {
|
||||
let messageContent = messageEventContentFromMarkdown(md: message)
|
||||
try self.room.send(msg: messageContent, txnId: transactionId)
|
||||
@@ -179,15 +200,15 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
||||
func sendReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
return await Task.dispatch(on: .global()) {
|
||||
return await Task.dispatch(on: serialDispatchQueue) {
|
||||
do {
|
||||
try self.room.sendReaction(eventId: eventId, key: reaction)
|
||||
try self.room.sendReaction(eventId: eventID, key: reaction)
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedSendingReaction)
|
||||
@@ -195,17 +216,17 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
||||
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
let transactionId = genTransactionId()
|
||||
|
||||
return await Task.dispatch(on: .global()) {
|
||||
return await Task.dispatch(on: serialDispatchQueue) {
|
||||
do {
|
||||
try self.room.edit(newMsg: newMessage, originalEventId: originalEventId, txnId: transactionId)
|
||||
try self.room.edit(newMsg: newMessage, originalEventId: eventID, txnId: transactionId)
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedEditingMessage)
|
||||
@@ -214,17 +235,21 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
let transactionID = genTransactionId()
|
||||
|
||||
return await Task {
|
||||
return await Task.dispatch(on: serialDispatchQueue) {
|
||||
do {
|
||||
try room.redact(eventId: eventID, reason: nil, txnId: transactionID)
|
||||
try self.room.redact(eventId: eventID, reason: nil, txnId: transactionID)
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedRedactingEvent)
|
||||
}
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
func members() async -> Result<[RoomMemberProxy], RoomProxyError> {
|
||||
@@ -238,9 +263,9 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async {
|
||||
func retryDecryption(for sessionID: String) async {
|
||||
await Task.dispatch(on: .global()) { [weak self] in
|
||||
self?.room.retryDecryption(sessionIds: [sessionId])
|
||||
self?.room.retryDecryption(sessionIds: [sessionID])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ enum RoomProxyError: Error {
|
||||
case failedPaginatingBackwards
|
||||
case failedRetrievingMemberAvatarURL
|
||||
case failedRetrievingMemberDisplayName
|
||||
case failedSendingReadReceipt
|
||||
case failedSendingMessage
|
||||
case failedSendingReaction
|
||||
case failedEditingMessage
|
||||
@@ -39,6 +40,7 @@ protocol RoomProxyProtocol {
|
||||
var isSpace: Bool { get }
|
||||
var isEncrypted: Bool { get }
|
||||
var isTombstoned: Bool { get }
|
||||
var hasUnreadNotifications: Bool { get }
|
||||
|
||||
var name: String? { get }
|
||||
var displayName: String? { get }
|
||||
@@ -59,21 +61,23 @@ protocol RoomProxyProtocol {
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func sendMessage(_ message: String, inReplyToEventId: String?) async -> Result<Void, RoomProxyError>
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func sendReaction(_ reaction: String, for eventId: String) 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>
|
||||
|
||||
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError>
|
||||
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func members() async -> Result<[RoomMemberProxy], RoomProxyError>
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async
|
||||
func retryDecryption(for sessionID: String) async
|
||||
}
|
||||
|
||||
extension RoomProxyProtocol {
|
||||
func sendMessage(_ message: String) async -> Result<Void, RoomProxyError> {
|
||||
await sendMessage(message, inReplyToEventId: nil)
|
||||
await sendMessage(message, inReplyTo: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,26 +20,4 @@ struct MockRoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
var itemsPublisher = CurrentValueSubject<[TimelineItemProxy], Never>([])
|
||||
|
||||
private var itemProxies = [TimelineItemProxy]()
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineProviderError> {
|
||||
.failure(.failedPaginatingBackwards)
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyToItemId: String?) async -> Result<Void, RoomTimelineProviderError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
.failure(.failedSendingReaction)
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, originalItemId: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
.failure(.failedRedactingItem)
|
||||
}
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async { }
|
||||
}
|
||||
|
||||
@@ -61,79 +61,6 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineProviderError> {
|
||||
MXLog.info("Started back pagination request")
|
||||
switch await roomProxy.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) {
|
||||
case .success:
|
||||
MXLog.info("Finished back pagination request")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed back pagination request with error: \(error)")
|
||||
|
||||
if error == .noMoreMessagesToBackPaginate {
|
||||
return .failure(.noMoreMessagesToBackPaginate)
|
||||
}
|
||||
|
||||
return .failure(.failedPaginatingBackwards)
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyToItemId: String?) async -> Result<Void, RoomTimelineProviderError> {
|
||||
if let inReplyToItemId {
|
||||
MXLog.info("Sending message in reply to: \(inReplyToItemId)")
|
||||
} else {
|
||||
MXLog.info("Sending message")
|
||||
}
|
||||
|
||||
switch await roomProxy.sendMessage(message, inReplyToEventId: inReplyToItemId) {
|
||||
case .success:
|
||||
MXLog.info("Finished sending message")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed sending message with error: \(error)")
|
||||
return .failure(.failedSendingMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
switch await roomProxy.sendReaction(reaction, for: itemId) {
|
||||
case .success:
|
||||
MXLog.info("Finished sending reaction")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed sending reaction with error: \(error)")
|
||||
return .failure(.failedSendingReaction)
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, originalItemId: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
MXLog.info("Editing message: \(originalItemId)")
|
||||
switch await roomProxy.editMessage(newMessage, originalEventId: originalItemId) {
|
||||
case .success:
|
||||
MXLog.info("Finished editing message")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed editing message with error: \(error)")
|
||||
return .failure(.failedSendingMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
MXLog.info("Redacting message: \(eventID)")
|
||||
switch await roomProxy.redact(eventID) {
|
||||
case .success:
|
||||
MXLog.info("Finished redacting message")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed redacting message with error: \(error)")
|
||||
return .failure(.failedRedactingItem)
|
||||
}
|
||||
}
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async {
|
||||
await roomProxy.retryDecryption(forSessionId: sessionId)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateItemsWithDiff(_ diff: TimelineDiff) {
|
||||
|
||||
@@ -17,33 +17,6 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
enum RoomTimelineProviderError: Error {
|
||||
case noMoreMessagesToBackPaginate
|
||||
case failedPaginatingBackwards
|
||||
case failedSendingMessage
|
||||
case failedSendingReaction
|
||||
case failedRedactingItem
|
||||
case generic
|
||||
}
|
||||
|
||||
protocol RoomTimelineProviderProtocol {
|
||||
var itemsPublisher: CurrentValueSubject<[TimelineItemProxy], Never> { get }
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
func sendMessage(_ message: String, inReplyToItemId: String?) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
func editMessage(_ newMessage: String, originalItemId: String) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async
|
||||
}
|
||||
|
||||
extension RoomTimelineProviderProtocol {
|
||||
func sendMessage(_ message: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
await sendMessage(message, inReplyToItemId: nil)
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -26,7 +26,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
/// An array of timeline items that will be appended in order when ``simulateIncomingItems()`` is called.
|
||||
var incomingItems: [RoomTimelineItemProtocol] = []
|
||||
|
||||
let roomId = "MockRoomIdentifier"
|
||||
let roomID = "MockRoomIdentifier"
|
||||
|
||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||
|
||||
@@ -46,27 +46,27 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func processItemAppearance(_ itemId: String) async { }
|
||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> { .success(()) }
|
||||
|
||||
func processItemDisappearance(_ itemId: String) async { }
|
||||
func processItemAppearance(_ itemID: String) async { }
|
||||
|
||||
func processItemDisappearance(_ itemID: String) async { }
|
||||
|
||||
func processItemTap(_ itemId: String) async -> RoomTimelineControllerAction { .none }
|
||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction { .none }
|
||||
|
||||
func sendMessage(_ message: String) async { }
|
||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async { }
|
||||
|
||||
func sendReply(_ message: String, to itemId: String) async { }
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async { }
|
||||
func sendReaction(_ reaction: String, to itemID: String) async { }
|
||||
|
||||
func editMessage(_ newMessage: String, of itemId: String) async { }
|
||||
func editMessage(_ newMessage: String, original itemID: String) async { }
|
||||
|
||||
func redact(_ eventID: String) async { }
|
||||
func redact(_ itemID: String) async { }
|
||||
|
||||
func debugDescriptionFor(_ itemId: String) -> String {
|
||||
func debugDescription(for itemID: String) -> String {
|
||||
"Mock debug description"
|
||||
}
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async { }
|
||||
func retryDecryption(for sessionID: String) async { }
|
||||
|
||||
// MARK: - UI Test signalling
|
||||
|
||||
|
||||
+67
-46
@@ -37,7 +37,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
||||
|
||||
var roomId: String {
|
||||
var roomID: String {
|
||||
roomProxy.id
|
||||
}
|
||||
|
||||
@@ -68,19 +68,35 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
switch await timelineProvider.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) {
|
||||
MXLog.info("Started back pagination request")
|
||||
switch await roomProxy.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) {
|
||||
case .success:
|
||||
MXLog.info("Finished back pagination request")
|
||||
return .success(())
|
||||
case .failure(.noMoreMessagesToBackPaginate):
|
||||
MXLog.warning("Back pagination requested when all messages have been loaded.")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
if error == .noMoreMessagesToBackPaginate {
|
||||
return .success(())
|
||||
}
|
||||
MXLog.error("Failed back pagination request with error: \(error)")
|
||||
return .failure(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
func processItemAppearance(_ itemId: String) async {
|
||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemId }) else {
|
||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> {
|
||||
guard roomProxy.hasUnreadNotifications,
|
||||
let eventID = timelineItems.last?.id
|
||||
else { return .success(()) }
|
||||
|
||||
switch await roomProxy.sendReadReceipt(for: eventID) {
|
||||
case .success:
|
||||
return .success(())
|
||||
case .failure:
|
||||
return .failure(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
func processItemAppearance(_ itemID: String) async {
|
||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -101,18 +117,18 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func processItemDisappearance(_ itemId: String) { }
|
||||
func processItemDisappearance(_ itemID: String) { }
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
func processItemTap(_ itemId: String) async -> RoomTimelineControllerAction {
|
||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemId }) else {
|
||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction {
|
||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
switch timelineItem {
|
||||
case let item as ImageRoomTimelineItem:
|
||||
await loadFileForImageTimelineItem(item)
|
||||
guard let index = timelineItems.firstIndex(where: { $0.id == itemId }),
|
||||
guard let index = timelineItems.firstIndex(where: { $0.id == itemID }),
|
||||
let item = timelineItems[index] as? ImageRoomTimelineItem else {
|
||||
return .none
|
||||
}
|
||||
@@ -122,7 +138,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return .none
|
||||
case let item as VideoRoomTimelineItem:
|
||||
await loadVideoForTimelineItem(item)
|
||||
guard let index = timelineItems.firstIndex(where: { $0.id == itemId }),
|
||||
guard let index = timelineItems.firstIndex(where: { $0.id == itemID }),
|
||||
let item = timelineItems[index] as? VideoRoomTimelineItem else {
|
||||
return .none
|
||||
}
|
||||
@@ -132,7 +148,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return .none
|
||||
case let item as FileRoomTimelineItem:
|
||||
await loadFileForTimelineItem(item)
|
||||
guard let index = timelineItems.firstIndex(where: { $0.id == itemId }),
|
||||
guard let index = timelineItems.firstIndex(where: { $0.id == itemID }),
|
||||
let item = timelineItems[index] as? FileRoomTimelineItem else {
|
||||
return .none
|
||||
}
|
||||
@@ -145,54 +161,59 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String) async {
|
||||
MXLog.info("Send message in \(roomId)")
|
||||
switch await timelineProvider.sendMessage(message) {
|
||||
default:
|
||||
break
|
||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async {
|
||||
if itemID == nil {
|
||||
MXLog.info("Send message in \(roomID)")
|
||||
} else {
|
||||
MXLog.info("Send reply in \(roomID)")
|
||||
}
|
||||
|
||||
switch await roomProxy.sendMessage(message, inReplyTo: itemID) {
|
||||
case .success:
|
||||
MXLog.info("Finished sending message")
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed sending message with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func sendReply(_ message: String, to itemId: String) async {
|
||||
MXLog.info("Send reply in \(roomId)")
|
||||
switch await timelineProvider.sendMessage(message, inReplyToItemId: itemId) {
|
||||
default:
|
||||
break
|
||||
func sendReaction(_ reaction: String, to itemID: String) async {
|
||||
MXLog.info("Send reaction in \(roomID)")
|
||||
switch await roomProxy.sendReaction(reaction, to: itemID) {
|
||||
case .success:
|
||||
MXLog.info("Finished sending reaction")
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed sending reaction with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async {
|
||||
MXLog.info("Send reaction in \(roomId)")
|
||||
switch await timelineProvider.sendReaction(reaction, for: itemId) {
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, of itemId: String) async {
|
||||
MXLog.info("Edit message in \(roomId)")
|
||||
switch await timelineProvider.editMessage(newMessage, originalItemId: itemId) {
|
||||
default:
|
||||
break
|
||||
func editMessage(_ newMessage: String, original itemID: String) async {
|
||||
MXLog.info("Edit message in \(roomID)")
|
||||
switch await roomProxy.editMessage(newMessage, original: itemID) {
|
||||
case .success:
|
||||
MXLog.info("Finished editing message")
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed editing message with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func redact(_ eventID: String) async {
|
||||
MXLog.info("Send redaction in \(roomId)")
|
||||
switch await timelineProvider.redact(eventID) {
|
||||
default:
|
||||
break
|
||||
func redact(_ itemID: String) async {
|
||||
MXLog.info("Send redaction in \(roomID)")
|
||||
switch await roomProxy.redact(itemID) {
|
||||
case .success:
|
||||
MXLog.info("Finished redacting message")
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed redacting message with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Handle this paralel to the timeline items so we're not forced
|
||||
// Handle this parallel to the timeline items so we're not forced
|
||||
// to bundle the Rust side objects within them
|
||||
func debugDescriptionFor(_ itemId: String) -> String {
|
||||
func debugDescription(for itemID: String) -> String {
|
||||
var description = "Unknown item"
|
||||
timelineProvider.itemsPublisher.value.forEach { timelineItemProxy in
|
||||
switch timelineItemProxy {
|
||||
case .event(let item):
|
||||
if item.id == itemId {
|
||||
if item.id == itemID {
|
||||
description = item.debugDescription
|
||||
return
|
||||
}
|
||||
@@ -204,8 +225,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return description
|
||||
}
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async {
|
||||
await timelineProvider.retryDecryption(forSessionId: sessionId)
|
||||
func retryDecryption(for sessionID: String) async {
|
||||
await roomProxy.retryDecryption(for: sessionID)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
+17
-11
@@ -37,30 +37,36 @@ enum RoomTimelineControllerError: Error {
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineControllerProtocol {
|
||||
var roomId: String { get }
|
||||
var roomID: String { get }
|
||||
|
||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||
|
||||
func processItemAppearance(_ itemId: String) async
|
||||
func processItemAppearance(_ itemID: String) async
|
||||
|
||||
func processItemDisappearance(_ itemId: String) async
|
||||
func processItemDisappearance(_ itemID: String) async
|
||||
|
||||
func processItemTap(_ itemId: String) async -> RoomTimelineControllerAction
|
||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func sendMessage(_ message: String) async
|
||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func sendReply(_ message: String, to itemId: String) async
|
||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async
|
||||
|
||||
func editMessage(_ newMessage: String, of itemId: String) async
|
||||
func editMessage(_ newMessage: String, original itemID: String) async
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async
|
||||
func sendReaction(_ reaction: String, to itemID: String) async
|
||||
|
||||
func redact(_ eventID: String) async
|
||||
func redact(_ itemID: String) async
|
||||
|
||||
func debugDescriptionFor(_ itemId: String) -> String
|
||||
func debugDescription(for itemID: String) -> String
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async
|
||||
func retryDecryption(for sessionID: String) async
|
||||
}
|
||||
|
||||
extension RoomTimelineControllerProtocol {
|
||||
func sendMessage(_ message: String) async {
|
||||
await sendMessage(message, inReplyTo: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Rooms: Mark rooms as read when opening/closing.
|
||||
Reference in New Issue
Block a user