Bump the RustSDK, adopt new marking/flagging rooms as (un)read API and fix sending read receipts when entering rooms or making the app active again

This commit is contained in:
Stefan Ceriu
2024-02-14 11:23:57 +02:00
committed by Stefan Ceriu
parent e11450e61e
commit 5cb5862e5e
15 changed files with 126 additions and 88 deletions
+1 -1
View File
@@ -6779,7 +6779,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 1.1.38; version = 1.1.39;
}; };
}; };
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {
@@ -129,8 +129,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "691d8b0f0994d9669fadbd2452bef7270f3713ad", "revision" : "8a5813a3cdf541bee3ceb4776c362d1f6b767581",
"version" : "1.1.38" "version" : "1.1.39"
} }
}, },
{ {
@@ -262,7 +262,7 @@
{ {
"identity" : "swiftui-introspect", "identity" : "swiftui-introspect",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect", "location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"state" : { "state" : {
"revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290", "revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290",
"version" : "0.9.2" "version" : "0.9.2"
@@ -409,9 +409,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
self.roomProxy = roomProxy self.roomProxy = roomProxy
Task { Task {
// Mark the room as read on entering but don't send read receipts // Flag the room as read on entering, the timeline will take care of the read receipts
// as those will be handled by the timeline await roomProxy.flagAsRead()
await roomProxy.markAsRead(sendReadReceipts: false, receiptType: appSettings.sharePresence ? .read : .readPrivate)
} }
let userID = userSession.clientProxy.userID let userID = userSession.clientProxy.userID
@@ -2232,42 +2232,59 @@ class RoomProxyMock: RoomProxyProtocol {
return canUserTriggerRoomNotificationUserIDReturnValue return canUserTriggerRoomNotificationUserIDReturnValue
} }
} }
//MARK: - markAsUnread //MARK: - flagAsUnread
var markAsUnreadCallsCount = 0 var flagAsUnreadCallsCount = 0
var markAsUnreadCalled: Bool { var flagAsUnreadCalled: Bool {
return markAsUnreadCallsCount > 0 return flagAsUnreadCallsCount > 0
} }
var markAsUnreadReturnValue: Result<Void, RoomProxyError>! var flagAsUnreadReturnValue: Result<Void, RoomProxyError>!
var markAsUnreadClosure: (() async -> Result<Void, RoomProxyError>)? var flagAsUnreadClosure: (() async -> Result<Void, RoomProxyError>)?
func markAsUnread() async -> Result<Void, RoomProxyError> { func flagAsUnread() async -> Result<Void, RoomProxyError> {
markAsUnreadCallsCount += 1 flagAsUnreadCallsCount += 1
if let markAsUnreadClosure = markAsUnreadClosure { if let flagAsUnreadClosure = flagAsUnreadClosure {
return await markAsUnreadClosure() return await flagAsUnreadClosure()
} else { } else {
return markAsUnreadReturnValue return flagAsUnreadReturnValue
}
}
//MARK: - flagAsRead
var flagAsReadCallsCount = 0
var flagAsReadCalled: Bool {
return flagAsReadCallsCount > 0
}
var flagAsReadReturnValue: Result<Void, RoomProxyError>!
var flagAsReadClosure: (() async -> Result<Void, RoomProxyError>)?
func flagAsRead() async -> Result<Void, RoomProxyError> {
flagAsReadCallsCount += 1
if let flagAsReadClosure = flagAsReadClosure {
return await flagAsReadClosure()
} else {
return flagAsReadReturnValue
} }
} }
//MARK: - markAsRead //MARK: - markAsRead
var markAsReadSendReadReceiptsReceiptTypeCallsCount = 0 var markAsReadReceiptTypeCallsCount = 0
var markAsReadSendReadReceiptsReceiptTypeCalled: Bool { var markAsReadReceiptTypeCalled: Bool {
return markAsReadSendReadReceiptsReceiptTypeCallsCount > 0 return markAsReadReceiptTypeCallsCount > 0
} }
var markAsReadSendReadReceiptsReceiptTypeReceivedArguments: (sendReadReceipts: Bool, receiptType: ReceiptType)? var markAsReadReceiptTypeReceivedReceiptType: ReceiptType?
var markAsReadSendReadReceiptsReceiptTypeReceivedInvocations: [(sendReadReceipts: Bool, receiptType: ReceiptType)] = [] var markAsReadReceiptTypeReceivedInvocations: [ReceiptType] = []
var markAsReadSendReadReceiptsReceiptTypeReturnValue: Result<Void, RoomProxyError>! var markAsReadReceiptTypeReturnValue: Result<Void, RoomProxyError>!
var markAsReadSendReadReceiptsReceiptTypeClosure: ((Bool, ReceiptType) async -> Result<Void, RoomProxyError>)? var markAsReadReceiptTypeClosure: ((ReceiptType) async -> Result<Void, RoomProxyError>)?
func markAsRead(sendReadReceipts: Bool, receiptType: ReceiptType) async -> Result<Void, RoomProxyError> { func markAsRead(receiptType: ReceiptType) async -> Result<Void, RoomProxyError> {
markAsReadSendReadReceiptsReceiptTypeCallsCount += 1 markAsReadReceiptTypeCallsCount += 1
markAsReadSendReadReceiptsReceiptTypeReceivedArguments = (sendReadReceipts: sendReadReceipts, receiptType: receiptType) markAsReadReceiptTypeReceivedReceiptType = receiptType
markAsReadSendReadReceiptsReceiptTypeReceivedInvocations.append((sendReadReceipts: sendReadReceipts, receiptType: receiptType)) markAsReadReceiptTypeReceivedInvocations.append(receiptType)
if let markAsReadSendReadReceiptsReceiptTypeClosure = markAsReadSendReadReceiptsReceiptTypeClosure { if let markAsReadReceiptTypeClosure = markAsReadReceiptTypeClosure {
return await markAsReadSendReadReceiptsReceiptTypeClosure(sendReadReceipts, receiptType) return await markAsReadReceiptTypeClosure(receiptType)
} else { } else {
return markAsReadSendReadReceiptsReceiptTypeReturnValue return markAsReadReceiptTypeReturnValue
} }
} }
//MARK: - sendTypingNotification //MARK: - sendTypingNotification
+4 -1
View File
@@ -80,7 +80,10 @@ extension RoomProxyMock {
canUserRedactOtherUserIDReturnValue = .success(false) canUserRedactOtherUserIDReturnValue = .success(false)
canUserTriggerRoomNotificationUserIDReturnValue = .success(configuration.canUserTriggerRoomNotification) canUserTriggerRoomNotificationUserIDReturnValue = .success(configuration.canUserTriggerRoomNotification)
canUserJoinCallUserIDReturnValue = .success(configuration.canUserJoinCall) canUserJoinCallUserIDReturnValue = .success(configuration.canUserJoinCall)
markAsReadSendReadReceiptsReceiptTypeReturnValue = .success(())
flagAsReadReturnValue = .success(())
flagAsUnreadReturnValue = .success(())
markAsReadReceiptTypeReturnValue = .success(())
let widgetDriver = ElementCallWidgetDriverMock() let widgetDriver = ElementCallWidgetDriverMock()
widgetDriver.underlyingMessagePublisher = .init() widgetDriver.underlyingMessagePublisher = .init()
@@ -167,7 +167,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return return
} }
switch await roomProxy.markAsUnread() { switch await roomProxy.flagAsUnread() {
case .success: case .success:
ServiceLocator.shared.analytics.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle) ServiceLocator.shared.analytics.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle)
case .failure(let error): case .failure(let error):
@@ -181,12 +181,16 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return return
} }
switch await roomProxy.markAsRead(sendReadReceipts: true, receiptType: appSettings.sharePresence ? .read : .readPrivate) { switch await roomProxy.flagAsRead() {
case .success: case .success:
ServiceLocator.shared.analytics.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle) ServiceLocator.shared.analytics.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle)
case .failure(let error):
if case .failure(let error) = await roomProxy.markAsRead(receiptType: appSettings.sharePresence ? .read : .readPrivate) {
MXLog.error("Failed marking room \(roomIdentifier) as read with error: \(error)") MXLog.error("Failed marking room \(roomIdentifier) as read with error: \(error)")
} }
case .failure(let error):
MXLog.error("Failed flagging room \(roomIdentifier) as read with error: \(error)")
}
} }
} }
} }
@@ -418,12 +418,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
notificationCenter.post(name: .roomMarkedAsRead, object: roomProxy.id) notificationCenter.post(name: .roomMarkedAsRead, object: roomProxy.id)
} }
switch await timelineController.sendReadReceipt(for: lastVisibleItemID) { await timelineController.sendReadReceipt(for: lastVisibleItemID)
case .success:
break
case let .failure(error):
MXLog.error("[TimelineViewController] Failed to send read receipt: \(error)")
}
} }
private func handleItemTapped(with itemID: TimelineItemIdentifier) async { private func handleItemTapped(with itemID: TimelineItemIdentifier) async {
@@ -64,7 +64,7 @@ class TimelineTableViewController: UIViewController {
paginateBackwardsPublisher.send() paginateBackwardsPublisher.send()
} }
sendReadReceiptIfNeeded() sendLastVisibleItemReadReceipt()
} }
} }
@@ -144,6 +144,12 @@ class TimelineTableViewController: UIViewController {
} }
.store(in: &cancellables) .store(in: &cancellables)
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { [weak self] _ in
self?.sendLastVisibleItemReadReceipt()
}
.store(in: &cancellables)
configureDataSource() configureDataSource()
} }
@@ -153,6 +159,8 @@ class TimelineTableViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
sendLastVisibleItemReadReceipt()
guard !hasAppearedOnce else { return } guard !hasAppearedOnce else { return }
tableView.contentOffset.y = -1 tableView.contentOffset.y = -1
hasAppearedOnce = true hasAppearedOnce = true
@@ -287,13 +295,20 @@ class TimelineTableViewController: UIViewController {
coordinator.send(viewAction: .paginateBackwards) coordinator.send(viewAction: .paginateBackwards)
} }
private func sendReadReceiptIfNeeded() { private func sendLastVisibleItemReadReceipt() {
guard let lastVisibleItemIndexPath = tableView.indexPathsForVisibleRows?.first, // Find the last visible timeline item and send a read receipt for it
let lastVisibleItemTimelineID = dataSource?.itemIdentifier(for: lastVisibleItemIndexPath), guard let visibleIndexPaths = tableView.indexPathsForVisibleRows else {
let lastVisibleItemID = timelineItemsDictionary[lastVisibleItemTimelineID]?.identifier return
else { return } }
coordinator.send(viewAction: .sendReadReceiptIfNeeded(lastVisibleItemID)) // These are already in reverse order because the table view is flipped
for indexPath in visibleIndexPaths {
if let visibleItemTimelineID = dataSource?.itemIdentifier(for: indexPath),
let visibleItemID = timelineItemsDictionary[visibleItemTimelineID]?.identifier {
coordinator.send(viewAction: .sendReadReceiptIfNeeded(visibleItemID))
return
}
}
} }
} }
@@ -327,15 +342,15 @@ extension TimelineTableViewController: UITableViewDelegate {
} }
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
sendReadReceiptIfNeeded() sendLastVisibleItemReadReceipt()
} }
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
sendReadReceiptIfNeeded() sendLastVisibleItemReadReceipt()
} }
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
sendReadReceiptIfNeeded() sendLastVisibleItemReadReceipt()
} }
} }
+15 -10
View File
@@ -368,28 +368,33 @@ class RoomProxy: RoomProxyProtocol {
} }
} }
func markAsUnread() async -> Result<Void, RoomProxyError> { func flagAsUnread() async -> Result<Void, RoomProxyError> {
MXLog.info("Marking room \(id) as unread") MXLog.info("Flagging room \(id) as unread")
do { do {
try await room.markAsUnread() try await room.setUnreadFlag(newValue: true)
return .success(()) return .success(())
} catch { } catch {
MXLog.error("Failed marking room \(id) as unread with error: \(error)") MXLog.error("Failed marking room \(id) as unread with error: \(error)")
return .failure(.failedMarkingAsUnread) return .failure(.failedFlaggingAsUnread)
} }
} }
func markAsRead(sendReadReceipts: Bool, receiptType: ReceiptType) async -> Result<Void, RoomProxyError> { func flagAsRead() async -> Result<Void, RoomProxyError> {
MXLog.info("Marking room \(id) as read, sending read receipts: \(sendReadReceipts)") MXLog.info("Flagging room \(id) as read")
do { do {
if sendReadReceipts { try await room.setUnreadFlag(newValue: false)
try await room.markAsReadAndSendReadReceipt(receiptType: receiptType) return .success(())
} else { } catch {
try await room.markAsRead() MXLog.error("Failed marking room \(id) as read with error: \(error)")
return .failure(.failedFlaggingAsRead)
}
} }
func markAsRead(receiptType: ReceiptType) async -> Result<Void, RoomProxyError> {
do {
try await room.markAsRead(receiptType: receiptType)
return .success(()) return .success(())
} catch { } catch {
MXLog.error("Failed marking room \(id) as read with error: \(error)") MXLog.error("Failed marking room \(id) as read with error: \(error)")
@@ -31,8 +31,9 @@ enum RoomProxyError: Error, Equatable {
case failedRemovingAvatar case failedRemovingAvatar
case failedUploadingAvatar case failedUploadingAvatar
case failedCheckingPermission case failedCheckingPermission
case failedFlaggingAsUnread
case failedFlaggingAsRead
case failedMarkingAsRead case failedMarkingAsRead
case failedMarkingAsUnread
case failedSendingTypingNotice case failedSendingTypingNotice
} }
@@ -108,9 +109,11 @@ protocol RoomProxyProtocol {
func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError> func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError>
func markAsUnread() async -> Result<Void, RoomProxyError> func flagAsUnread() async -> Result<Void, RoomProxyError>
func markAsRead(sendReadReceipts: Bool, receiptType: ReceiptType) async -> Result<Void, RoomProxyError> func flagAsRead() async -> Result<Void, RoomProxyError>
func markAsRead(receiptType: ReceiptType) async -> Result<Void, RoomProxyError>
/// https://spec.matrix.org/v1.9/client-server-api/#typing-notifications /// https://spec.matrix.org/v1.9/client-server-api/#typing-notifications
@discardableResult func sendTypingNotification(isTyping: Bool) async -> Result<Void, RoomProxyError> @discardableResult func sendTypingNotification(isTyping: Bool) async -> Result<Void, RoomProxyError>
@@ -55,14 +55,9 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
return .success(()) return .success(())
} }
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> { func sendReadReceipt(for itemID: TimelineItemIdentifier) async {
guard let roomProxy, let eventID = itemID.eventID else { return .failure(.generic) } guard let roomProxy, let eventID = itemID.eventID else { return }
switch await roomProxy.timeline.sendReadReceipt(for: eventID, type: .read) { _ = await roomProxy.timeline.sendReadReceipt(for: eventID, type: .read)
case .success:
return .success(())
case .failure:
return .failure(.generic)
}
} }
func processItemAppearance(_ itemID: TimelineItemIdentifier) async { } func processItemAppearance(_ itemID: TimelineItemIdentifier) async { }
@@ -16,6 +16,7 @@
import Combine import Combine
import Foundation import Foundation
import MatrixRustSDK
import UIKit import UIKit
class RoomTimelineController: RoomTimelineControllerProtocol { class RoomTimelineController: RoomTimelineControllerProtocol {
@@ -91,17 +92,18 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
} }
} }
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> { func sendReadReceipt(for itemID: TimelineItemIdentifier) async {
let receiptType: MatrixRustSDK.ReceiptType = appSettings.sharePresence ? .read : .readPrivate
// Mark the whole room as read if it's the last timeline item
if timelineItems.last?.id == itemID {
_ = await roomProxy.markAsRead(receiptType: receiptType)
} else {
guard let eventID = itemID.eventID else { guard let eventID = itemID.eventID else {
return .failure(.generic) return
} }
switch await roomProxy.timeline.sendReadReceipt(for: eventID, _ = await roomProxy.timeline.sendReadReceipt(for: eventID, type: receiptType)
type: appSettings.sharePresence ? .read : .readPrivate) {
case .success:
return .success(())
case .failure:
return .failure(.generic)
} }
} }
@@ -49,7 +49,7 @@ protocol RoomTimelineControllerProtocol {
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError>
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> func sendReadReceipt(for itemID: TimelineItemIdentifier) async
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String?,
@@ -454,7 +454,7 @@ final class TimelineProxy: TimelineProxyProtocol {
} }
func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError> { func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError> {
MXLog.info("Sending read receipt for eventID: \(eventID)") MXLog.verbose("Sending read receipt for eventID: \(eventID)")
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer { defer {
+1 -1
View File
@@ -47,7 +47,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.1.38 exactVersion: 1.1.39
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
Compound: Compound:
url: https://github.com/element-hq/compound-ios url: https://github.com/element-hq/compound-ios