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:
Doug
2023-01-18 15:14:03 +00:00
committed by GitHub
parent 6615116226
commit 5017ba7122
14 changed files with 183 additions and 226 deletions
@@ -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)
}
}
@@ -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
@@ -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
@@ -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)
}
}