mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
Adopt the new send queue interfaces
This commit is contained in:
@@ -12506,6 +12506,110 @@ nonisolated class TimelineControllerMock: TimelineControllerProtocol, @unchecked
|
|||||||
return sendHandleForReturnValue
|
return sendHandleForReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - pendingSendTarget
|
||||||
|
|
||||||
|
private let pendingSendTargetForCallsCountLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var pendingSendTargetForUnderlyingCallsCount = 0
|
||||||
|
var pendingSendTargetForCallsCount: Int {
|
||||||
|
get { pendingSendTargetForCallsCountLock.withLock { pendingSendTargetForUnderlyingCallsCount } }
|
||||||
|
set { pendingSendTargetForCallsCountLock.withLock { pendingSendTargetForUnderlyingCallsCount = newValue } }
|
||||||
|
}
|
||||||
|
var pendingSendTargetForCalled: Bool {
|
||||||
|
return pendingSendTargetForCallsCount > 0
|
||||||
|
}
|
||||||
|
private let pendingSendTargetForReceivedItemIDLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var pendingSendTargetForUnderlyingReceivedItemID: TimelineItemIdentifier?
|
||||||
|
var pendingSendTargetForReceivedItemID: TimelineItemIdentifier? {
|
||||||
|
get { pendingSendTargetForReceivedItemIDLock.withLock { pendingSendTargetForUnderlyingReceivedItemID } }
|
||||||
|
set { pendingSendTargetForReceivedItemIDLock.withLock { pendingSendTargetForUnderlyingReceivedItemID = newValue } }
|
||||||
|
}
|
||||||
|
private let pendingSendTargetForReceivedInvocationsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var pendingSendTargetForUnderlyingReceivedInvocations: [TimelineItemIdentifier] = []
|
||||||
|
var pendingSendTargetForReceivedInvocations: [TimelineItemIdentifier] {
|
||||||
|
get { pendingSendTargetForReceivedInvocationsLock.withLock { pendingSendTargetForUnderlyingReceivedInvocations } }
|
||||||
|
set { pendingSendTargetForReceivedInvocationsLock.withLock { pendingSendTargetForUnderlyingReceivedInvocations = newValue } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private let pendingSendTargetForReturnValueLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var pendingSendTargetForUnderlyingReturnValue: SendTarget?
|
||||||
|
var pendingSendTargetForReturnValue: SendTarget? {
|
||||||
|
get { pendingSendTargetForReturnValueLock.withLock { pendingSendTargetForUnderlyingReturnValue } }
|
||||||
|
set { pendingSendTargetForReturnValueLock.withLock { pendingSendTargetForUnderlyingReturnValue = newValue } }
|
||||||
|
}
|
||||||
|
nonisolated(unsafe) var pendingSendTargetForClosure: ((TimelineItemIdentifier) -> SendTarget?)?
|
||||||
|
|
||||||
|
func pendingSendTarget(for itemID: TimelineItemIdentifier) -> SendTarget? {
|
||||||
|
pendingSendTargetForCallsCountLock.withLock { pendingSendTargetForUnderlyingCallsCount += 1 }
|
||||||
|
pendingSendTargetForReceivedItemID = itemID
|
||||||
|
pendingSendTargetForReceivedInvocationsLock.withLock { pendingSendTargetForUnderlyingReceivedInvocations.append(itemID) }
|
||||||
|
if let pendingSendTargetForClosure = pendingSendTargetForClosure {
|
||||||
|
return pendingSendTargetForClosure(itemID)
|
||||||
|
} else {
|
||||||
|
return pendingSendTargetForReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//MARK: - retrySend
|
||||||
|
|
||||||
|
private let retrySendTargetCallsCountLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var retrySendTargetUnderlyingCallsCount = 0
|
||||||
|
var retrySendTargetCallsCount: Int {
|
||||||
|
get { retrySendTargetCallsCountLock.withLock { retrySendTargetUnderlyingCallsCount } }
|
||||||
|
set { retrySendTargetCallsCountLock.withLock { retrySendTargetUnderlyingCallsCount = newValue } }
|
||||||
|
}
|
||||||
|
var retrySendTargetCalled: Bool {
|
||||||
|
return retrySendTargetCallsCount > 0
|
||||||
|
}
|
||||||
|
private let retrySendTargetReceivedArgumentsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var retrySendTargetUnderlyingReceivedArguments: (itemID: TimelineItemIdentifier, target: SendTarget)?
|
||||||
|
var retrySendTargetReceivedArguments: (itemID: TimelineItemIdentifier, target: SendTarget)? {
|
||||||
|
get { retrySendTargetReceivedArgumentsLock.withLock { retrySendTargetUnderlyingReceivedArguments } }
|
||||||
|
set { retrySendTargetReceivedArgumentsLock.withLock { retrySendTargetUnderlyingReceivedArguments = newValue } }
|
||||||
|
}
|
||||||
|
private let retrySendTargetReceivedInvocationsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var retrySendTargetUnderlyingReceivedInvocations: [(itemID: TimelineItemIdentifier, target: SendTarget)] = []
|
||||||
|
var retrySendTargetReceivedInvocations: [(itemID: TimelineItemIdentifier, target: SendTarget)] {
|
||||||
|
get { retrySendTargetReceivedInvocationsLock.withLock { retrySendTargetUnderlyingReceivedInvocations } }
|
||||||
|
set { retrySendTargetReceivedInvocationsLock.withLock { retrySendTargetUnderlyingReceivedInvocations = newValue } }
|
||||||
|
}
|
||||||
|
nonisolated(unsafe) var retrySendTargetClosure: ((TimelineItemIdentifier, SendTarget) async -> Void)?
|
||||||
|
|
||||||
|
@concurrent func retrySend(_ itemID: TimelineItemIdentifier, target: SendTarget) async {
|
||||||
|
retrySendTargetCallsCountLock.withLock { retrySendTargetUnderlyingCallsCount += 1 }
|
||||||
|
retrySendTargetReceivedArguments = (itemID: itemID, target: target)
|
||||||
|
retrySendTargetReceivedInvocationsLock.withLock { retrySendTargetUnderlyingReceivedInvocations.append((itemID: itemID, target: target)) }
|
||||||
|
await retrySendTargetClosure?(itemID, target)
|
||||||
|
}
|
||||||
|
//MARK: - abortSend
|
||||||
|
|
||||||
|
private let abortSendTargetCallsCountLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var abortSendTargetUnderlyingCallsCount = 0
|
||||||
|
var abortSendTargetCallsCount: Int {
|
||||||
|
get { abortSendTargetCallsCountLock.withLock { abortSendTargetUnderlyingCallsCount } }
|
||||||
|
set { abortSendTargetCallsCountLock.withLock { abortSendTargetUnderlyingCallsCount = newValue } }
|
||||||
|
}
|
||||||
|
var abortSendTargetCalled: Bool {
|
||||||
|
return abortSendTargetCallsCount > 0
|
||||||
|
}
|
||||||
|
private let abortSendTargetReceivedArgumentsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var abortSendTargetUnderlyingReceivedArguments: (itemID: TimelineItemIdentifier, target: SendTarget)?
|
||||||
|
var abortSendTargetReceivedArguments: (itemID: TimelineItemIdentifier, target: SendTarget)? {
|
||||||
|
get { abortSendTargetReceivedArgumentsLock.withLock { abortSendTargetUnderlyingReceivedArguments } }
|
||||||
|
set { abortSendTargetReceivedArgumentsLock.withLock { abortSendTargetUnderlyingReceivedArguments = newValue } }
|
||||||
|
}
|
||||||
|
private let abortSendTargetReceivedInvocationsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var abortSendTargetUnderlyingReceivedInvocations: [(itemID: TimelineItemIdentifier, target: SendTarget)] = []
|
||||||
|
var abortSendTargetReceivedInvocations: [(itemID: TimelineItemIdentifier, target: SendTarget)] {
|
||||||
|
get { abortSendTargetReceivedInvocationsLock.withLock { abortSendTargetUnderlyingReceivedInvocations } }
|
||||||
|
set { abortSendTargetReceivedInvocationsLock.withLock { abortSendTargetUnderlyingReceivedInvocations = newValue } }
|
||||||
|
}
|
||||||
|
nonisolated(unsafe) var abortSendTargetClosure: ((TimelineItemIdentifier, SendTarget) async -> Void)?
|
||||||
|
|
||||||
|
@concurrent func abortSend(_ itemID: TimelineItemIdentifier, target: SendTarget) async {
|
||||||
|
abortSendTargetCallsCountLock.withLock { abortSendTargetUnderlyingCallsCount += 1 }
|
||||||
|
abortSendTargetReceivedArguments = (itemID: itemID, target: target)
|
||||||
|
abortSendTargetReceivedInvocationsLock.withLock { abortSendTargetUnderlyingReceivedInvocations.append((itemID: itemID, target: target)) }
|
||||||
|
await abortSendTargetClosure?(itemID, target)
|
||||||
|
}
|
||||||
//MARK: - eventTimestamp
|
//MARK: - eventTimestamp
|
||||||
|
|
||||||
private let eventTimestampForCallsCountLock = NSLock()
|
private let eventTimestampForCallsCountLock = NSLock()
|
||||||
@@ -13292,6 +13396,90 @@ nonisolated class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable
|
|||||||
return redactReasonReturnValue
|
return redactReasonReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - retrySend
|
||||||
|
|
||||||
|
private let retrySendTargetCallsCountLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var retrySendTargetUnderlyingCallsCount = 0
|
||||||
|
var retrySendTargetCallsCount: Int {
|
||||||
|
get { retrySendTargetCallsCountLock.withLock { retrySendTargetUnderlyingCallsCount } }
|
||||||
|
set { retrySendTargetCallsCountLock.withLock { retrySendTargetUnderlyingCallsCount = newValue } }
|
||||||
|
}
|
||||||
|
var retrySendTargetCalled: Bool {
|
||||||
|
return retrySendTargetCallsCount > 0
|
||||||
|
}
|
||||||
|
private let retrySendTargetReceivedArgumentsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var retrySendTargetUnderlyingReceivedArguments: (eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)?
|
||||||
|
var retrySendTargetReceivedArguments: (eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)? {
|
||||||
|
get { retrySendTargetReceivedArgumentsLock.withLock { retrySendTargetUnderlyingReceivedArguments } }
|
||||||
|
set { retrySendTargetReceivedArgumentsLock.withLock { retrySendTargetUnderlyingReceivedArguments = newValue } }
|
||||||
|
}
|
||||||
|
private let retrySendTargetReceivedInvocationsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var retrySendTargetUnderlyingReceivedInvocations: [(eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)] = []
|
||||||
|
var retrySendTargetReceivedInvocations: [(eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)] {
|
||||||
|
get { retrySendTargetReceivedInvocationsLock.withLock { retrySendTargetUnderlyingReceivedInvocations } }
|
||||||
|
set { retrySendTargetReceivedInvocationsLock.withLock { retrySendTargetUnderlyingReceivedInvocations = newValue } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private let retrySendTargetReturnValueLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var retrySendTargetUnderlyingReturnValue: Result<Bool, TimelineProxyError>!
|
||||||
|
var retrySendTargetReturnValue: Result<Bool, TimelineProxyError>! {
|
||||||
|
get { retrySendTargetReturnValueLock.withLock { retrySendTargetUnderlyingReturnValue } }
|
||||||
|
set { retrySendTargetReturnValueLock.withLock { retrySendTargetUnderlyingReturnValue = newValue } }
|
||||||
|
}
|
||||||
|
nonisolated(unsafe) var retrySendTargetClosure: ((TimelineItemIdentifier.EventOrTransactionID, SendTarget) async -> Result<Bool, TimelineProxyError>)?
|
||||||
|
|
||||||
|
@concurrent func retrySend(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
retrySendTargetCallsCountLock.withLock { retrySendTargetUnderlyingCallsCount += 1 }
|
||||||
|
retrySendTargetReceivedArguments = (eventOrTransactionID: eventOrTransactionID, target: target)
|
||||||
|
retrySendTargetReceivedInvocationsLock.withLock { retrySendTargetUnderlyingReceivedInvocations.append((eventOrTransactionID: eventOrTransactionID, target: target)) }
|
||||||
|
if let retrySendTargetClosure = retrySendTargetClosure {
|
||||||
|
return await retrySendTargetClosure(eventOrTransactionID, target)
|
||||||
|
} else {
|
||||||
|
return retrySendTargetReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//MARK: - abortSend
|
||||||
|
|
||||||
|
private let abortSendTargetCallsCountLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var abortSendTargetUnderlyingCallsCount = 0
|
||||||
|
var abortSendTargetCallsCount: Int {
|
||||||
|
get { abortSendTargetCallsCountLock.withLock { abortSendTargetUnderlyingCallsCount } }
|
||||||
|
set { abortSendTargetCallsCountLock.withLock { abortSendTargetUnderlyingCallsCount = newValue } }
|
||||||
|
}
|
||||||
|
var abortSendTargetCalled: Bool {
|
||||||
|
return abortSendTargetCallsCount > 0
|
||||||
|
}
|
||||||
|
private let abortSendTargetReceivedArgumentsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var abortSendTargetUnderlyingReceivedArguments: (eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)?
|
||||||
|
var abortSendTargetReceivedArguments: (eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)? {
|
||||||
|
get { abortSendTargetReceivedArgumentsLock.withLock { abortSendTargetUnderlyingReceivedArguments } }
|
||||||
|
set { abortSendTargetReceivedArgumentsLock.withLock { abortSendTargetUnderlyingReceivedArguments = newValue } }
|
||||||
|
}
|
||||||
|
private let abortSendTargetReceivedInvocationsLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var abortSendTargetUnderlyingReceivedInvocations: [(eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)] = []
|
||||||
|
var abortSendTargetReceivedInvocations: [(eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget)] {
|
||||||
|
get { abortSendTargetReceivedInvocationsLock.withLock { abortSendTargetUnderlyingReceivedInvocations } }
|
||||||
|
set { abortSendTargetReceivedInvocationsLock.withLock { abortSendTargetUnderlyingReceivedInvocations = newValue } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private let abortSendTargetReturnValueLock = NSLock()
|
||||||
|
private nonisolated(unsafe) var abortSendTargetUnderlyingReturnValue: Result<Bool, TimelineProxyError>!
|
||||||
|
var abortSendTargetReturnValue: Result<Bool, TimelineProxyError>! {
|
||||||
|
get { abortSendTargetReturnValueLock.withLock { abortSendTargetUnderlyingReturnValue } }
|
||||||
|
set { abortSendTargetReturnValueLock.withLock { abortSendTargetUnderlyingReturnValue = newValue } }
|
||||||
|
}
|
||||||
|
nonisolated(unsafe) var abortSendTargetClosure: ((TimelineItemIdentifier.EventOrTransactionID, SendTarget) async -> Result<Bool, TimelineProxyError>)?
|
||||||
|
|
||||||
|
@concurrent func abortSend(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
abortSendTargetCallsCountLock.withLock { abortSendTargetUnderlyingCallsCount += 1 }
|
||||||
|
abortSendTargetReceivedArguments = (eventOrTransactionID: eventOrTransactionID, target: target)
|
||||||
|
abortSendTargetReceivedInvocationsLock.withLock { abortSendTargetUnderlyingReceivedInvocations.append((eventOrTransactionID: eventOrTransactionID, target: target)) }
|
||||||
|
if let abortSendTargetClosure = abortSendTargetClosure {
|
||||||
|
return await abortSendTargetClosure(eventOrTransactionID, target)
|
||||||
|
} else {
|
||||||
|
return abortSendTargetReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - pin
|
//MARK: - pin
|
||||||
|
|
||||||
private let pinEventIDCallsCountLock = NSLock()
|
private let pinEventIDCallsCountLock = NSLock()
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ struct TimelineRedactConfirmationInfo: Identifiable {
|
|||||||
enum TimelineAlertInfoType: Hashable {
|
enum TimelineAlertInfoType: Hashable {
|
||||||
case audioRecodingPermissionError
|
case audioRecodingPermissionError
|
||||||
case pollEndConfirmation(String)
|
case pollEndConfirmation(String)
|
||||||
case sendingFailed(reason: String?, sendHandle: SendHandleProxy?)
|
case sendingFailed(reason: String?, itemID: TimelineItemIdentifier, target: SendTarget?)
|
||||||
case encryptionAuthenticity(String)
|
case encryptionAuthenticity(String)
|
||||||
case encryptionForwarder(String)
|
case encryptionForwarder(String)
|
||||||
case inviteAgain
|
case inviteAgain
|
||||||
|
|||||||
@@ -758,8 +758,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if case let .sendingFailed(.unknown(reason)) = eventTimelineItem.properties.deliveryStatus {
|
if case let .sendingFailed(.unknown(reason)) = eventTimelineItem.properties.deliveryStatus {
|
||||||
// A missing send handle only costs the retry/remove actions, the reason is still worth showing.
|
// A missing target only costs the retry/remove actions, the reason is still worth showing.
|
||||||
displayAlert(.sendingFailed(reason: reason, sendHandle: timelineController.sendHandle(for: itemID)))
|
displayAlert(.sendingFailed(reason: reason, itemID: itemID, target: timelineController.pendingSendTarget(for: itemID)))
|
||||||
} else if case let .sendingFailed(.verifiedUser(failure)) = eventTimelineItem.properties.deliveryStatus {
|
} else if case let .sendingFailed(.verifiedUser(failure)) = eventTimelineItem.properties.deliveryStatus {
|
||||||
guard let sendHandle = timelineController.sendHandle(for: itemID) else {
|
guard let sendHandle = timelineController.sendHandle(for: itemID) else {
|
||||||
MXLog.error("Cannot find send handle for \(itemID).")
|
MXLog.error("Cannot find send handle for \(itemID).")
|
||||||
@@ -776,13 +776,12 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func retrySending(_ sendHandle: SendHandleProxy) {
|
private func retrySending(_ itemID: TimelineItemIdentifier, target: SendTarget) {
|
||||||
Task {
|
Task { await timelineController.retrySend(itemID, target: target) }
|
||||||
if case .failure(let error) = await sendHandle.resend() {
|
|
||||||
MXLog.error("Failed retrying to send \(sendHandle.itemID): \(error)")
|
|
||||||
displayErrorToast(L10n.errorUnknown)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func abortSending(_ itemID: TimelineItemIdentifier, target: SendTarget) {
|
||||||
|
Task { await timelineController.abortSend(itemID, target: target) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func slashCommand(message: String) -> SlashCommand? {
|
private func slashCommand(message: String) -> SlashCommand? {
|
||||||
@@ -1125,15 +1124,15 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
message: L10n.commonPollEndConfirmation,
|
message: L10n.commonPollEndConfirmation,
|
||||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionOk) { self.timelineInteractionHandler.endPoll(pollStartID: pollStartID) })
|
secondaryButton: .init(title: L10n.actionOk) { self.timelineInteractionHandler.endPoll(pollStartID: pollStartID) })
|
||||||
case .sendingFailed(let reason, let sendHandle):
|
case .sendingFailed(let reason, let itemID, let target):
|
||||||
state.bindings.alertInfo = .init(id: type,
|
state.bindings.alertInfo = .init(id: type,
|
||||||
title: L10n.commonSendingFailed,
|
title: L10n.commonSendingFailed,
|
||||||
message: reason,
|
message: reason,
|
||||||
primaryButton: .init(title: sendHandle == nil ? L10n.actionOk : L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: target == nil ? L10n.actionOk : L10n.actionCancel, role: .cancel, action: nil),
|
||||||
verticalButtons: sendHandle.map { sendHandle in
|
verticalButtons: target.map { target in
|
||||||
[.init(title: L10n.actionRetry) { [weak self] in self?.retrySending(sendHandle) },
|
[.init(title: L10n.actionRetry) { [weak self] in self?.retrySending(itemID, target: target) },
|
||||||
.init(title: L10n.actionRemoveMessage, role: .destructive) { [weak self] in
|
.init(title: target == .event ? L10n.actionRemoveMessage : L10n.actionDiscard, role: .destructive) { [weak self] in
|
||||||
self?.timelineInteractionHandler.redact(sendHandle.itemID, reason: nil)
|
self?.abortSending(itemID, target: target)
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
case .encryptionAuthenticity(let message):
|
case .encryptionAuthenticity(let message):
|
||||||
|
|||||||
@@ -288,6 +288,53 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pendingSendTarget(for itemID: TimelineItemIdentifier) -> SendTarget? {
|
||||||
|
for timelineItemProxy in activeTimelineItemProvider.itemProxies {
|
||||||
|
switch timelineItemProxy {
|
||||||
|
case .event(let item):
|
||||||
|
if item.id == itemID {
|
||||||
|
return item.pendingSend?.target
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrySend(_ itemID: TimelineItemIdentifier, target: SendTarget) async {
|
||||||
|
guard let eventOrTransactionID = itemID.eventOrTransactionID else {
|
||||||
|
MXLog.error("Cannot retry sending on \(itemID).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MXLog.info("Retry \(target) send in \(roomID)")
|
||||||
|
|
||||||
|
switch await activeTimeline.retrySend(eventOrTransactionID, target: target) {
|
||||||
|
case .success(let hadPendingSend):
|
||||||
|
MXLog.info("Finished retrying send, had one pending: \(hadPendingSend)")
|
||||||
|
case .failure(let error):
|
||||||
|
MXLog.error("Failed retrying send with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func abortSend(_ itemID: TimelineItemIdentifier, target: SendTarget) async {
|
||||||
|
guard let eventOrTransactionID = itemID.eventOrTransactionID else {
|
||||||
|
MXLog.error("Cannot abort sending on \(itemID).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MXLog.info("Abort \(target) send in \(roomID)")
|
||||||
|
|
||||||
|
switch await activeTimeline.abortSend(eventOrTransactionID, target: target) {
|
||||||
|
case .success(let hadPendingSend):
|
||||||
|
MXLog.info("Finished aborting send, had one pending: \(hadPendingSend)")
|
||||||
|
case .failure(let error):
|
||||||
|
MXLog.error("Failed aborting send with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Sending
|
// MARK: - Sending
|
||||||
|
|
||||||
func sendMessage(_ message: String,
|
func sendMessage(_ message: String,
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ protocol TimelineControllerProtocol: Sendable {
|
|||||||
|
|
||||||
func sendHandle(for itemID: TimelineItemIdentifier) -> SendHandleProxy?
|
func sendHandle(for itemID: TimelineItemIdentifier) -> SendHandleProxy?
|
||||||
|
|
||||||
|
func pendingSendTarget(for itemID: TimelineItemIdentifier) -> SendTarget?
|
||||||
|
|
||||||
|
func retrySend(_ itemID: TimelineItemIdentifier, target: SendTarget) async
|
||||||
|
|
||||||
|
func abortSend(_ itemID: TimelineItemIdentifier, target: SendTarget) async
|
||||||
|
|
||||||
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date?
|
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date?
|
||||||
|
|
||||||
// MARK: - Sending
|
// MARK: - Sending
|
||||||
|
|||||||
@@ -84,11 +84,11 @@ final nonisolated class EventTimelineItemProxy: Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var deliveryStatus: TimelineItemDeliveryStatus? {
|
var deliveryStatus: TimelineItemDeliveryStatus? {
|
||||||
guard let localSendState = item.localSendState else {
|
guard let pendingSend else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch localSendState {
|
switch pendingSend.state {
|
||||||
case .sendingFailed(let error, _):
|
case .sendingFailed(let error, _):
|
||||||
switch error {
|
switch error {
|
||||||
case .identityViolations(let users):
|
case .identityViolations(let users):
|
||||||
@@ -107,6 +107,19 @@ final nonisolated class EventTimelineItemProxy: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The item's own pending send or, when it has already gone out, our pending edit or redaction of it.
|
||||||
|
var pendingSend: (target: SendTarget, state: EventSendState)? {
|
||||||
|
if let state = item.localSendState {
|
||||||
|
(.event, state)
|
||||||
|
} else if let state = item.editSendState {
|
||||||
|
(.edit, state)
|
||||||
|
} else if let state = item.redactionSendState {
|
||||||
|
(.redaction, state)
|
||||||
|
} else {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var canBeRepliedTo: Bool {
|
var canBeRepliedTo: Bool {
|
||||||
item.canBeRepliedTo
|
item.canBeRepliedTo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,6 +213,36 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func retrySend(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
MXLog.info("Retrying \(target) send on timeline item: \(eventOrTransactionID)")
|
||||||
|
|
||||||
|
do {
|
||||||
|
let hadPendingSend = try await timeline.retrySend(itemId: eventOrTransactionID.rustValue, target: target)
|
||||||
|
|
||||||
|
MXLog.info("Retried \(target) send on timeline item: \(eventOrTransactionID), had one pending: \(hadPendingSend)")
|
||||||
|
|
||||||
|
return .success(hadPendingSend)
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed retrying \(target) send on timeline item: \(eventOrTransactionID) with error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func abortSend(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, target: SendTarget) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
MXLog.info("Aborting \(target) send on timeline item: \(eventOrTransactionID)")
|
||||||
|
|
||||||
|
do {
|
||||||
|
let hadPendingSend = try await timeline.abortSend(itemId: eventOrTransactionID.rustValue, target: target)
|
||||||
|
|
||||||
|
MXLog.info("Aborted \(target) send on timeline item: \(eventOrTransactionID), had one pending: \(hadPendingSend)")
|
||||||
|
|
||||||
|
return .success(hadPendingSend)
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed aborting \(target) send on timeline item: \(eventOrTransactionID) with error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getLoadedReplyDetails(eventID: String) async -> Result<InReplyToDetails, TimelineProxyError> {
|
func getLoadedReplyDetails(eventID: String) async -> Result<InReplyToDetails, TimelineProxyError> {
|
||||||
do {
|
do {
|
||||||
return try await .success(timeline.loadReplyDetails(eventIdStr: eventID))
|
return try await .success(timeline.loadReplyDetails(eventIdStr: eventID))
|
||||||
|
|||||||
@@ -99,6 +99,12 @@ protocol TimelineProxyProtocol: Sendable {
|
|||||||
func redact(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID,
|
func redact(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID,
|
||||||
reason: String?) async -> Result<Void, TimelineProxyError>
|
reason: String?) async -> Result<Void, TimelineProxyError>
|
||||||
|
|
||||||
|
func retrySend(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID,
|
||||||
|
target: SendTarget) async -> Result<Bool, TimelineProxyError>
|
||||||
|
|
||||||
|
func abortSend(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID,
|
||||||
|
target: SendTarget) async -> Result<Bool, TimelineProxyError>
|
||||||
|
|
||||||
func pin(eventID: String) async -> Result<Bool, TimelineProxyError>
|
func pin(eventID: String) async -> Result<Bool, TimelineProxyError>
|
||||||
|
|
||||||
func unpin(eventID: String) async -> Result<Bool, TimelineProxyError>
|
func unpin(eventID: String) async -> Result<Bool, TimelineProxyError>
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ final class TimelineViewModelTests {
|
|||||||
// Given a room with a message that failed to send for a known reason
|
// Given a room with a message that failed to send for a known reason
|
||||||
let items = [TextRoomTimelineItem(eventID: "t1", sendFailure: .unknown(reason: "M_TOO_LARGE"))]
|
let items = [TextRoomTimelineItem(eventID: "t1", sendFailure: .unknown(reason: "M_TOO_LARGE"))]
|
||||||
let timelineController = TimelineControllerMock(.init(timelineItems: items))
|
let timelineController = TimelineControllerMock(.init(timelineItems: items))
|
||||||
timelineController.sendHandleForReturnValue = .mock
|
timelineController.pendingSendTargetForReturnValue = .event
|
||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
viewModel.process(viewAction: .itemSendInfoTapped(itemID: items[0].id))
|
viewModel.process(viewAction: .itemSendInfoTapped(itemID: items[0].id))
|
||||||
|
|||||||
Reference in New Issue
Block a user