Skip to content

Commit de3a3f0

Browse files
Add message preview with attachments in channel list
1 parent 5c84fb1 commit de3a3f0

File tree

12 files changed

+284
-3
lines changed

12 files changed

+284
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
33

44
# Upcoming
55

6-
### 🔄 Changed
6+
### ✅ Added
7+
- Add message preview with attachments in channel list
78

89
# [4.39.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.39.0)
910
_October 06, 2023_

Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ extension ChatChannel {
219219

220220
public var lastMessageText: String? {
221221
if let latestMessage = latestMessages.first {
222-
return "\(latestMessage.author.name ?? latestMessage.author.id): \(latestMessage.textContent ?? latestMessage.adjustedText)"
222+
return "\(latestMessage.author.name ?? latestMessage.author.id): \(textContent(for: latestMessage))"
223223
} else {
224224
return nil
225225
}
@@ -250,4 +250,45 @@ extension ChatChannel {
250250
return ""
251251
}
252252
}
253+
254+
private func textContent(for previewMessage: ChatMessage) -> String {
255+
if let attachmentPreviewText = attachmentPreviewText(for: previewMessage) {
256+
return attachmentPreviewText
257+
}
258+
if let textContent = previewMessage.textContent, !textContent.isEmpty {
259+
return textContent
260+
}
261+
return previewMessage.adjustedText
262+
}
263+
264+
/// The message preview text in case it contains attachments.
265+
/// - Parameter previewMessage: The preview message of the channel.
266+
/// - Returns: A string representing the message preview text.
267+
private func attachmentPreviewText(for previewMessage: ChatMessage) -> String? {
268+
guard let attachment = previewMessage.allAttachments.first else {
269+
return nil
270+
}
271+
let text = previewMessage.textContent ?? previewMessage.text
272+
switch attachment.type {
273+
case .audio:
274+
let defaultAudioText = L10n.Channel.Item.audio
275+
return "🎧 \(text.isEmpty ? defaultAudioText : text)"
276+
case .file:
277+
guard let fileAttachment = previewMessage.fileAttachments.first else {
278+
return nil
279+
}
280+
let title = fileAttachment.payload.title
281+
return "📄 \(title ?? text)"
282+
case .image:
283+
let defaultPhotoText = L10n.Channel.Item.photo
284+
return "📷 \(text.isEmpty ? defaultPhotoText : text)"
285+
case .video:
286+
let defaultVideoText = L10n.Channel.Item.video
287+
return "📹 \(text.isEmpty ? defaultVideoText : text)"
288+
case .giphy:
289+
return "/giphy"
290+
default:
291+
return nil
292+
}
293+
}
253294
}

Sources/StreamChatSwiftUI/Generated/L10n.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,24 @@ internal enum L10n {
5757

5858
internal enum Channel {
5959
internal enum Item {
60+
/// Audio
61+
internal static var audio: String { L10n.tr("Localizable", "channel.item.audio") }
6062
/// No messages
6163
internal static var emptyMessages: String { L10n.tr("Localizable", "channel.item.empty-messages") }
6264
/// Mute
6365
internal static var mute: String { L10n.tr("Localizable", "channel.item.mute") }
6466
/// Channel is muted
6567
internal static var muted: String { L10n.tr("Localizable", "channel.item.muted") }
68+
/// Photo
69+
internal static var photo: String { L10n.tr("Localizable", "channel.item.photo") }
6670
/// are typing ...
6771
internal static var typingPlural: String { L10n.tr("Localizable", "channel.item.typing-plural") }
6872
/// is typing ...
6973
internal static var typingSingular: String { L10n.tr("Localizable", "channel.item.typing-singular") }
7074
/// Unmute
7175
internal static var unmute: String { L10n.tr("Localizable", "channel.item.unmute") }
76+
/// Video
77+
internal static var video: String { L10n.tr("Localizable", "channel.item.video") }
7278
}
7379
internal enum Name {
7480
/// and

Sources/StreamChatSwiftUI/Resources/en.lproj/Localizable.strings

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,7 @@
140140

141141
"chat-info.rename.name" = "NAME";
142142
"chat-info.rename.placeholder" = "Add a group name";
143+
144+
"channel.item.audio" = "Audio";
145+
"channel.item.photo" = "Photo";
146+
"channel.item.video" = "Video";

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@
222222
8492974B27ABDDCB00A8EEB0 /* NotificationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8492974827ABDDBF00A8EEB0 /* NotificationsHandler.swift */; };
223223
8492975227B156D100A8EEB0 /* SlowModeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8492975127B156D000A8EEB0 /* SlowModeView.swift */; };
224224
8492975427B1725B00A8EEB0 /* MessageComposerView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8492975327B1725B00A8EEB0 /* MessageComposerView_Tests.swift */; };
225+
849894952AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849894942AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift */; };
225226
849CDD942768E0E1003C7A51 /* MessageActionsResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849CDD932768E0E1003C7A51 /* MessageActionsResolver.swift */; };
226227
849FD5112811B05C00952934 /* ChatInfoParticipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849FD5102811B05C00952934 /* ChatInfoParticipantsView.swift */; };
227228
84A1CACD2816BC420046595A /* ChatChannelInfoHelperViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1CACC2816BC420046595A /* ChatChannelInfoHelperViews.swift */; };
@@ -636,6 +637,7 @@
636637
8492974827ABDDBF00A8EEB0 /* NotificationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsHandler.swift; sourceTree = "<group>"; };
637638
8492975127B156D000A8EEB0 /* SlowModeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlowModeView.swift; sourceTree = "<group>"; };
638639
8492975327B1725B00A8EEB0 /* MessageComposerView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerView_Tests.swift; sourceTree = "<group>"; };
640+
849894942AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelListItemView_Tests.swift; sourceTree = "<group>"; };
639641
849CDD932768E0E1003C7A51 /* MessageActionsResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsResolver.swift; sourceTree = "<group>"; };
640642
849FD5102811B05C00952934 /* ChatInfoParticipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoParticipantsView.swift; sourceTree = "<group>"; };
641643
84A1CACC2816BC420046595A /* ChatChannelInfoHelperViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelInfoHelperViews.swift; sourceTree = "<group>"; };
@@ -1332,6 +1334,7 @@
13321334
84DEC8DC2760A10500172876 /* NoChannelsView_Tests.swift */,
13331335
8413D90127A9654600A89432 /* SearchResultsView_Tests.swift */,
13341336
84D6B55927DF6EC7009C6D07 /* LoadingView_Tests.swift */,
1337+
849894942AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift */,
13351338
);
13361339
path = ChatChannelList;
13371340
sourceTree = "<group>";
@@ -2044,6 +2047,7 @@
20442047
isa = PBXSourcesBuildPhase;
20452048
buildActionMask = 2147483647;
20462049
files = (
2050+
849894952AD96CCC004ACB41 /* ChatChannelListItemView_Tests.swift in Sources */,
20472051
84E04791284A444E00BAFA17 /* MockNetworkURLProtocol.swift in Sources */,
20482052
848399EC275FB41B003075E4 /* ChatChannelListView_Tests.swift in Sources */,
20492053
84C94D54275A1380007FE2B9 /* DateUtils_Tests.swift in Sources */,
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
//
2+
// Copyright © 2023 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import SnapshotTesting
6+
@testable import StreamChat
7+
@testable import StreamChatSwiftUI
8+
@testable import StreamChatTestTools
9+
import XCTest
10+
11+
final class ChatChannelListItemView_Tests: StreamChatTestCase {
12+
13+
func test_channelListItem_audioMessage() throws {
14+
// Given
15+
let message = try mockAudioMessage(text: "Audio", isSentByCurrentUser: true)
16+
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])
17+
18+
// When
19+
let view = ChatChannelListItem(
20+
channel: channel,
21+
channelName: "Test",
22+
avatar: .circleImage,
23+
onlineIndicatorShown: true,
24+
onItemTap: { _ in }
25+
)
26+
.frame(width: defaultScreenSize.width)
27+
28+
// Then
29+
assertSnapshot(matching: view, as: .image(perceptualPrecision: snapshotPrecision))
30+
}
31+
32+
func test_channelListItem_imageMessage() throws {
33+
// Given
34+
let message = try mockImageMessage(text: "Image", isSentByCurrentUser: true)
35+
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])
36+
37+
// When
38+
let view = ChatChannelListItem(
39+
channel: channel,
40+
channelName: "Test",
41+
avatar: .circleImage,
42+
onlineIndicatorShown: true,
43+
onItemTap: { _ in }
44+
)
45+
.frame(width: defaultScreenSize.width)
46+
47+
// Then
48+
assertSnapshot(matching: view, as: .image(perceptualPrecision: snapshotPrecision))
49+
}
50+
51+
func test_channelListItem_videoMessage() throws {
52+
// Given
53+
let message = try mockVideoMessage(text: "Video", isSentByCurrentUser: true)
54+
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])
55+
56+
// When
57+
let view = ChatChannelListItem(
58+
channel: channel,
59+
channelName: "Test",
60+
avatar: .circleImage,
61+
onlineIndicatorShown: true,
62+
onItemTap: { _ in }
63+
)
64+
.frame(width: defaultScreenSize.width)
65+
66+
// Then
67+
assertSnapshot(matching: view, as: .image(perceptualPrecision: snapshotPrecision))
68+
}
69+
70+
func test_channelListItem_fileMessage() throws {
71+
// Given
72+
let message = try mockFileMessage(title: "Filename", text: "File", isSentByCurrentUser: true)
73+
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])
74+
75+
// When
76+
let view = ChatChannelListItem(
77+
channel: channel,
78+
channelName: "Test",
79+
avatar: .circleImage,
80+
onlineIndicatorShown: true,
81+
onItemTap: { _ in }
82+
)
83+
.frame(width: defaultScreenSize.width)
84+
85+
// Then
86+
assertSnapshot(matching: view, as: .image(perceptualPrecision: snapshotPrecision))
87+
}
88+
89+
func test_channelListItem_giphyMessage() throws {
90+
// Given
91+
let message = try mockGiphyMessage(text: "Giphy", isSentByCurrentUser: true)
92+
let channel = ChatChannel.mock(cid: .unique, latestMessages: [message])
93+
94+
// When
95+
let view = ChatChannelListItem(
96+
channel: channel,
97+
channelName: "Test",
98+
avatar: .circleImage,
99+
onlineIndicatorShown: true,
100+
onItemTap: { _ in }
101+
)
102+
.frame(width: defaultScreenSize.width)
103+
104+
// Then
105+
assertSnapshot(matching: view, as: .image(perceptualPrecision: snapshotPrecision))
106+
}
107+
108+
//MARK: - private
109+
110+
private func mockAudioMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
111+
.mock(
112+
id: .unique,
113+
cid: .unique,
114+
text: text,
115+
type: .regular,
116+
author: .mock(id: "user", name: "User"),
117+
createdAt: Date(timeIntervalSince1970: 100),
118+
attachments: [
119+
.dummy(
120+
type: .audio,
121+
payload: try JSONEncoder().encode(AudioAttachmentPayload(
122+
title: "Some Audio",
123+
audioRemoteURL: URL(string: "url")!,
124+
file: .init(type: .mp3, size: 123, mimeType: nil),
125+
extraData: nil
126+
))
127+
)
128+
],
129+
localState: nil,
130+
isSentByCurrentUser: isSentByCurrentUser
131+
)
132+
}
133+
134+
private func mockImageMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
135+
.mock(
136+
id: .unique,
137+
cid: .unique,
138+
text: text,
139+
type: .regular,
140+
author: .mock(id: "user", name: "User"),
141+
createdAt: Date(timeIntervalSince1970: 100),
142+
attachments: [
143+
.dummy(
144+
type: .image,
145+
payload: try JSONEncoder().encode(ImageAttachmentPayload(
146+
title: "Test",
147+
imageRemoteURL: URL(string: "Url")!
148+
))
149+
)
150+
],
151+
localState: nil,
152+
isSentByCurrentUser: isSentByCurrentUser
153+
)
154+
}
155+
156+
private func mockVideoMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
157+
.mock(
158+
id: .unique,
159+
cid: .unique,
160+
text: text,
161+
type: .regular,
162+
author: .mock(id: "user", name: "User"),
163+
createdAt: Date(timeIntervalSince1970: 100),
164+
attachments: [
165+
.dummy(
166+
type: .video,
167+
payload: try JSONEncoder().encode(VideoAttachmentPayload(
168+
title: "Test",
169+
videoRemoteURL: URL(string: "Url")!,
170+
file: .init(type: .mp4, size: 123, mimeType: nil),
171+
extraData: nil
172+
))
173+
)
174+
],
175+
localState: nil,
176+
isSentByCurrentUser: isSentByCurrentUser
177+
)
178+
}
179+
180+
private func mockFileMessage(title: String?, text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
181+
.mock(
182+
id: .unique,
183+
cid: .unique,
184+
text: text,
185+
type: .regular,
186+
author: .mock(id: "user", name: "User"),
187+
createdAt: Date(timeIntervalSince1970: 100),
188+
attachments: [
189+
.dummy(
190+
type: .file,
191+
payload: try JSONEncoder().encode(FileAttachmentPayload(
192+
title: title,
193+
assetRemoteURL: URL(string: "Url")!,
194+
file: .init(type: .pdf, size: 123, mimeType: nil),
195+
extraData: nil
196+
))
197+
)
198+
],
199+
localState: nil,
200+
isSentByCurrentUser: isSentByCurrentUser
201+
)
202+
}
203+
204+
private func mockGiphyMessage(text: String, isSentByCurrentUser: Bool) throws -> ChatMessage {
205+
.mock(
206+
id: .unique,
207+
cid: .unique,
208+
text: text,
209+
type: .regular,
210+
author: .mock(id: "user", name: "User"),
211+
createdAt: Date(timeIntervalSince1970: 100),
212+
attachments: [
213+
.dummy(
214+
type: .giphy,
215+
payload: try JSONEncoder().encode(GiphyAttachmentPayload(
216+
title: "Test",
217+
previewURL: URL(string: "Url")!
218+
))
219+
)
220+
],
221+
localState: nil,
222+
isSentByCurrentUser: isSentByCurrentUser
223+
)
224+
}
225+
}
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)