Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ class TimelineTableViewController: UIViewController {
}
}

// We only animate when there's a new last message, so its safe
// We only animate when there's a new last message, so it's safe
// to animate from the bottom (which is the top as we're flipped).
dataSource?.defaultRowAnimation = (UIAccessibility.isReduceMotionEnabled ? .none : .top)
tableView.delegate = self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
.zIndex(1)
}

VStack(alignment: alignment, spacing: 0) {
VStack(alignment: alignment, spacing: -4) {
// -4 spacing to compensate for the Spacer we have to add to stop
// animation oversteer - see below.
// XXX: does this squidge the bubble & the SR too close together now?
// it looks like it should, but in practice it doesn't seem to.

HStack(spacing: 0) {
if timelineItem.isOutgoing {
Spacer()
Expand All @@ -59,17 +64,36 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
if !timelineItem.isOutgoing {
Spacer()
}
TimelineItemStatusView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus)
TimelineItemStatusView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, context: context)
.environmentObject(context)
.padding(.top, 8)
.padding(.bottom, 3)
}
// .background(Color.purple)

// we need a Spacer here to top-align the bubble within the VStack, so that
// when the the animation doesn't drift around and overshoot when the SR is hidden
// see https://github.com/element-hq/element-x-ios/issues/4127
Spacer()
// .background { Color.yellow.frame(minWidth: 10) }
}
.padding(.horizontal, bubbleHorizontalPadding)
.padding(.leading, bubbleAvatarPadding)
}
}
// .background {
// Int(timelineItem.id.uniqueID.value).unsafelyUnwrapped % 4 == 0 ? Color.green :
// Int(timelineItem.id.uniqueID.value).unsafelyUnwrapped % 4 == 1 ? Color.cyan :
// Int(timelineItem.id.uniqueID.value).unsafelyUnwrapped % 4 == 2 ? Color.brown :
// Color.indigo
// }
.padding(EdgeInsets(top: 1, leading: 8, bottom: 1, trailing: 8))
// hoist the unanimated messageBubbleTopPadding to the topmost view, as otherwise the
// UITableView row pops when this View changes layout seemingly.
// N.B. we can't combine two paddings together without triggering popping.
.padding(.top, messageBubbleTopPadding)
// don't reanimate bubble layouts if we're just changing the messageBubbleTopPadding
.animation(nil, value: messageBubbleTopPadding)
.highlightedTimelineItem(isFocussed)
}

Expand Down Expand Up @@ -163,7 +187,6 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
}
}
.pinnedIndicator(isPinned: isPinned, isOutgoing: timelineItem.isOutgoing)
.padding(.top, messageBubbleTopPadding)
}

var messageBubble: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,35 @@ struct TimelineItemStatusView: View {
let timelineItem: EventBasedTimelineItemProtocol
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
@EnvironmentObject private var context: TimelineViewModel.Context
@State private var isSendReceiptVisible: Bool

private var isLastOutgoingMessage: Bool {
timelineItem.isOutgoing && context.viewState.timelineState.uniqueIDs.last == timelineItem.id.uniqueID
}

init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?, context: TimelineViewModel.Context, isSendReceiptVisible: Bool = false) {
self.timelineItem = timelineItem
self.adjustedDeliveryStatus = adjustedDeliveryStatus
// Ugly - we can't call isLastOutgoingMessage here as the real `context` hasn't loaded yet
// so instead we manually pass in context to init() and duplicate isLastOutgoingMessage here.
self.isSendReceiptVisible = timelineItem.isOutgoing && context.viewState.timelineState.uniqueIDs.last == timelineItem.id.uniqueID
}

var body: some View {
mainContent
.onChange(of: context.viewState.timelineState.uniqueIDs.last) { _, _ in
if isLastOutgoingMessage {
isSendReceiptVisible = true
} else if isSendReceiptVisible {
// we were the last msg in the timeline, but not any more
// so remove the SR after a short delay to avoid racing with the new msg animation
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation {
isSendReceiptVisible = false
}
}
}
}
}

@ViewBuilder
Expand All @@ -39,9 +61,10 @@ struct TimelineItemStatusView: View {
case .sending:
TimelineDeliveryStatusView(deliveryStatus: .sending)
case .sent, .none:
if isLastOutgoingMessage {
if isSendReceiptVisible {
// We only display the sent icon for the latest outgoing message
TimelineDeliveryStatusView(deliveryStatus: .sent)
// .transition(.identity) // makes the SR disappear rapidly to avoid ugly z-index flickering
}
case .sendingFailed:
// Bubbles handle the case internally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct SeparatorRoomTimelineView: View {
.multilineTextAlignment(.center)
.padding(.horizontal, 36.0)
.padding(.vertical, 8.0)
// .background(Color.red)
}
}

Expand Down
Loading