Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Use fixed width for attachment previews [#1335](https://github.com/GetStream/stream-chat-swiftui/pull/1335)
- Fix showing bubble for quoted message and file or image attachment [#1335](https://github.com/GetStream/stream-chat-swiftui/pull/1335)
- Fix scaling of giphy attachments [#1335](https://github.com/GetStream/stream-chat-swiftui/pull/1335)
- Fix spacings in message annotations [#1403](https://github.com/GetStream/stream-chat-swiftui/pull/1403)

### 🔄 Changed
- Renamed the `onMessageSent` callback to `willSendMessage` in `MessageComposerViewModel`, `ViewModelsFactory`, and `ComposerViewFactoryOptions` [#1327](https://github.com/GetStream/stream-chat-swiftui/pull/1327)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ public struct MessageAnnotationView: View {
HStack(spacing: tokens.spacingXxs) {
Image(uiImage: icon)
.renderingMode(.template)
.scaledToFit()
.frame(width: tokens.iconSizeSm, height: tokens.iconSizeSm)
.padding(.horizontal, tokens.spacingXxxs)
.accessibilityHidden(true)
if let title {
Text(title)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@
}
}
.frame(maxWidth: .infinity, alignment: messageViewModel.isRightAligned ? .trailing : .leading)
.padding(.top, messageViewModel.topReactionsShown && !messageViewModel.isPinned ? messageListConfig.messageDisplayOptions.reactionsTopPadding(message) : 0)
.padding(.top, messageViewModel.topReactionsShown && !messageViewModel.annotationsShown ? messageListConfig.messageDisplayOptions.reactionsTopPadding(message) : 0)
.padding(.horizontal, messageListConfig.messagePaddings.horizontal)
.padding(.bottom, showsAllInfo || messageViewModel.annotationsShown ? paddingValue : groupMessageInterItemSpacing)
.padding(.top, isLast ? paddingValue : 0)
.padding(.top, isLast ? paddingValue : (messageViewModel.annotationsShown ? groupMessageInterItemSpacing : 0))

Check warning on line 98 in Sources/StreamChatSwiftUI/ChatMessageList/MessageContainerView.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract the nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-chat-swiftui&issues=AZ12o4F1rNumCMcWZTly&open=AZ12o4F1rNumCMcWZTly&pullRequest=1403
}

// MARK: - Sub-views
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public final class MessageDisplayOptions {
}

public static var defaultReactionsTopPadding: (ChatMessage) -> CGFloat {
{ _ in 20 }
{ _ in 19 }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine tuning to get the same spacing what Figma uses between reactions and annotations

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct MessageTopView: View {
var usesInvertedStyle: Bool = false

var body: some View {
VStack(alignment: messageViewModel.isRightAligned ? .trailing : .leading, spacing: tokens.spacingXxs) {
VStack(alignment: messageViewModel.isRightAligned ? .trailing : .leading, spacing: tokens.spacingXs) {
if messageViewModel.isPinned {
MessageAnnotationView(
icon: images.pin,
Expand Down
131 changes: 131 additions & 0 deletions StreamChatSwiftUITests/Tests/ChatChannel/MessageListView_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,137 @@ import XCTest
AssertSnapshot(view)
}

// MARK: - Grouped Messages with Annotations

func test_messageListView_groupedMessages_withThreadReplyAnnotation() {
// Given
let channel = ChatChannel.mockNonDMChannel()
let author = ChatUser.mock(id: "martin", name: "Martin")
let baseTime = Date(timeIntervalSince1970: 1000)
let msg1 = ChatMessage.mock(
id: "msg1",
cid: channel.cid,
text: "Let me check the latest updates.",
author: author,
createdAt: baseTime.addingTimeInterval(-20)
)
let msg2 = ChatMessage.mock(
id: "msg2",
cid: channel.cid,
text: "I found the issue in the logs.",
author: author,
createdAt: baseTime.addingTimeInterval(-10),
parentMessageId: .unique,
showReplyInChannel: true
)
let msg3 = ChatMessage.mock(
id: "msg3",
cid: channel.cid,
text: "Looks like it was a timeout.",
author: author,
createdAt: baseTime
)
let messages = [msg3, msg2, msg1]
let messagesGroupingInfo: [String: [String]] = [
msg3.id: [firstMessageKey],
msg1.id: [lastMessageKey]
]
let view = MessageListView(
factory: DefaultViewFactory.shared,
channel: channel,
messages: messages,
messagesGroupingInfo: messagesGroupingInfo,
scrolledId: .constant(nil),
showScrollToLatestButton: .constant(false),
quotedMessage: .constant(nil),
currentDateString: nil,
listId: "listId",
isMessageThread: false,
shouldShowTypingIndicator: false,
onMessageAppear: { _, _ in },
onScrollToBottom: {},
onLongPress: { _ in }
)
.applyDefaultSize()

// Then
assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
}

func test_messageListView_groupedMessages_withAllAnnotations() {
// Given
streamChat = StreamChat(chatClient: chatClient, utils: Utils(
messageListConfig: .init(messageDisplayOptions: MessageDisplayOptions(showOriginalTranslatedButton: true))
))

let channel = ChatChannel.mock(
cid: .unique,
config: .mock(messageRemindersEnabled: true),
membership: .mock(id: .unique, language: .spanish)
)
let author = ChatUser.mock(id: "martin", name: "Martin")
let baseTime = Date(timeIntervalSince1970: 1000)
let msg1 = ChatMessage.mock(
id: "msg1",
cid: channel.cid,
text: "Let me check the latest updates.",
author: author,
createdAt: baseTime.addingTimeInterval(-20)
)
let msg2 = ChatMessage.mock(
id: "msg2",
cid: channel.cid,
text: "I found the issue in the logs.",
author: author,
createdAt: baseTime.addingTimeInterval(-10),
parentMessageId: .unique,
showReplyInChannel: true,
translations: [.spanish: "Encontré el problema en los registros."],
pinDetails: MessagePinDetails(
pinnedAt: Date(),
pinnedBy: .mock(id: .unique, name: "Martin"),
expiresAt: nil
),
reminder: MessageReminderInfo(
remindAt: Date().addingTimeInterval(3600),
createdAt: Date(),
updatedAt: Date()
)
)
let msg3 = ChatMessage.mock(
id: "msg3",
cid: channel.cid,
text: "Looks like it was a timeout.",
author: author,
createdAt: baseTime
)
let messages = [msg3, msg2, msg1]
let messagesGroupingInfo: [String: [String]] = [
msg3.id: [firstMessageKey],
msg1.id: [lastMessageKey]
]
let view = MessageListView(
factory: DefaultViewFactory.shared,
channel: channel,
messages: messages,
messagesGroupingInfo: messagesGroupingInfo,
scrolledId: .constant(nil),
showScrollToLatestButton: .constant(false),
quotedMessage: .constant(nil),
currentDateString: nil,
listId: "listId",
isMessageThread: false,
shouldShowTypingIndicator: false,
onMessageAppear: { _, _ in },
onScrollToBottom: {},
onLongPress: { _ in }
)
.applyDefaultSize()

// Then
assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
}

// MARK: - private

func makeMessageListView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ import XCTest
)
)

let view = makeAnnotationsView(message: message)
let size = CGSize(width: 375, height: 72)
let view = makeAnnotationsView(message: message, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 40))
AssertSnapshot(view, size: size)
}

func test_pinnedByYouAnnotation_snapshot() {
Expand All @@ -45,9 +46,10 @@ import XCTest
)
)

let view = makeAnnotationsView(message: message)
let size = CGSize(width: 375, height: 72)
let view = makeAnnotationsView(message: message, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 40))
AssertSnapshot(view, size: size)
}

// MARK: - Sent in channel
Expand All @@ -62,9 +64,10 @@ import XCTest
showReplyInChannel: true
)

let view = makeAnnotationsView(message: message, isInThread: true)
let size = CGSize(width: 375, height: 72)
let view = makeAnnotationsView(message: message, isInThread: true, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 40))
AssertSnapshot(view, size: size)
}

// MARK: - Replied to thread
Expand All @@ -79,17 +82,19 @@ import XCTest
showReplyInChannel: true
)

let view = makeAnnotationsView(message: message, isInThread: false)
let size = CGSize(width: 375, height: 72)
let view = makeAnnotationsView(message: message, isInThread: false, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 40))
AssertSnapshot(view, size: size)
}

// MARK: - Reminder

func test_reminderAnnotation_snapshot() {
let channel = ChatChannel.mockDMChannel(config: .mock(messageRemindersEnabled: true))
let message = ChatMessage.mock(
id: .unique,
cid: .unique,
cid: channel.cid,
text: "Message with reminder",
author: .mock(id: .unique),
reminder: MessageReminderInfo(
Expand All @@ -99,9 +104,10 @@ import XCTest
)
)

let view = makeAnnotationsView(message: message)
let size = CGSize(width: 375, height: 72)
let view = makeAnnotationsView(message: message, channel: channel, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 40))
AssertSnapshot(view, size: size)
}

// MARK: - Translated
Expand All @@ -124,9 +130,10 @@ import XCTest
translations: [.spanish: "Hola"]
)

let view = makeAnnotationsView(message: message, channel: channel)
let size = CGSize(width: 375, height: 72)
let view = makeAnnotationsView(message: message, channel: channel, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 40))
AssertSnapshot(view, size: size)
}

// MARK: - All annotations (not in thread)
Expand All @@ -138,6 +145,7 @@ import XCTest

let channel = ChatChannel.mock(
cid: .unique,
config: .mock(messageRemindersEnabled: true),
membership: .mock(id: .unique, language: .spanish)
)

Expand All @@ -161,9 +169,10 @@ import XCTest
)
)

let view = makeAnnotationsView(message: message, channel: channel, isInThread: false)
let size = CGSize(width: 375, height: 100)
let view = makeAnnotationsView(message: message, channel: channel, isInThread: false, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 140))
AssertSnapshot(view, size: size)
}

// MARK: - All annotations (in thread)
Expand All @@ -175,6 +184,7 @@ import XCTest

let channel = ChatChannel.mock(
cid: .unique,
config: .mock(messageRemindersEnabled: true),
membership: .mock(id: .unique, language: .spanish)
)

Expand All @@ -198,17 +208,19 @@ import XCTest
)
)

let view = makeAnnotationsView(message: message, channel: channel, isInThread: true)
let size = CGSize(width: 375, height: 100)
let view = makeAnnotationsView(message: message, channel: channel, isInThread: true, size: size)

AssertSnapshot(view, size: CGSize(width: 375, height: 140))
AssertSnapshot(view, size: size)
}

// MARK: - Helpers

private func makeAnnotationsView(
message: ChatMessage,
channel: ChatChannel? = nil,
isInThread: Bool = false
isInThread: Bool = false,
size: CGSize
) -> some View {
let ch = channel ?? .mockDMChannel()
let viewModel = MessageViewModel(message: message, channel: ch, isInThread: isInThread)
Expand All @@ -217,6 +229,6 @@ import XCTest
channel: ch,
messageViewModel: viewModel
)
.frame(width: 375)
.applySize(size)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,21 @@ lane :install_runtime do |options|
install_ios_runtime(version: options[:ios], custom_script: 'Scripts/install_ios_runtime.sh')
end

desc 'Record UI Snapshots locally'
lane :record_snapshots_locally do
Comment on lines +397 to +398
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convenience for me since quite often I just wanna run them all

remove_snapshots

scan(
project: xcode_project,
scheme: 'StreamChatSwiftUI',
testplan: 'StreamChatSwiftUI',
configuration: 'Debug',
devices: ['iPhone 17 Pro (26.2)'],
fail_build: false,
suppress_xcode_output: true
)
end

desc 'Remove UI Snapshots'
lane :remove_snapshots do |options|
snapshots_path = "../StreamChatSwiftUITests/**/__Snapshots__/**/*.png"
Expand Down
Loading