mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Retry send and cancel send for failed to send messages (#1088)
* is now possible to open the confirmation dialog and pass the transaction id * task completed added some tests * changelog * title visibility set to .visible
This commit is contained in:
@@ -440,6 +440,7 @@
|
||||
A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */; };
|
||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||
A7F55D1C2A3B639A009D8E93 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */; };
|
||||
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
||||
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
|
||||
@@ -1121,6 +1122,7 @@
|
||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||
A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = "<group>"; };
|
||||
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
|
||||
A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = "<group>"; };
|
||||
@@ -1928,6 +1930,7 @@
|
||||
CED722AC2A3B0E7B000F10C8 /* URLSession.swift */,
|
||||
897DF5E9A70CE05A632FC8AF /* UTType.swift */,
|
||||
E992D7B8BE54B2AB454613AF /* XCUIElement.swift */,
|
||||
A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -4206,6 +4209,7 @@
|
||||
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */,
|
||||
1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */,
|
||||
DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */,
|
||||
A7F55D1C2A3B639A009D8E93 /* ConfirmationDialog.swift in Sources */,
|
||||
5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */,
|
||||
C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */,
|
||||
F32B271F60531BE92C6E62A1 /* StickerRoomTimelineView.swift in Sources */,
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"screen_room_member_details_unblock_user" = "Unblock user";
|
||||
"screen_room_member_list_pending_header_title" = "Pending";
|
||||
"screen_room_member_list_room_members_header_title" = "Room members";
|
||||
"screen_room_retry_send_menu_send_again_action" = "Send again";
|
||||
"screen_room_retry_send_menu_title" = "Your message failed to send";
|
||||
"screen_roomlist_a11y_create_message" = "Create a new conversation or room";
|
||||
"screen_roomlist_main_space_title" = "All Chats";
|
||||
"screen_server_confirmation_change_server" = "Change account provider";
|
||||
@@ -387,6 +389,7 @@
|
||||
"screen_room_details_security_title" = "Security";
|
||||
"screen_room_details_topic_title" = "Topic";
|
||||
"screen_room_error_failed_processing_media" = "Failed processing media to upload, please try again.";
|
||||
"screen_room_retry_send_menu_remove_action" = "Remove";
|
||||
"screen_session_verification_cancelled_title" = "Verification cancelled";
|
||||
"screen_session_verification_positive_button_ready" = "Start";
|
||||
"screen_signout_confirmation_dialog_submit" = "Sign out";
|
||||
|
||||
@@ -702,6 +702,12 @@ public enum L10n {
|
||||
public static var screenRoomMemberListPendingHeaderTitle: String { return L10n.tr("Localizable", "screen_room_member_list_pending_header_title") }
|
||||
/// Room members
|
||||
public static var screenRoomMemberListRoomMembersHeaderTitle: String { return L10n.tr("Localizable", "screen_room_member_list_room_members_header_title") }
|
||||
/// Remove
|
||||
public static var screenRoomRetrySendMenuRemoveAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_remove_action") }
|
||||
/// Send again
|
||||
public static var screenRoomRetrySendMenuSendAgainAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_send_again_action") }
|
||||
/// Your message failed to send
|
||||
public static var screenRoomRetrySendMenuTitle: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_title") }
|
||||
/// Create a new conversation or room
|
||||
public static var screenRoomlistA11yCreateMessage: String { return L10n.tr("Localizable", "screen_roomlist_a11y_create_message") }
|
||||
/// All Chats
|
||||
|
||||
@@ -685,6 +685,38 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return sendFileUrlFileInfoReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - retrySend
|
||||
|
||||
var retrySendTransactionIDCallsCount = 0
|
||||
var retrySendTransactionIDCalled: Bool {
|
||||
return retrySendTransactionIDCallsCount > 0
|
||||
}
|
||||
var retrySendTransactionIDReceivedTransactionID: String?
|
||||
var retrySendTransactionIDReceivedInvocations: [String] = []
|
||||
var retrySendTransactionIDClosure: ((String) async -> Void)?
|
||||
|
||||
func retrySend(transactionID: String) async {
|
||||
retrySendTransactionIDCallsCount += 1
|
||||
retrySendTransactionIDReceivedTransactionID = transactionID
|
||||
retrySendTransactionIDReceivedInvocations.append(transactionID)
|
||||
await retrySendTransactionIDClosure?(transactionID)
|
||||
}
|
||||
//MARK: - cancelSend
|
||||
|
||||
var cancelSendTransactionIDCallsCount = 0
|
||||
var cancelSendTransactionIDCalled: Bool {
|
||||
return cancelSendTransactionIDCallsCount > 0
|
||||
}
|
||||
var cancelSendTransactionIDReceivedTransactionID: String?
|
||||
var cancelSendTransactionIDReceivedInvocations: [String] = []
|
||||
var cancelSendTransactionIDClosure: ((String) async -> Void)?
|
||||
|
||||
func cancelSend(transactionID: String) async {
|
||||
cancelSendTransactionIDCallsCount += 1
|
||||
cancelSendTransactionIDReceivedTransactionID = transactionID
|
||||
cancelSendTransactionIDReceivedInvocations.append(transactionID)
|
||||
await cancelSendTransactionIDClosure?(transactionID)
|
||||
}
|
||||
//MARK: - editMessage
|
||||
|
||||
var editMessageOriginalCallsCount = 0
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
protocol ConfirmationDialogProtocol {
|
||||
var title: String { get }
|
||||
}
|
||||
|
||||
extension View {
|
||||
func confirmationDialog<Item, Actions>(item: Binding<Item?>,
|
||||
titleVisibility: Visibility = .automatic,
|
||||
@ViewBuilder actions: (Item) -> Actions) -> some View where Item: ConfirmationDialogProtocol, Actions: View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
if !newValue {
|
||||
item.wrappedValue = nil
|
||||
}
|
||||
})
|
||||
return confirmationDialog(item.wrappedValue?.title ?? "", isPresented: binding, titleVisibility: titleVisibility, presenting: item.wrappedValue, actions: actions)
|
||||
}
|
||||
|
||||
func confirmationDialog<Item, Actions, Message>(item: Binding<Item?>,
|
||||
titleVisibility: Visibility = .automatic,
|
||||
@ViewBuilder actions: (Item) -> Actions,
|
||||
@ViewBuilder message: (Item) -> Message) -> some View where Item: ConfirmationDialogProtocol, Actions: View, Message: View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
if !newValue {
|
||||
item.wrappedValue = nil
|
||||
}
|
||||
})
|
||||
return confirmationDialog(item.wrappedValue?.title ?? "", isPresented: binding, titleVisibility: titleVisibility, presenting: item.wrappedValue, actions: actions, message: message)
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,9 @@ enum RoomScreenViewAction {
|
||||
|
||||
case handlePasteOrDrop(provider: NSItemProvider)
|
||||
case tappedOnUser(userID: String)
|
||||
|
||||
case retrySend(transactionID: String?)
|
||||
case cancelSend(transactionID: String?)
|
||||
}
|
||||
|
||||
struct RoomScreenViewState: BindableState {
|
||||
@@ -109,6 +112,8 @@ struct RoomScreenViewStateBindings {
|
||||
var debugInfo: TimelineItemDebugInfo?
|
||||
|
||||
var actionMenuInfo: TimelineItemActionMenuInfo?
|
||||
|
||||
var sendFailedConfirmationDialogInfo: SendFailedConfirmationDialogInfo?
|
||||
}
|
||||
|
||||
struct TimelineItemActionMenuInfo: Identifiable {
|
||||
@@ -119,6 +124,12 @@ struct TimelineItemActionMenuInfo: Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
struct SendFailedConfirmationDialogInfo: ConfirmationDialogProtocol {
|
||||
let title = L10n.screenRoomRetrySendMenuTitle
|
||||
|
||||
let transactionID: String?
|
||||
}
|
||||
|
||||
enum RoomScreenErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
|
||||
@@ -105,10 +105,13 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
handlePasteOrDrop(provider)
|
||||
case .tappedOnUser(userID: let userID):
|
||||
Task { await handleTappedUser(userID: userID) }
|
||||
|
||||
case .displayEmojiPicker(let itemID):
|
||||
guard let item = state.items.first(where: { $0.id == itemID }), item.isReactable else { return }
|
||||
callback?(.displayEmojiPicker(itemID: itemID))
|
||||
case .retrySend(let transactionID):
|
||||
Task { await handleRetrySend(transactionID: transactionID) }
|
||||
case .cancelSend(let transactionID):
|
||||
Task { await handleCancelSend(transactionID: transactionID) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,6 +473,22 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
}
|
||||
|
||||
private func handleRetrySend(transactionID: String?) async {
|
||||
guard let transactionID else {
|
||||
return
|
||||
}
|
||||
|
||||
await roomProxy.retrySend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
private func handleCancelSend(transactionID: String?) async {
|
||||
guard let transactionID else {
|
||||
return
|
||||
}
|
||||
|
||||
await roomProxy.cancelSend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
private static let loadingIndicatorIdentifier = "RoomScreenLoadingIndicator"
|
||||
|
||||
private func showLoadingIndicator(with delay: Duration) -> Task<Void, Never> {
|
||||
|
||||
@@ -62,6 +62,14 @@ struct RoomScreen: View {
|
||||
context.send(viewAction: .handlePasteOrDrop(provider: provider))
|
||||
return true
|
||||
}
|
||||
.confirmationDialog(item: $context.sendFailedConfirmationDialogInfo, titleVisibility: .visible) { item in
|
||||
Button(L10n.screenRoomRetrySendMenuSendAgainAction) {
|
||||
context.send(viewAction: .retrySend(transactionID: item.transactionID))
|
||||
}
|
||||
Button(L10n.screenRoomRetrySendMenuRemoveAction, role: .destructive) {
|
||||
context.send(viewAction: .cancelSend(transactionID: item.transactionID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var timeline: some View {
|
||||
|
||||
+31
-14
@@ -146,30 +146,47 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
|
||||
@ViewBuilder
|
||||
var contentWithTimestamp: some View {
|
||||
if isTextItem {
|
||||
if isTextItem || isMediaType {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
contentWithReply
|
||||
localizedSendInfo
|
||||
}
|
||||
} else if isMediaType {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
contentWithReply
|
||||
localizedSendInfo
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.element.system)
|
||||
.cornerRadius(10)
|
||||
.padding(.trailing, 4)
|
||||
.padding(.bottom, 4)
|
||||
interactiveLocalizedSendInfo
|
||||
}
|
||||
} else {
|
||||
HStack(alignment: .bottom, spacing: 4) {
|
||||
contentWithReply
|
||||
localizedSendInfo
|
||||
interactiveLocalizedSendInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var interactiveLocalizedSendInfo: some View {
|
||||
if timelineItem.properties.deliveryStatus == .sendingFailed {
|
||||
backgroundedLocalizedSendInfo
|
||||
.onTapGesture {
|
||||
context.sendFailedConfirmationDialogInfo = .init(transactionID: timelineItem.properties.transactionID)
|
||||
}
|
||||
} else {
|
||||
backgroundedLocalizedSendInfo
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var backgroundedLocalizedSendInfo: some View {
|
||||
if isMediaType {
|
||||
localizedSendInfo
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.element.system)
|
||||
.cornerRadius(10)
|
||||
.padding(.trailing, 4)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
} else {
|
||||
localizedSendInfo
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var localizedSendInfo: some View {
|
||||
HStack(spacing: 4) {
|
||||
|
||||
@@ -69,6 +69,9 @@ struct TimelineItemStatusView: View {
|
||||
.resizable()
|
||||
.foregroundColor(.element.alert)
|
||||
.frame(width: 16, height: 16)
|
||||
.onTapGesture {
|
||||
context.sendFailedConfirmationDialogInfo = .init(transactionID: timelineItem.properties.transactionID)
|
||||
}
|
||||
}
|
||||
// The bubbles handle the failure internally
|
||||
}
|
||||
|
||||
@@ -326,6 +326,28 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func retrySend(transactionID: String) async {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
return await Task.dispatch(on: userInitiatedDispatchQueue) {
|
||||
self.room.retrySend(txnId: transactionID)
|
||||
}
|
||||
}
|
||||
|
||||
func cancelSend(transactionID: String) async {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
return await Task.dispatch(on: userInitiatedDispatchQueue) {
|
||||
self.room.cancelSend(txnId: transactionID)
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
|
||||
@@ -101,6 +101,12 @@ protocol RoomProxyProtocol {
|
||||
|
||||
func sendFile(url: URL, fileInfo: FileInfo) async -> Result<Void, RoomProxyError>
|
||||
|
||||
/// Retries sending a failed message given its transaction ID
|
||||
func retrySend(transactionID: String) async
|
||||
|
||||
/// Cancels a failed message given its transaction ID from the timeline
|
||||
func cancelSend(transactionID: String) async
|
||||
|
||||
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
+2
@@ -26,4 +26,6 @@ struct RoomTimelineItemProperties: Hashable {
|
||||
var deliveryStatus: TimelineItemDeliveryStatus?
|
||||
/// The read receipts of the item, ordered from newest to oldest
|
||||
var orderedReadReceipts: [ReadReceipt] = []
|
||||
/// The original transaction id transmitted by the client
|
||||
var transactionID: String?
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ struct EventTimelineItemProxy {
|
||||
var id: String {
|
||||
item.uniqueIdentifier()
|
||||
}
|
||||
|
||||
var transactionID: String? {
|
||||
item.transactionId()
|
||||
}
|
||||
|
||||
var deliveryStatus: TimelineItemDeliveryStatus? {
|
||||
guard let localSendState = item.localSendState() else {
|
||||
|
||||
@@ -133,7 +133,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
blurhash: imageInfo.blurhash,
|
||||
properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -184,7 +185,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -201,7 +203,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -218,7 +221,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -235,7 +239,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -252,7 +257,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -269,7 +275,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -286,7 +293,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
}
|
||||
|
||||
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
|
||||
|
||||
@@ -248,6 +248,68 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1)
|
||||
XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob")
|
||||
}
|
||||
|
||||
func testRetrySend() async {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
let viewModel = RoomScreenViewModel(timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomProxy: roomProxyMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .retrySend(transactionID: "test retry send id"))
|
||||
await Task.yield()
|
||||
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 1)
|
||||
XCTAssert(roomProxyMock.retrySendTransactionIDReceivedInvocations == ["test retry send id"])
|
||||
}
|
||||
|
||||
func testRetrySendNoTransactionID() async {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
let viewModel = RoomScreenViewModel(timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomProxy: roomProxyMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .retrySend(transactionID: nil))
|
||||
await Task.yield()
|
||||
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 0)
|
||||
}
|
||||
|
||||
func testCancelSend() async {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
let viewModel = RoomScreenViewModel(timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomProxy: roomProxyMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .cancelSend(transactionID: "test cancel send id"))
|
||||
await Task.yield()
|
||||
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 1)
|
||||
XCTAssert(roomProxyMock.cancelSendTransactionIDReceivedInvocations == ["test cancel send id"])
|
||||
}
|
||||
|
||||
func testCancelSendNoTransactionID() async {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
let viewModel = RoomScreenViewModel(timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomProxy: roomProxyMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .cancelSend(transactionID: nil))
|
||||
await Task.yield()
|
||||
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 0)
|
||||
}
|
||||
}
|
||||
|
||||
private extension TextRoomTimelineItem {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Failed to send messages can now be either retried or removed by tapping on the error icon/timestamp.
|
||||
Reference in New Issue
Block a user