From 003b494c90e330e9feac3acecee8f58a6fd9c05d Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:09:29 +0100 Subject: [PATCH] Artificial delay when a message fails to send (#2091) --- .../Style/TimelineItemBubbledStylerView.swift | 9 +++-- .../Style/TimelineItemPlainStylerView.swift | 3 +- .../View/Style/TimelineStyler.swift | 37 ++++++++++++++++++- .../TimelineItemStatusView.swift | 7 ++-- changelog.d/1352.bugfix | 1 + changelog.d/2078.feature | 1 + 6 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 changelog.d/1352.bugfix create mode 100644 changelog.d/2078.feature diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index ce54fdd99..f292d49dd 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -24,6 +24,7 @@ struct TimelineItemBubbledStylerView: View { @Environment(\.timelineGroupStyle) private var timelineGroupStyle let timelineItem: EventBasedTimelineItemProtocol + let adjustedDeliveryStatus: TimelineItemDeliveryStatus? @ViewBuilder let content: () -> Content @State private var showItemActionMenu = false @@ -62,7 +63,7 @@ struct TimelineItemBubbledStylerView: View { if !timelineItem.isOutgoing { Spacer() } - TimelineItemStatusView(timelineItem: timelineItem) + TimelineItemStatusView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus) .environmentObject(context) .padding(.top, 8) .padding(.bottom, 3) @@ -162,7 +163,7 @@ struct TimelineItemBubbledStylerView: View { @ViewBuilder var interactiveLocalizedSendInfo: some View { - if timelineItem.hasFailedToSend { + if adjustedDeliveryStatus == .sendingFailed { layoutedLocalizedSendInfo .onTapGesture { context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id) @@ -203,12 +204,12 @@ struct TimelineItemBubbledStylerView: View { Text(timelineItem.timestamp) } - if timelineItem.hasFailedToSend { + if adjustedDeliveryStatus == .sendingFailed { CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyXS) } } .font(.compound.bodyXS) - .foregroundColor(timelineItem.hasFailedToSend ? .compound.textCriticalPrimary : .compound.textSecondary) + .foregroundColor(adjustedDeliveryStatus == .sendingFailed ? .compound.textCriticalPrimary : .compound.textSecondary) } @ViewBuilder diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift index a5f701edc..300ef1422 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift @@ -22,6 +22,7 @@ struct TimelineItemPlainStylerView: View { @Environment(\.timelineGroupStyle) private var timelineGroupStyle let timelineItem: EventBasedTimelineItemProtocol + let adjustedDeliveryStatus: TimelineItemDeliveryStatus? @ViewBuilder let content: () -> Content @State private var showItemActionMenu = false @@ -36,7 +37,7 @@ struct TimelineItemPlainStylerView: View { supplementaryViews } } - TimelineItemStatusView(timelineItem: timelineItem) + TimelineItemStatusView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus) .environmentObject(context) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift index f399c614f..116eb0c45 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift @@ -24,13 +24,46 @@ struct TimelineStyler: View { let timelineItem: EventBasedTimelineItemProtocol @ViewBuilder let content: () -> Content + + @State private var adjustedDeliveryStatus: TimelineItemDeliveryStatus? + @State private var task: Task? + + init(timelineItem: EventBasedTimelineItemProtocol, @ViewBuilder content: @escaping () -> Content) { + self.timelineItem = timelineItem + self.content = content + _adjustedDeliveryStatus = State(initialValue: timelineItem.properties.deliveryStatus) + } var body: some View { + mainContent + .onChange(of: timelineItem.properties.deliveryStatus) { newStatus in + if newStatus == .sendingFailed { + guard task == nil else { + return + } + task = Task { + try? await Task.sleep(for: .milliseconds(700)) + if !Task.isCancelled { + adjustedDeliveryStatus = newStatus + } + task = nil + } + } else { + task?.cancel() + task = nil + adjustedDeliveryStatus = newStatus + } + } + .animation(.elementDefault, value: adjustedDeliveryStatus) + } + + @ViewBuilder + var mainContent: some View { switch style { case .plain: - TimelineItemPlainStylerView(timelineItem: timelineItem, content: content) + TimelineItemPlainStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content) case .bubbles: - TimelineItemBubbledStylerView(timelineItem: timelineItem, content: content) + TimelineItemBubbledStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content) } } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift index d826eee80..c58febb39 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift @@ -19,6 +19,7 @@ import SwiftUI struct TimelineItemStatusView: View { let timelineItem: EventBasedTimelineItemProtocol + let adjustedDeliveryStatus: TimelineItemDeliveryStatus? @Environment(\.timelineStyle) private var style @Environment(\.readReceiptsEnabled) private var readReceiptsEnabled @EnvironmentObject private var context: RoomScreenViewModel.Context @@ -36,13 +37,13 @@ struct TimelineItemStatusView: View { if !timelineItem.properties.orderedReadReceipts.isEmpty, readReceiptsEnabled { readReceipts } else { - deliveryStatus + deliveryStatusBadge } } @ViewBuilder - var deliveryStatus: some View { - switch timelineItem.properties.deliveryStatus { + var deliveryStatusBadge: some View { + switch adjustedDeliveryStatus { case .sending: TimelineDeliveryStatusView(deliveryStatus: .sending) case .sent, .none: diff --git a/changelog.d/1352.bugfix b/changelog.d/1352.bugfix new file mode 100644 index 000000000..33fdc4977 --- /dev/null +++ b/changelog.d/1352.bugfix @@ -0,0 +1 @@ +Added a small artificial delay when resending a failed message if it fails instantly, for better UX. \ No newline at end of file diff --git a/changelog.d/2078.feature b/changelog.d/2078.feature new file mode 100644 index 000000000..9a0bea26b --- /dev/null +++ b/changelog.d/2078.feature @@ -0,0 +1 @@ +Added a disclaimer on the Mentions Only option for notifications, so that the user will know if it will work properly or not on their home server. \ No newline at end of file