Skip to content

Commit

Permalink
Add message preview with attachments in channel list
Browse files Browse the repository at this point in the history
  • Loading branch information
martinmitrevski committed Oct 13, 2023
1 parent 5c84fb1 commit de3a3f0
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 3 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

# Upcoming

### 🔄 Changed
### ✅ Added
- Add message preview with attachments in channel list

# [4.39.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.39.0)
_October 06, 2023_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ extension ChatChannel {

public var lastMessageText: String? {
if let latestMessage = latestMessages.first {
return "\(latestMessage.author.name ?? latestMessage.author.id): \(latestMessage.textContent ?? latestMessage.adjustedText)"
return "\(latestMessage.author.name ?? latestMessage.author.id): \(textContent(for: latestMessage))"
} else {
return nil
}
Expand Down Expand Up @@ -250,4 +250,45 @@ extension ChatChannel {
return ""
}
}

private func textContent(for previewMessage: ChatMessage) -> String {
if let attachmentPreviewText = attachmentPreviewText(for: previewMessage) {
return attachmentPreviewText
}
if let textContent = previewMessage.textContent, !textContent.isEmpty {
return textContent
}
return previewMessage.adjustedText
}

/// The message preview text in case it contains attachments.
/// - Parameter previewMessage: The preview message of the channel.
/// - Returns: A string representing the message preview text.
private func attachmentPreviewText(for previewMessage: ChatMessage) -> String? {
guard let attachment = previewMessage.allAttachments.first else {
return nil
}
let text = previewMessage.textContent ?? previewMessage.text
switch attachment.type {
case .audio:
let defaultAudioText = L10n.Channel.Item.audio
return "🎧 \(text.isEmpty ? defaultAudioText : text)"
case .file:
guard let fileAttachment = previewMessage.fileAttachments.first else {
return nil
}
let title = fileAttachment.payload.title
return "📄 \(title ?? text)"
case .image:
let defaultPhotoText = L10n.Channel.Item.photo
return "📷 \(text.isEmpty ? defaultPhotoText : text)"
case .video:
let defaultVideoText = L10n.Channel.Item.video
return "📹 \(text.isEmpty ? defaultVideoText : text)"
case .giphy:
return "/giphy"
default:
return nil
}
}
}
6 changes: 6 additions & 0 deletions Sources/StreamChatSwiftUI/Generated/L10n.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,24 @@ internal enum L10n {

internal enum Channel {
internal enum Item {
/// Audio
internal static var audio: String { L10n.tr("Localizable", "channel.item.audio") }
/// No messages
internal static var emptyMessages: String { L10n.tr("Localizable", "channel.item.empty-messages") }
/// Mute
internal static var mute: String { L10n.tr("Localizable", "channel.item.mute") }
/// Channel is muted
internal static var muted: String { L10n.tr("Localizable", "channel.item.muted") }
/// Photo
internal static var photo: String { L10n.tr("Localizable", "channel.item.photo") }
/// are typing ...
internal static var typingPlural: String { L10n.tr("Localizable", "channel.item.typing-plural") }
/// is typing ...
internal static var typingSingular: String { L10n.tr("Localizable", "channel.item.typing-singular") }
/// Unmute
internal static var unmute: String { L10n.tr("Localizable", "channel.item.unmute") }
/// Video
internal static var video: String { L10n.tr("Localizable", "channel.item.video") }
}
internal enum Name {
/// and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,7 @@

"chat-info.rename.name" = "NAME";
"chat-info.rename.placeholder" = "Add a group name";

"channel.item.audio" = "Audio";
"channel.item.photo" = "Photo";
"channel.item.video" = "Video";
4 changes: 4 additions & 0 deletions StreamChatSwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
8492974B27ABDDCB00A8EEB0 /* NotificationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8492974827ABDDBF00A8EEB0 /* NotificationsHandler.swift */; };
8492975227B156D100A8EEB0 /* SlowModeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8492975127B156D000A8EEB0 /* SlowModeView.swift */; };
8492975427B1725B00A8EEB0 /* MessageComposerView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8492975327B1725B00A8EEB0 /* MessageComposerView_Tests.swift */; };
849894952AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849894942AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift */; };
849CDD942768E0E1003C7A51 /* MessageActionsResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849CDD932768E0E1003C7A51 /* MessageActionsResolver.swift */; };
849FD5112811B05C00952934 /* ChatInfoParticipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849FD5102811B05C00952934 /* ChatInfoParticipantsView.swift */; };
84A1CACD2816BC420046595A /* ChatChannelInfoHelperViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1CACC2816BC420046595A /* ChatChannelInfoHelperViews.swift */; };
Expand Down Expand Up @@ -636,6 +637,7 @@
8492974827ABDDBF00A8EEB0 /* NotificationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsHandler.swift; sourceTree = "<group>"; };
8492975127B156D000A8EEB0 /* SlowModeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlowModeView.swift; sourceTree = "<group>"; };
8492975327B1725B00A8EEB0 /* MessageComposerView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerView_Tests.swift; sourceTree = "<group>"; };
849894942AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelListItemView_Tests.swift; sourceTree = "<group>"; };
849CDD932768E0E1003C7A51 /* MessageActionsResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsResolver.swift; sourceTree = "<group>"; };
849FD5102811B05C00952934 /* ChatInfoParticipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoParticipantsView.swift; sourceTree = "<group>"; };
84A1CACC2816BC420046595A /* ChatChannelInfoHelperViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelInfoHelperViews.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1332,6 +1334,7 @@
84DEC8DC2760A10500172876 /* NoChannelsView_Tests.swift */,
8413D90127A9654600A89432 /* SearchResultsView_Tests.swift */,
84D6B55927DF6EC7009C6D07 /* LoadingView_Tests.swift */,
849894942AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift */,
);
path = ChatChannelList;
sourceTree = "<group>";
Expand Down Expand Up @@ -2044,6 +2047,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
849894952AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift in Sources */,
84E04791284A444E00BAFA17 /* MockNetworkURLProtocol.swift in Sources */,
848399EC275FB41B003075E4 /* ChatChannelListView_Tests.swift in Sources */,
84C94D54275A1380007FE2B9 /* DateUtils_Tests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
//
// Copyright © 2023 Stream.io Inc. All rights reserved.
//

import SnapshotTesting
@testable import StreamChat
@testable import StreamChatSwiftUI
@testable import StreamChatTestTools
import XCTest

final class ChatChannelListItemView_Tests: StreamChatTestCase {

func test_channelListItem_audioMessage() throws {
// Given
let message = try mockAudioMessage(text: "Audio", isSentByCurrentUser: true)
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])

// When
let view = ChatChannelListItem(
channel: channel,
channelName: "Test",
avatar: .circleImage,
onlineIndicatorShown: true,
onItemTap: { _ in }
)
.frame(width: defaultScreenSize.width)

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

func test_channelListItem_imageMessage() throws {
// Given
let message = try mockImageMessage(text: "Image", isSentByCurrentUser: true)
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])

// When
let view = ChatChannelListItem(
channel: channel,
channelName: "Test",
avatar: .circleImage,
onlineIndicatorShown: true,
onItemTap: { _ in }
)
.frame(width: defaultScreenSize.width)

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

func test_channelListItem_videoMessage() throws {
// Given
let message = try mockVideoMessage(text: "Video", isSentByCurrentUser: true)
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])

// When
let view = ChatChannelListItem(
channel: channel,
channelName: "Test",
avatar: .circleImage,
onlineIndicatorShown: true,
onItemTap: { _ in }
)
.frame(width: defaultScreenSize.width)

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

func test_channelListItem_fileMessage() throws {
// Given
let message = try mockFileMessage(title: "Filename", text: "File", isSentByCurrentUser: true)
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])

// When
let view = ChatChannelListItem(
channel: channel,
channelName: "Test",
avatar: .circleImage,
onlineIndicatorShown: true,
onItemTap: { _ in }
)
.frame(width: defaultScreenSize.width)

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

func test_channelListItem_giphyMessage() throws {
// Given
let message = try mockGiphyMessage(text: "Giphy", isSentByCurrentUser: true)
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])

// When
let view = ChatChannelListItem(
channel: channel,
channelName: "Test",
avatar: .circleImage,
onlineIndicatorShown: true,
onItemTap: { _ in }
)
.frame(width: defaultScreenSize.width)

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

//MARK: - private

private func mockAudioMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
.mock(
id: .unique,
cid: .unique,
text: text,
type: .regular,
author: .mock(id: "user", name: "User"),
createdAt: Date(timeIntervalSince1970: 100),
attachments: [
.dummy(
type: .audio,
payload: try JSONEncoder().encode(AudioAttachmentPayload(
title: "Some Audio",
audioRemoteURL: URL(string: "url")!,
file: .init(type: .mp3, size: 123, mimeType: nil),
extraData: nil
))
)
],
localState: nil,
isSentByCurrentUser: isSentByCurrentUser
)
}

private func mockImageMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
.mock(
id: .unique,
cid: .unique,
text: text,
type: .regular,
author: .mock(id: "user", name: "User"),
createdAt: Date(timeIntervalSince1970: 100),
attachments: [
.dummy(
type: .image,
payload: try JSONEncoder().encode(ImageAttachmentPayload(
title: "Test",
imageRemoteURL: URL(string: "Url")!
))
)
],
localState: nil,
isSentByCurrentUser: isSentByCurrentUser
)
}

private func mockVideoMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
.mock(
id: .unique,
cid: .unique,
text: text,
type: .regular,
author: .mock(id: "user", name: "User"),
createdAt: Date(timeIntervalSince1970: 100),
attachments: [
.dummy(
type: .video,
payload: try JSONEncoder().encode(VideoAttachmentPayload(
title: "Test",
videoRemoteURL: URL(string: "Url")!,
file: .init(type: .mp4, size: 123, mimeType: nil),
extraData: nil
))
)
],
localState: nil,
isSentByCurrentUser: isSentByCurrentUser
)
}

private func mockFileMessage(title: String?, text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
.mock(
id: .unique,
cid: .unique,
text: text,
type: .regular,
author: .mock(id: "user", name: "User"),
createdAt: Date(timeIntervalSince1970: 100),
attachments: [
.dummy(
type: .file,
payload: try JSONEncoder().encode(FileAttachmentPayload(
title: title,
assetRemoteURL: URL(string: "Url")!,
file: .init(type: .pdf, size: 123, mimeType: nil),
extraData: nil
))
)
],
localState: nil,
isSentByCurrentUser: isSentByCurrentUser
)
}

private func mockGiphyMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
.mock(
id: .unique,
cid: .unique,
text: text,
type: .regular,
author: .mock(id: "user", name: "User"),
createdAt: Date(timeIntervalSince1970: 100),
attachments: [
.dummy(
type: .giphy,
payload: try JSONEncoder().encode(GiphyAttachmentPayload(
title: "Test",
previewURL: URL(string: "Url")!
))
)
],
localState: nil,
isSentByCurrentUser: isSentByCurrentUser
)
}
}
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.
2 changes: 1 addition & 1 deletion StreamChatSwiftUITests/Tests/StreamChatTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import XCTest
open class StreamChatTestCase: XCTestCase {

public static var currentUserId: String = .unique
public let snapshotPrecision: Float = 0.97
public let snapshotPrecision: Float = 0.95

public var chatClient: ChatClient = {
let client = ChatClient.mock(isLocalStorageEnabled: false)
Expand Down

0 comments on commit de3a3f0

Please sign in to comment.