mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Artificial delay when a message fails to send (#2091)
This commit is contained in:
@@ -24,6 +24,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
||||||
|
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
|
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
|
|
||||||
@State private var showItemActionMenu = false
|
@State private var showItemActionMenu = false
|
||||||
@@ -62,7 +63,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
if !timelineItem.isOutgoing {
|
if !timelineItem.isOutgoing {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
TimelineItemStatusView(timelineItem: timelineItem)
|
TimelineItemStatusView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus)
|
||||||
.environmentObject(context)
|
.environmentObject(context)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
.padding(.bottom, 3)
|
.padding(.bottom, 3)
|
||||||
@@ -162,7 +163,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var interactiveLocalizedSendInfo: some View {
|
var interactiveLocalizedSendInfo: some View {
|
||||||
if timelineItem.hasFailedToSend {
|
if adjustedDeliveryStatus == .sendingFailed {
|
||||||
layoutedLocalizedSendInfo
|
layoutedLocalizedSendInfo
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
||||||
@@ -203,12 +204,12 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
Text(timelineItem.timestamp)
|
Text(timelineItem.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if timelineItem.hasFailedToSend {
|
if adjustedDeliveryStatus == .sendingFailed {
|
||||||
CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyXS)
|
CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyXS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.compound.bodyXS)
|
.font(.compound.bodyXS)
|
||||||
.foregroundColor(timelineItem.hasFailedToSend ? .compound.textCriticalPrimary : .compound.textSecondary)
|
.foregroundColor(adjustedDeliveryStatus == .sendingFailed ? .compound.textCriticalPrimary : .compound.textSecondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
|||||||
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
||||||
|
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
|
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
|
|
||||||
@State private var showItemActionMenu = false
|
@State private var showItemActionMenu = false
|
||||||
@@ -36,7 +37,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
|||||||
supplementaryViews
|
supplementaryViews
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TimelineItemStatusView(timelineItem: timelineItem)
|
TimelineItemStatusView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus)
|
||||||
.environmentObject(context)
|
.environmentObject(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,46 @@ struct TimelineStyler<Content: View>: View {
|
|||||||
|
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
|
|
||||||
|
@State private var adjustedDeliveryStatus: TimelineItemDeliveryStatus?
|
||||||
|
@State private var task: Task<Void, Never>?
|
||||||
|
|
||||||
|
init(timelineItem: EventBasedTimelineItemProtocol, @ViewBuilder content: @escaping () -> Content) {
|
||||||
|
self.timelineItem = timelineItem
|
||||||
|
self.content = content
|
||||||
|
_adjustedDeliveryStatus = State(initialValue: timelineItem.properties.deliveryStatus)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
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 {
|
switch style {
|
||||||
case .plain:
|
case .plain:
|
||||||
TimelineItemPlainStylerView(timelineItem: timelineItem, content: content)
|
TimelineItemPlainStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
|
||||||
case .bubbles:
|
case .bubbles:
|
||||||
TimelineItemBubbledStylerView(timelineItem: timelineItem, content: content)
|
TimelineItemBubbledStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct TimelineItemStatusView: View {
|
struct TimelineItemStatusView: View {
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
|
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
|
||||||
@Environment(\.timelineStyle) private var style
|
@Environment(\.timelineStyle) private var style
|
||||||
@Environment(\.readReceiptsEnabled) private var readReceiptsEnabled
|
@Environment(\.readReceiptsEnabled) private var readReceiptsEnabled
|
||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
@@ -36,13 +37,13 @@ struct TimelineItemStatusView: View {
|
|||||||
if !timelineItem.properties.orderedReadReceipts.isEmpty, readReceiptsEnabled {
|
if !timelineItem.properties.orderedReadReceipts.isEmpty, readReceiptsEnabled {
|
||||||
readReceipts
|
readReceipts
|
||||||
} else {
|
} else {
|
||||||
deliveryStatus
|
deliveryStatusBadge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var deliveryStatus: some View {
|
var deliveryStatusBadge: some View {
|
||||||
switch timelineItem.properties.deliveryStatus {
|
switch adjustedDeliveryStatus {
|
||||||
case .sending:
|
case .sending:
|
||||||
TimelineDeliveryStatusView(deliveryStatus: .sending)
|
TimelineDeliveryStatusView(deliveryStatus: .sending)
|
||||||
case .sent, .none:
|
case .sent, .none:
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Added a small artificial delay when resending a failed message if it fails instantly, for better UX.
|
||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user