Conversation
β¦iew and fix the layout of the message view to match the Figma design system
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| private var backgroundColor: UIColor { | ||
| if viewModel.shownInMessageList { | ||
| return viewModel.quotedByCurrentUser ? colors.chatBackgroundAttachmentOutgoing : colors.chatBackgroundAttachmentIncoming | ||
| } else { | ||
| return viewModel.isSentByCurrentUser ? colors.chatBackgroundOutgoing : colors.chatBackgroundIncoming | ||
| } | ||
| } |
There was a problem hiding this comment.
Slightly different background colors and logic. Else is used by composer
| @@ -7,13 +7,12 @@ import SwiftUI | |||
|
|
|||
| /// Background modifier for message reference views. | |||
| public struct ReferenceMessageViewBackgroundModifier: ViewModifier { | |||
There was a problem hiding this comment.
Used by composer and message list, needed changes to support different background colors.
| message: message, | ||
| reactionsShown: topReactionsShown | ||
| ) | ||
| MessageDecoratedView( |
There was a problem hiding this comment.
This is going to be used by reactions overlay. Message gesture logic stays here, but the main layout logic is now there along with reactions.
Generated by π« Danger |
| ) | ||
| } | ||
|
|
||
| var body: some View { |
There was a problem hiding this comment.
Main changes here is that we use alignment instead of spacers. Fixes many of small layout issues in messages with different attachments and applies updated spacings from Figma.
Next PR will implement updated bottom and top reactions.
| Color(message.isSentByCurrentUser ? colors.chatThreadConnectorOutgoing : colors.chatThreadConnectorIncoming), | ||
| Color(message.isSentByCurrentUser ? colors.chatBackgroundOutgoing : colors.chatBackgroundIncoming), |
There was a problem hiding this comment.
Changed the color for now, it looks too out of place if different color is used. There is an open question about it in Figma.
# Conflicts: # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_jumpToUnreadButton.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_typingIndicator.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_viewModelInit_withReactions.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_withReactions.1.png
martinmitrevski
left a comment
There was a problem hiding this comment.
looks good in general, left few comments. Also, few UI glitches shared on slack.
| viewModel: messageViewModel | ||
| ) | ||
| ) | ||
| .environment(\.channelTranslationLanguage, channel.membership?.language) |
There was a problem hiding this comment.
can we pass the translation language in the options now?
There was a problem hiding this comment.
We can, will clean this up in the PR finally
There was a problem hiding this comment.
Looked into this, going to be a bigger refactoring along with message view model env key and translations store. There is a ticket for that, proposing to do this with a separate PR.
There was a problem hiding this comment.
ok, let's do it in another one
| @State private var frame: CGRect = .zero | ||
| @State private var computeFrame = false | ||
|
|
||
| public init( |
There was a problem hiding this comment.
it's a bit tricky to track changes because of the new file. What else was changed except the swipe modifier? Which section to focus on?
There was a problem hiding this comment.
This got messy since I renamed, iterated a couple of times.
Handwritten notes
/// A view that renders a single message item in the message list, including
/// the avatar, bubble, reactions, thread replies, delivery status, and gesture handling.
public struct MessageItemView<Factory: ViewFactory>: View {
public init(
factory: Factory,
channel: ChatChannel,
message: ChatMessage,
width: CGFloat? = nil,
showsAllInfo: Bool,
shownAsPreview: Bool = false, <<< for changing colors when shown in the ReactionsOverlayView
public var body: some View {
HStack(alignment: .bottom) {
// β¦
.onTapGesture(count: 2) {
if messageViewModel.isDoubleTapOverlayEnabled {
handleGestureForMessage(showsMessageActions: true)
}
}
.onLongPressGesture(perform: {
handleGestureForMessage(showsMessageActions: true)
})
// Swiping is in a view modifier for making it easier to turn it off when this view is used in the ReactionsOverlayView
.modifier(SwipeToReplyModifier(
message: message,
channel: channel,
isSwipeToQuoteReplyPossible: !shownAsPreview && messageViewModel.isSwipeToQuoteReplyPossible,
quotedMessage: $quotedMessage
))
}
// β¦
}
// MARK: - Message Content
private var messageView: some View {
// layout changes with new spacings and using alignment instead of spacers
HStack(alignment: .bottom, spacing: tokens.spacingXs) {
if !messageViewModel.isRightAligned {
avatarView
}
VStack(
alignment: messageViewModel.isRightAligned ? .trailing : .leading,
spacing: tokens.spacingXxs
) {
if messageViewModel.isPinned {
MessagePinDetailsView(
message: message,
reactionsShown: topReactionsShown
)
}
messageBubbleContent
.accessibilityElement(children: .contain)
.accessibilityIdentifier("MessageView")
if !isInThread {
threadRepliesView
}
if bottomReactionsShown {
factory.makeBottomReactionsView(
options: ReactionsBottomViewOptions(
message: message,
showsAllInfo: showsAllInfo,
onTap: {
handleGestureForMessage(showsMessageActions: false)
},
onLongPress: {
handleGestureForMessage(showsMessageActions: false)
}
)
)
}
if messageViewModel.translatedText != nil {
factory.makeMessageTranslationFooterView(
options: MessageTranslationFooterViewOptions(
messageViewModel: messageViewModel,
usesInvertedStyle: shownAsPreview <<< - makes the color light when shown on ReactionsOverlayView
)
)
}
// β¦
}
// MARK: - Sub-views
@ViewBuilder
private var messageBubbleContent: some View {
Group {
if messageViewModel.usesScrollView { <<< special for ReactionsOverlayView when long message becomes scrollable
ScrollView {
MessageView(
factory: factory,
message: message,
contentWidth: contentWidth,
isFirst: showsAllInfo,
scrolledId: $scrolledId
)
}
} else {
MessageView(
factory: factory,
message: message,
contentWidth: contentWidth,
isFirst: showsAllInfo,
scrolledId: $scrolledId
)
}
}
.overlay(
topReactionsShown ?
factory.makeMessageReactionView(
options: MessageReactionViewOptions(
message: message,
onTapGesture: {
handleGestureForMessage(showsMessageActions: false)
},
onLongPressGesture: {
handleGestureForMessage(showsMessageActions: false)
}
)
)
: nil,
alignment: messageViewModel.isRightAligned ? .trailing : .leading
)
.overlay(
messageViewModel.failureIndicatorShown ? SendFailureIndicator() : nil
)
.frame(maxWidth: contentWidth, alignment: messageViewModel.isRightAligned ? .trailing : .leading)
}
@ViewBuilder
private var avatarView: some View {
factory.makeUserAvatarView(
options: UserAvatarViewOptions(
user: message.author,
size: AvatarSize.medium,
showsIndicator: false
)
)
.opacity(isLast || showsAllInfo ? 1 : 0)
}
@ViewBuilder
private var threadRepliesView: some View {
if message.replyCount > 0 {
factory.makeMessageRepliesView(
options: MessageRepliesViewOptions(
channel: channel,
message: message,
replyCount: message.replyCount,
usesInvertedStyle: shownAsPreview
)
)
.accessibilityElement(children: .contain)
.accessibility(identifier: "MessageRepliesView")
} else if message.showReplyInChannel,
let parentId = message.parentMessageId,
let controller = utils.channelControllerFactory.currentChannelController,
let parentMessage = controller.dataStore.message(id: parentId) {
factory.makeMessageRepliesShownInChannelView(
options: MessageRepliesShownInChannelViewOptions(
channel: channel,
message: message,
parentMessage: parentMessage,
replyCount: parentMessage.replyCount,
usesInvertedStyle: shownAsPreview
)
)
.accessibilityElement(children: .contain)
.accessibility(identifier: "MessageRepliesView")
} else if message.showReplyInChannel, let parentId = message.parentMessageId {
LazyMessageRepliesView(
factory: factory,
channel: channel,
message: message,
parentMessageController: chatClient.messageController(
cid: channel.cid,
messageId: parentId
),
usesInvertedStyle: shownAsPreview
)
.accessibilityElement(children: .contain)
.accessibility(identifier: "MessageRepliesView")
}
}
@ViewBuilder
private var deliveryStatusView: some View {
// Spacings and coloring changes
if message.isSentByCurrentUser && channel.config.readEventsEnabled {
HStack(spacing: tokens.spacingXxs) {
factory.makeMessageReadIndicatorView(
options: MessageReadIndicatorViewOptions(
channel: channel,
message: message,
usesInvertedStyle: shownAsPreview
)
)
if messageViewModel.messageDateShown {
factory.makeMessageDateView(
options: MessageDateViewOptions(message: message, usesInvertedStyle: shownAsPreview)
)
}
}
.padding(.bottom, tokens.spacingXxs)
} else if messageViewModel.authorAndDateShown {
factory.makeMessageAuthorAndDateView(
options: MessageAuthorAndDateViewOptions(message: message, usesInvertedStyle: shownAsPreview)
)
.padding(.bottom, tokens.spacingXxs)
} else if messageViewModel.messageDateShown {
factory.makeMessageDateView(
options: MessageDateViewOptions(message: message, usesInvertedStyle: shownAsPreview)
)
.padding(.bottom, tokens.spacingXxs)
}
}
// MARK: - Computed Properties
// β¦
}
// MARK: - Swipe to Reply
private struct SwipeToReplyModifier: ViewModifier {
let message: ChatMessage
let channel: ChatChannel
let isSwipeToQuoteReplyPossible: Bool
@Binding var quotedMessage: ChatMessage?
@Injected(\.images) private var images
@Injected(\.utils) private var utils
@State private var offsetX: CGFloat = 0
@GestureState private var offset: CGSize = .zero
private let replyThreshold: CGFloat = 60
func body(content: Content) -> some View {
// Same as before, just moved
content
.offset(x: min(offsetX, maximumHorizontalSwipeDisplacement))
.simultaneousGesture(
DragGesture(
minimumDistance: minimumSwipeDistance,
coordinateSpace: .local
)
.updating($offset) { (value, gestureState, _) in
guard isSwipeToQuoteReplyPossible else {
return
}
let diff = CGSize(
width: value.location.x - value.startLocation.x,
height: value.location.y - value.startLocation.y
)
if diff == .zero {
gestureState = .zero
} else {
gestureState = value.translation
}
}
)
.onChange(of: offset, perform: { _ in
if !channel.config.quotesEnabled {
return
}
if offset == .zero {
setOffsetX(value: 0)
} else {
dragChanged(to: offset.width)
}
})
.overlay(
offsetX > 0 ?
TopLeftView {
Image(uiImage: images.messageActionInlineReply)
}
.offset(x: -32)
: nil
)
}
private var maximumHorizontalSwipeDisplacement: CGFloat {
replyThreshold + 30
}
private var minimumSwipeDistance: CGFloat {
utils.messageListConfig.messageDisplayOptions.minimumSwipeGestureDistance
}
private func dragChanged(to value: CGFloat) {
let horizontalTranslation = value
if horizontalTranslation < 0 {
return
}
if horizontalTranslation >= minimumSwipeDistance {
offsetX = horizontalTranslation
} else {
offsetX = 0
}
if offsetX > replyThreshold && quotedMessage != message {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
quotedMessage = message
}
}
}
private func setOffsetX(value: CGFloat) {
withAnimation(.interpolatingSpring(stiffness: 170, damping: 20)) {
offsetX = value
}
}
}
| factory: factory, | ||
| channel: channel, | ||
| message: message, | ||
| parentMessageController: chatClient.messageController( |
There was a problem hiding this comment.
I assume this will get re-created many times?
There was a problem hiding this comment.
Same as before, this has a TODO for fixing it in v5. Needs separate PR.
| 2 * availableWidth / 3 | ||
| } else { | ||
| availableWidth / 4 | ||
| 82 |
There was a problem hiding this comment.
this feels random?
There was a problem hiding this comment.
I will document this
| showDelivered: Bool = false, | ||
| localState: LocalMessageState? = nil | ||
| localState: LocalMessageState? = nil, | ||
| textColor: UIColor? = nil |
There was a problem hiding this comment.
why do we pass this text color everywhere? Can't we put it in the color palete?
There was a problem hiding this comment.
In message list this is one value, on reactions overlay this needs to be a different (lighter) color. Let me think if there is nicer way. Environment key could be an option here for communicating this without littering everything with extra color arguments.
There was a problem hiding this comment.
I'll change it around.
# Conflicts: # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelViewDateOverlay_Tests/test_chatChannelView_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelView_Tests/test_chatChannelView_liquidGlassStyle_composer_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelView_Tests/test_chatChannelView_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelView_Tests/test_chatChannelView_themedNavigationBar_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerCurrentUserColor_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerEditedAIGenerated_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerEdited_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerViewSentThisUser_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_translatedText_myMessageIsNotTranslated_snapshot.default-light.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_translatedText_myMessageIsNotTranslated_snapshot.extraExtraExtraLarge-light.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_translatedText_myMessageIsNotTranslated_snapshot.rightToLeftLayout-default.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_translatedText_myMessageIsNotTranslated_snapshot.small-dark.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListViewLastGroupHeader_Tests/test_messageListView_headerOnTop.1.png
# Conflicts: # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatQuotedMessageView_Tests/test_chatQuotedMessageView_withAttachment.default-light.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatQuotedMessageView_Tests/test_chatQuotedMessageView_withAttachment.extraExtraExtraLarge-light.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatQuotedMessageView_Tests/test_chatQuotedMessageView_withAttachment.rightToLeftLayout-default.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatQuotedMessageView_Tests/test_chatQuotedMessageView_withAttachment.small-dark.png
| @@ -123,6 +123,7 @@ struct LazyGiphyView: View { | |||
| .processors([ImageProcessors.Resize(width: width)]) | |||
| .priority(.high) | |||
| .aspectRatio(contentMode: .fit) | |||
| .frame(width: width) | |||
| } | |||
There was a problem hiding this comment.
Layout issue when it was shown in the reactions overlay.
nuno-vieira
left a comment
There was a problem hiding this comment.
Overall looks really good π I'm only requesting changes because the shownInMessageList: false goes against the new API design of the Quoted Message View. Other than that, all good
| options: QuotedMessageViewOptions( | ||
| quotedMessage: quotedMessage, | ||
| quotedByCurrentUser: true, | ||
| shownInMessageList: false, |
There was a problem hiding this comment.
We shouldn't be having this. The goal of ChatQuotedMessageView vs ComposerQuotedMessageView is to have the context of where is the child QuotedMessageView is being rendered.
There was a problem hiding this comment.
Slight refactor of ReferenceMessageView where there is now outgoing style parameter since the styling in composer uses quotedMessage.isSentByCurrentUser, but in chat quoted message view it is parent view's isSentByCurrentUser. This is needed for the vertical line in that view which needs different color.
Example, here I am quoting my own message and other participant's message. Styling needs to use the outgoing style.
| quotedMessage: quotedMessage | ||
| quotedMessage: quotedMessage, | ||
| quotedByCurrentUser: parentMessageSentByCurrentUser, | ||
| shownInMessageList: true |
There was a problem hiding this comment.
We should opt for either passing the background color or moving the ReferenceMessageViewBackgroundModifier to the upper views
| shownInMessageList: true | |
| backgroundColor: Color |
| let backgroundColor: Color | ||
|
|
||
| init(isSentByCurrentUser: Bool) { | ||
| self.isSentByCurrentUser = isSentByCurrentUser | ||
| init(backgroundColor: Color) { | ||
| self.backgroundColor = backgroundColor |
There was a problem hiding this comment.
Yup this makes sense. I initially wanted to do this, I was already guessing it would require changes since it was not very flexible the way it was before
# Conflicts: # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelViewDateOverlay_Tests/test_chatChannelView_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelView_Tests/test_chatChannelView_liquidGlassStyle_composer_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelView_Tests/test_chatChannelView_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelView_Tests/test_chatChannelView_themedNavigationBar_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_imageAttachments_failedWhenMessageTextIsEmpty_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_imageAttachments_failed_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerOtherUserColor_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerViewPinned_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerViewSentOtherUser_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_editingFailed_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_sendingFailed_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_translatedText_showOriginalTranslatedButtonDisabled_translatedTextShown_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_translatedText_showOriginalTranslatedButtonEnabled_originalTextShown_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_translatedText_showOriginalTranslatedButtonEnabled_translatedTextShown_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListViewLastGroupHeader_Tests/test_messageListView_headerOnTop.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_jumpToUnreadButton.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_typingIndicator.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_viewModelInit_noReactions.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_viewModelInit_unreadIndicator.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_viewModelInit_withReactions.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageListView_Tests/test_messageListView_withReactions.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageViewMultiRowReactions_Tests/test_messageViewMultiRowReactions_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageRepliesViewShownInChannel_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageRepliesView_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlayView_noReactions.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlayView_snapshot.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlayView_translated.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlayView_usersReactions.1.png # StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlay_veryLongMessage.1.png
Public Interface+ public struct MessageItemView: View
+
+ public var body: some View
+
+
+ public init(factory: Factory,channel: ChatChannel,message: ChatMessage,width: CGFloat? = nil,showsAllInfo: Bool,shownAsPreview: Bool = false,isInThread: Bool,isLast: Bool,scrolledId: Binding<String?>,quotedMessage: Binding<ChatMessage?>,onLongPress: @escaping (MessageDisplayInfo) -> Void,viewModel: MessageViewModel? = nil)
+ public final class MessageItemViewOptions: Sendable
+
+ public let channel: ChatChannel
+ public let message: ChatMessage
+ public let width: CGFloat?
+ public let showsAllInfo: Bool
+ public let shownAsPreview: Bool
+ public let isInThread: Bool
+ public let scrolledId: Binding<String?>
+ public let quotedMessage: Binding<ChatMessage?>
+ public let onLongPress: @MainActor (MessageDisplayInfo) -> Void
+ public let isLast: Bool
+ public let viewModel: MessageViewModel?
+
+
+ public init(channel: ChatChannel,message: ChatMessage,width: CGFloat?,showsAllInfo: Bool,shownAsPreview: Bool = false,isInThread: Bool,scrolledId: Binding<String?>,quotedMessage: Binding<ChatMessage?>,onLongPress: @escaping @MainActor (MessageDisplayInfo) -> Void,isLast: Bool,viewModel: MessageViewModel? = nil)
- public struct MessageContainerView: View
-
- public var body: some View
-
-
- public init(factory: Factory,channel: ChatChannel,message: ChatMessage,width: CGFloat? = nil,showsAllInfo: Bool,isInThread: Bool,isLast: Bool,scrolledId: Binding<String?>,quotedMessage: Binding<ChatMessage?>,onLongPress: @escaping (MessageDisplayInfo) -> Void,viewModel: MessageViewModel? = nil)
- public final class MessageContainerViewOptions: Sendable
-
- public let channel: ChatChannel
- public let message: ChatMessage
- public let width: CGFloat?
- public let showsAllInfo: Bool
- public let isInThread: Bool
- public let scrolledId: Binding<String?>
- public let quotedMessage: Binding<ChatMessage?>
- public let onLongPress: @MainActor (MessageDisplayInfo) -> Void
- public let isLast: Bool
-
-
- public init(channel: ChatChannel,message: ChatMessage,width: CGFloat?,showsAllInfo: Bool,isInThread: Bool,scrolledId: Binding<String?>,quotedMessage: Binding<ChatMessage?>,onLongPress: @escaping @MainActor (MessageDisplayInfo) -> Void,isLast: Bool)
public final class MessageRepliesShownInChannelViewOptions: Sendable
-
+ public let usesInvertedStyle: Bool
-
+
- public init(channel: ChatChannel,message: ChatMessage,parentMessage: ChatMessage,replyCount: Int)
+
+ public init(channel: ChatChannel,message: ChatMessage,parentMessage: ChatMessage,replyCount: Int,usesInvertedStyle: Bool = false)
public final class MessageDateViewOptions: Sendable
- public let textColor: Color?
+ public let usesInvertedStyle: Bool
- public init(message: ChatMessage,textColor: Color? = nil)
+ public init(message: ChatMessage,usesInvertedStyle: Bool = false)
@MainActor open class MessageViewModel: ObservableObject
- public var originalTextShown: Bool
+ @Published public var usesScrollView: Bool
- public var systemMessageShown: Bool
+ public var originalTextShown: Bool
- public var reactionsShown: Bool
+ public var systemMessageShown: Bool
- public var failureIndicatorShown: Bool
+ public var reactionsShown: Bool
- open var authorAndDateShown: Bool
+ public var failureIndicatorShown: Bool
- open var messageDateShown: Bool
+ open var authorAndDateShown: Bool
- public var isPinned: Bool
+ open var messageDateShown: Bool
- public var isRightAligned: Bool
+ public var isPinned: Bool
- public var messageAuthor: ChatUser?
+ public var isRightAligned: Bool
- open var isSwipeToQuoteReplyPossible: Bool
+ public var messageAuthor: ChatUser?
- open var textContent: String
+ public var isDoubleTapOverlayEnabled: Bool
- public var translatedText: String?
+ open var isSwipeToQuoteReplyPossible: Bool
- public var translatedLanguageText: String?
+ open var textContent: String
- public var originalTranslationButtonText: String
+ public var translatedText: String?
-
+ public var translatedLanguageText: String?
-
+ public var originalTranslationButtonText: String
- public init(message: ChatMessage,channel: ChatChannel?)
+
-
+
-
+ public init(message: ChatMessage,channel: ChatChannel?)
- public func showOriginalText()
+
- public func hideOriginalText()
+
+ public func showOriginalText()
+ public func hideOriginalText()
+ public func isHighlighted(messageId: String?)-> Bool
public final class QuotedMessageViewOptions: Sendable
- public let padding: EdgeInsets?
+ public let outgoing: Bool
-
+ public let padding: EdgeInsets?
-
+
- public init(quotedMessage: ChatMessage,padding: EdgeInsets? = nil)
+
+ public init(quotedMessage: ChatMessage,outgoing: Bool,padding: EdgeInsets? = nil)
public struct MessageTranslationFooterView: View
- public init(messageViewModel: MessageViewModel)
+ public init(messageViewModel: MessageViewModel,usesInvertedStyle: Bool = false)
public final class MessageTranslationFooterViewOptions: Sendable
-
+ public let usesInvertedStyle: Bool
-
+
- public init(messageViewModel: MessageViewModel)
+
+ public init(messageViewModel: MessageViewModel,usesInvertedStyle: Bool = false)
public struct ReferenceMessageView: View
- public let isSentByCurrentUser: Bool
+ public let outgoing: Bool
- public init(title: String,subtitle: String,isSentByCurrentUser: Bool,@ViewBuilder iconPreview: () -> IconPreview,@ViewBuilder attachmentPreview: () -> AttachmentPreview)
+ public init(title: String,subtitle: String,outgoing: Bool,@ViewBuilder iconPreview: () -> IconPreview,@ViewBuilder attachmentPreview: () -> AttachmentPreview)
public final class MessageDisplayOptions
- public let showAvatars: Bool
+ public let showIncomingMessageAvatar: Bool
- public let showAvatarsInGroups: Bool
+ public let showOutgoingMessageAvatar: Bool
- public let showMessageDate: Bool
+ public let showAvatarsInGroups: Bool
- public let showAuthorName: Bool
+ public let showMessageDate: Bool
- public let animateChanges: Bool
+ public let showAuthorName: Bool
- public let dateLabelSize: CGFloat
+ public let animateChanges: Bool
- public let lastInGroupHeaderSize: CGFloat
+ public let dateLabelSize: CGFloat
- public let newMessagesSeparatorSize: CGFloat
+ public let lastInGroupHeaderSize: CGFloat
- public let minimumSwipeGestureDistance: CGFloat
+ public let newMessagesSeparatorSize: CGFloat
- public let currentUserMessageTransition: AnyTransition
+ public let minimumSwipeGestureDistance: CGFloat
- public let otherUserMessageTransition: AnyTransition
+ public let currentUserMessageTransition: AnyTransition
- public let shouldAnimateReactions: Bool
+ public let otherUserMessageTransition: AnyTransition
- public let reactionsPlacement: ReactionsPlacement
+ public let shouldAnimateReactions: Bool
- public let reactionsStyle: ReactionsStyle
+ public let reactionsPlacement: ReactionsPlacement
- public let showOriginalTranslatedButton: Bool
+ public let reactionsStyle: ReactionsStyle
- public let messageLinkDisplayResolver: @MainActor (ChatMessage) -> [NSAttributedString.Key: Any]
+ public let showOriginalTranslatedButton: Bool
- public let spacerWidth: (CGFloat) -> CGFloat
+ public let messageLinkDisplayResolver: @MainActor (ChatMessage) -> [NSAttributedString.Key: Any]
- public let reactionsTopPadding: (ChatMessage) -> CGFloat
+ public let spacerWidth: @MainActor (CGFloat) -> CGFloat
- public let dateSeparator: (ChatMessage, ChatMessage) -> Date?
+ public let reactionsTopPadding: (ChatMessage) -> CGFloat
- public static var defaultLinkDisplay: @MainActor (ChatMessage) -> [NSAttributedString.Key: Any]
+ public let dateSeparator: (ChatMessage, ChatMessage) -> Date?
- public static var defaultSpacerWidth: (CGFloat) -> (CGFloat)
+ public static var defaultLinkDisplay: @MainActor (ChatMessage) -> [NSAttributedString.Key: Any]
- public static var defaultReactionsTopPadding: (ChatMessage) -> CGFloat
+ public static var defaultSpacerWidth: @MainActor (CGFloat) -> (CGFloat)
-
+ public static var defaultReactionsTopPadding: (ChatMessage) -> CGFloat
-
+
- public init(showAvatars: Bool = true,showAvatarsInGroups: Bool? = nil,showMessageDate: Bool = true,showAuthorName: Bool = true,animateChanges: Bool = true,overlayDateLabelSize: CGFloat = 40,lastInGroupHeaderSize: CGFloat = 0,newMessagesSeparatorSize: CGFloat = 50,minimumSwipeGestureDistance: CGFloat = 20,currentUserMessageTransition: AnyTransition = .identity,otherUserMessageTransition: AnyTransition = .identity,shouldAnimateReactions: Bool = true,reactionsPlacement: ReactionsPlacement = .top,reactionsStyle: ReactionsStyle = .segmented,showOriginalTranslatedButton: Bool = false,messageLinkDisplayResolver: @escaping @MainActor (ChatMessage) -> [NSAttributedString.Key: Any] = MessageDisplayOptions
+
- .defaultLinkDisplay,spacerWidth: @escaping (CGFloat) -> CGFloat = MessageDisplayOptions.defaultSpacerWidth,reactionsTopPadding: @escaping (ChatMessage) -> CGFloat = MessageDisplayOptions.defaultReactionsTopPadding,dateSeparator: @escaping (ChatMessage, ChatMessage) -> Date? = MessageDisplayOptions.defaultDateSeparator)
+ public init(showIncomingMessageAvatar: Bool = true,showOutgoingMessageAvatar: Bool = false,showAvatarsInGroups: Bool = true,showMessageDate: Bool = true,showAuthorName: Bool = true,animateChanges: Bool = true,overlayDateLabelSize: CGFloat = 40,lastInGroupHeaderSize: CGFloat = 0,newMessagesSeparatorSize: CGFloat = 50,minimumSwipeGestureDistance: CGFloat = 20,currentUserMessageTransition: AnyTransition = .identity,otherUserMessageTransition: AnyTransition = .identity,shouldAnimateReactions: Bool = true,reactionsPlacement: ReactionsPlacement = .top,reactionsStyle: ReactionsStyle = .segmented,showOriginalTranslatedButton: Bool = false,messageLinkDisplayResolver: @escaping @MainActor (ChatMessage) -> [NSAttributedString.Key: Any] = MessageDisplayOptions
-
+ .defaultLinkDisplay,spacerWidth: @escaping @MainActor (CGFloat) -> CGFloat = MessageDisplayOptions.defaultSpacerWidth,reactionsTopPadding: @escaping (ChatMessage) -> CGFloat = MessageDisplayOptions.defaultReactionsTopPadding,dateSeparator: @escaping (ChatMessage, ChatMessage) -> Date? = MessageDisplayOptions.defaultDateSeparator)
-
+
- public func showAvatars(for channel: ChatChannel)-> Bool
+
- public static func defaultDateSeparator(message: ChatMessage,previous: ChatMessage)-> Date?
+ public func showAvatars(for channel: ChatChannel,incoming: Bool)-> Bool
+ public static func defaultDateSeparator(message: ChatMessage,previous: ChatMessage)-> Date?
@MainActor open class QuotedMessageViewModel
- open var title: String
+ public let outgoing: Bool
- open var authorName: String
+ open var title: String
- open var isSentByCurrentUser: Bool
+ open var authorName: String
- public init(message: ChatMessage,currentUser: CurrentChatUser?)
+ public init(message: ChatMessage,currentUser: CurrentChatUser?,outgoing: Bool)
public struct MessageRepliesView: View
- public init(factory: Factory,channel: ChatChannel,message: ChatMessage,replyCount: Int,showReplyCount: Bool = true,isRightAligned: Bool? = nil,threadReplyMessage: ChatMessage? = nil)
+ public init(factory: Factory,channel: ChatChannel,message: ChatMessage,replyCount: Int,showReplyCount: Bool = true,isRightAligned: Bool? = nil,usesInvertedStyle: Bool = false,threadReplyMessage: ChatMessage? = nil)
extension ViewFactory
- public func makeMessageContainerView(options: MessageContainerViewOptions)-> some View
+ public func makeMessageItemView(options: MessageItemViewOptions)-> some View
public struct ReactionsOverlayView: View
- public init(factory: Factory,channel: ChatChannel,currentSnapshot: UIImage,messageDisplayInfo: MessageDisplayInfo,topOffset: CGFloat = 0,bottomOffset: CGFloat = 0,onBackgroundTap: @escaping () -> Void,onActionExecuted: @escaping (MessageActionInfo) -> Void,viewModel: ReactionsOverlayViewModel? = nil,messageViewModel: MessageViewModel? = nil)
+ public init(factory: Factory,channel: ChatChannel,currentSnapshot: UIImage,messageDisplayInfo: MessageDisplayInfo,verticalInset: CGFloat = 40,onBackgroundTap: @escaping () -> Void,onActionExecuted: @escaping (MessageActionInfo) -> Void,viewModel: ReactionsOverlayViewModel? = nil,messageViewModel: MessageViewModel? = nil)
public final class MessageReadIndicatorViewOptions: Sendable
-
+ public let usesInvertedStyle: Bool
-
+
- public init(channel: ChatChannel,message: ChatMessage)
+
+ public init(channel: ChatChannel,message: ChatMessage,usesInvertedStyle: Bool = false)
public final class ChatQuotedMessageViewOptions: Sendable
- public let scrolledId: Binding<String?>
+ public let parentMessage: ChatMessage
-
+ public let scrolledId: Binding<String?>
-
+
- public init(quotedMessage: ChatMessage,scrolledId: Binding<String?>)
+
+ public init(quotedMessage: ChatMessage,parentMessage: ChatMessage,scrolledId: Binding<String?>)
public final class MessagePaddings
- public init(horizontal: CGFloat = 8,quotedViewPadding: CGFloat = 8,singleBottom: CGFloat = 8,groupBottom: CGFloat = 2)
+ public init(horizontal: CGFloat = 16,quotedViewPadding: CGFloat = 16,singleBottom: CGFloat = 8,groupBottom: CGFloat = 4)
public struct MessageReadIndicatorView: View
- public init(readUsers: [ChatUser],showReadCount: Bool,showDelivered: Bool = false,localState: LocalMessageState? = nil)
+ public init(readUsers: [ChatUser],showReadCount: Bool,showDelivered: Bool = false,localState: LocalMessageState? = nil,usesInvertedStyle: Bool = false)
public final class MessageRepliesViewOptions: Sendable
-
+ public let usesInvertedStyle: Bool
-
+
- public init(channel: ChatChannel,message: ChatMessage,replyCount: Int)
+
+ public init(channel: ChatChannel,message: ChatMessage,replyCount: Int,usesInvertedStyle: Bool = false)
public struct MessageAuthorAndDateView: View
- public init(message: ChatMessage)
+ public init(message: ChatMessage,usesInvertedStyle: Bool = false)
public struct ChatQuotedMessageView: View
- public init(factory: Factory,quotedMessage: ChatMessage,scrolledId: Binding<String?>)
+ public init(factory: Factory,quotedMessage: ChatMessage,parentMessage: ChatMessage,scrolledId: Binding<String?>)
public final class MessageAuthorAndDateViewOptions: Sendable
-
+ public let usesInvertedStyle: Bool
-
+
- public init(message: ChatMessage)
+
+ public init(message: ChatMessage,usesInvertedStyle: Bool = false)
public struct MessageAuthorView: View
- public init(message: ChatMessage)
+ public init(message: ChatMessage,usesInvertedStyle: Bool = false) |
SDK Size
|
StreamChatSwiftUI XCSize
Show 23 more objects
|
|


π Issue Links
Related: IOS-1379
π― Goal
Extract the core message rendering logic into a new
MessageItemViewand fix the layout to match the Figma design system.π Summary
MessageContainerViewtoMessageItemView; extracted swipe-to-reply into aSwipeToReplyModifierand split layout into focused sub-views (messageBubbleContent,avatarView,threadRepliesView,deliveryStatusView)shownAsPreviewmode that propagates apreviewTextColor(colors.textOnAccent) to thread replies, delivery status, and translation footer viewsReactionsOverlayViewnow delegates message rendering tomakeMessageItemView(shownAsPreview: true), removing ~80 lines of duplicated layout codeMessageSpacer; alignment handled via VStack alignment and.frame(maxWidth:alignment:)MessageRepliesViewthread connector path to use Figma-exported SVG bezier curveQuotedMessageViewandChatQuotedMessageViewso incoming/outgoing style is driven by the parent message (quotedByCurrentUser,shownInMessageList) instead of the quoted messageReferenceMessageViewBackgroundModifiernow takes abackgroundColor: Colordirectly instead of computing it fromisSentByCurrentUsertextColor: UIColor?parameter toMessageAuthorView,MessageReadIndicatorView,MessageTranslationFooterView,MessageRepliesView, and their corresponding factory optionsMessageDateView.textColortype fromColor?toUIColor?for consistency with other viewsavailableWidth/4β82, reactions top padding 24 β 20MessageContainerViewOptionstoMessageItemViewOptions; addedπ§ͺ Manual Testing Notes
N/A
βοΈ Contributor Checklist
docs-contentrepo