diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index d4b5a30d3..173b8a47c 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -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) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index 3a9c16fa2..30b4f178b 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -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 { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index a209ef862..3f4ed4fc9 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -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) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 860cd137b..bee5b570a 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -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 { diff --git a/ElementX/Sources/Services/Room/MockRoomProxy.swift b/ElementX/Sources/Services/Room/MockRoomProxy.swift index 8209717e6..69bede4fd 100644 --- a/ElementX/Sources/Services/Room/MockRoomProxy.swift +++ b/ElementX/Sources/Services/Room/MockRoomProxy.swift @@ -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 { .failure(.failedPaginatingBackwards) } + + func sendReadReceipt(for eventID: String) async -> Result { + .failure(.failedSendingReadReceipt) + } - func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result { + func sendMessage(_ message: String, inReplyTo eventID: String? = nil) async -> Result { .failure(.failedSendingMessage) } - func sendReaction(_ reaction: String, for eventId: String) async -> Result { + func sendReaction(_ reaction: String, to eventID: String) async -> Result { .failure(.failedSendingMessage) } - func editMessage(_ newMessage: String, originalEventId: String) async -> Result { + func editMessage(_ newMessage: String, original eventID: String) async -> Result { .failure(.failedSendingMessage) } @@ -82,5 +87,5 @@ struct MockRoomProxy: RoomProxyProtocol { return .failure(.failedRetrievingMembers) } - func retryDecryption(forSessionId sessionId: String) async { } + func retryDecryption(for sessionID: String) async { } } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 827a5eab7..661a26628 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -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 { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true) + func sendReadReceipt(for eventID: String) async -> Result { + 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 { + 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 { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true) + func sendReaction(_ reaction: String, to eventID: String) async -> Result { + 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 { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true) + func editMessage(_ newMessage: String, original eventID: String) async -> Result { + 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 { + 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]) } } diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index b9308459f..69ecbcf9b 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -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 - func sendMessage(_ message: String, inReplyToEventId: String?) async -> Result + func sendReadReceipt(for eventID: String) async -> Result - func sendReaction(_ reaction: String, for eventId: String) async -> Result + func sendMessage(_ message: String, inReplyTo eventID: String?) async -> Result + + func sendReaction(_ reaction: String, to eventID: String) async -> Result - func editMessage(_ newMessage: String, originalEventId: String) async -> Result + func editMessage(_ newMessage: String, original eventID: String) async -> Result func redact(_ eventID: String) async -> Result 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 { - await sendMessage(message, inReplyToEventId: nil) + await sendMessage(message, inReplyTo: nil) } } diff --git a/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift index 33d587c0b..c746a89b2 100644 --- a/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift @@ -20,26 +20,4 @@ struct MockRoomTimelineProvider: RoomTimelineProviderProtocol { var itemsPublisher = CurrentValueSubject<[TimelineItemProxy], Never>([]) private var itemProxies = [TimelineItemProxy]() - - func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result { - .failure(.failedPaginatingBackwards) - } - - func sendMessage(_ message: String, inReplyToItemId: String?) async -> Result { - .failure(.failedSendingMessage) - } - - func sendReaction(_ reaction: String, for itemId: String) async -> Result { - .failure(.failedSendingReaction) - } - - func editMessage(_ newMessage: String, originalItemId: String) async -> Result { - .failure(.failedSendingMessage) - } - - func redact(_ eventID: String) async -> Result { - .failure(.failedRedactingItem) - } - - func retryDecryption(forSessionId sessionId: String) async { } } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift index 545ce2cd5..54184d9dc 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -61,79 +61,6 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { } } - func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result { - 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 { - 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 { - 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 { - 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 { - 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) { diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift index 948981c0d..3362c7358 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift @@ -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 - - func sendMessage(_ message: String, inReplyToItemId: String?) async -> Result - - func sendReaction(_ reaction: String, for itemId: String) async -> Result - - func editMessage(_ newMessage: String, originalItemId: String) async -> Result - - func redact(_ eventID: String) async -> Result - - func retryDecryption(forSessionId sessionId: String) async -} - -extension RoomTimelineProviderProtocol { - func sendMessage(_ message: String) async -> Result { - await sendMessage(message, inReplyToItemId: nil) - } } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift index 7c009ec76..98d9c9c20 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift @@ -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() @@ -46,27 +46,27 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { return .success(()) } - func processItemAppearance(_ itemId: String) async { } + func markRoomAsRead() async -> Result { .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 diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index 889e9cfb3..ec9fd750f 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -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 { - 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 { + 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 diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift index f265c7543..404becf36 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift @@ -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 { 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 - func sendMessage(_ message: String) async + func markRoomAsRead() async -> Result - 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) + } } diff --git a/changelog.d/414.change b/changelog.d/414.change new file mode 100644 index 000000000..b18fc3866 --- /dev/null +++ b/changelog.d/414.change @@ -0,0 +1 @@ +Rooms: Mark rooms as read when opening/closing. \ No newline at end of file