Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### 🐞 Fixed
- Fix show/hide message translation animation [#1426](https://github.com/GetStream/stream-chat-swiftui/pull/1426)
- Fix tapping a media attachment in the reactions overlay opening the fullscreen gallery [#1424](https://github.com/GetStream/stream-chat-swiftui/pull/1424)
- Fix empty space around the previewed message in the reactions overlay not dismissing the overlay [#1424](https://github.com/GetStream/stream-chat-swiftui/pull/1424)
- Fix long-pressing a message with attachments occasionally opening the fullscreen gallery [#1424](https://github.com/GetStream/stream-chat-swiftui/pull/1424)

# [5.0.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/5.0.0)
_April 16, 2026_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ struct MessageContainerView<Factory: ViewFactory>: View {
messageViewModel.failureIndicatorShown ? SendFailureIndicator() : nil
)
.frame(maxWidth: contentWidth, alignment: messageViewModel.isRightAligned ? .trailing : .leading)
.highPriorityGesture(
TapGesture().onEnded { /* Swallow taps on the bubble so attachments don't open and the overlay doesn't dismiss. */ },
including: shownAsPreview ? .all : .none
)
}

@ViewBuilder
Expand Down
60 changes: 48 additions & 12 deletions Sources/StreamChatSwiftUI/ChatMessageList/MessageItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,11 @@ public struct MessageItemView<Factory: ViewFactory>: View {
}
)
.contentShape(Rectangle())
.allowsHitTesting(!shownAsPreview || (messageViewModel.usesScrollView))
.onTapGesture(count: 2) {
if messageViewModel.isDoubleTapOverlayEnabled {
handleGestureForMessage(showsMessageActions: true)
}
}
.simultaneousGesture(
LongPressGesture(minimumDuration: 0.3)
.onEnded { _ in
handleGestureForMessage(showsMessageActions: true)
}
)
.modifier(MessageActionsGestureModifier(
shownAsPreview: shownAsPreview,
isDoubleTapEnabled: messageViewModel.isDoubleTapOverlayEnabled,
onActionsTriggered: { handleGestureForMessage(showsMessageActions: true) }
))
.modifier(SwipeToReplyModifier(
message: message,
channel: channel,
Expand Down Expand Up @@ -196,6 +189,49 @@ public struct MessageItemView<Factory: ViewFactory>: View {
}
}

// MARK: - Message Actions Gesture

/// Attaches the double-tap and long-press gestures that open the message actions
/// overlay on a `MessageItemView`.
///
/// When the message is rendered as a preview inside the reactions overlay
/// (`shownAsPreview == true`), both gestures are intentionally skipped. Keeping
/// them would force SwiftUI to wait for double-tap / long-press disambiguation
/// before delivering a single tap to the overlay's dismiss handler, introducing
/// a noticeable delay when the user taps the empty space around the message
/// bubble to dismiss.
struct MessageActionsGestureModifier: ViewModifier {
/// Whether the message is rendered inside the reactions overlay.
/// When `true`, no gestures are attached.
let shownAsPreview: Bool
/// Whether double-tap should trigger the message actions overlay.
let isDoubleTapEnabled: Bool
/// Invoked when either the double-tap or long-press is recognized.
let onActionsTriggered: () -> Void

private let longPressMinimumDuration: Double = 0.3

@ViewBuilder
func body(content: Content) -> some View {
if shownAsPreview {
content
} else {
content
.onTapGesture(count: 2) {
if isDoubleTapEnabled {
onActionsTriggered()
}
}
.highPriorityGesture(
LongPressGesture(minimumDuration: longPressMinimumDuration)
.onEnded { _ in
onActionsTriggered()
}
)
}
}
}

// MARK: - Swipe to Reply

/// Areas that should not trigger swipe-to-reply (e.g. waveform sliders).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ public struct ReactionsOverlayView<Factory: ViewFactory>: View {

GeometryReader { reader in
let height = reader.frame(in: .local).height
Color.clear.preference(key: HeightPreferenceKey.self, value: height)
Color.clear
.preference(key: HeightPreferenceKey.self, value: height)
.contentShape(Rectangle())
.onTapGesture {
dismissReactionsOverlay { /* No additional handling. */ }
}

VStack(alignment: isRightAligned ? .trailing : .leading, spacing: tokens.spacingXs) {
reactionsPickerView(reader: reader)
Expand All @@ -107,6 +112,9 @@ public struct ReactionsOverlayView<Factory: ViewFactory>: View {
.frame(maxHeight: messageDisplayInfo.frame.height)
.scaleEffect(popIn || willPopOut ? 1 : 0.95)
.animation(willPopOut ? .easeInOut : popInAnimation, value: popIn)
.onTapGesture {
dismissReactionsOverlay { /* No additional handling. */ }
}
messageActionsView(reader: reader)
}
.frame(width: overlayContentWidth, alignment: isRightAligned ? .trailing : .leading)
Expand Down
Loading