Skip to content

Commit 6a59193

Browse files
Header for oldest message in a group (#109)
1 parent 43cd793 commit 6a59193

File tree

15 files changed

+232
-23
lines changed

15 files changed

+232
-23
lines changed

CHANGELOG.md

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

44
# Upcoming
55

6-
### 🔄 Changed
6+
### ✅ Added
7+
- Possibility to add a custom view above the oldest message in a group
8+
9+
### 🐞 Fixed
10+
- Memory cache trimming on chat dismiss
711

812
# [4.16.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.16.0)
913
_June 10, 2022_

DemoAppSwiftUI/AppDelegate.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ class AppDelegate: NSObject, UIApplicationDelegate {
6262
}
6363
#endif
6464

65-
let utils = Utils(messageListConfig: MessageListConfig(dateIndicatorPlacement: .messageList))
65+
let utils = Utils(
66+
messageListConfig: MessageListConfig(dateIndicatorPlacement: .messageList)
67+
)
6668
streamChat = StreamChat(chatClient: chatClient, utils: utils)
6769

6870
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,34 +346,46 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
346346

347347
private func groupMessages() {
348348
var temp = [String: [String]]()
349-
let primary = "primary"
350349
for (index, message) in messages.enumerated() {
351350
let date = message.createdAt
351+
temp[message.id] = []
352352
if index == 0 {
353-
temp[message.id] = [primary]
353+
temp[message.id] = [firstMessageKey]
354354
continue
355+
} else if index == messages.count - 1 {
356+
temp[message.id] = [lastMessageKey]
355357
}
356-
358+
357359
let previous = index - 1
358360
let previousMessage = messages[previous]
359361
let currentAuthorId = messageCachingUtils.authorId(for: message)
360362
let previousAuthorId = messageCachingUtils.authorId(for: previousMessage)
361363

362364
if currentAuthorId != previousAuthorId {
363-
temp[message.id] = [primary]
365+
temp[message.id]?.append(firstMessageKey)
366+
var prevInfo = temp[previousMessage.id] ?? []
367+
prevInfo.append(lastMessageKey)
368+
temp[previousMessage.id] = prevInfo
364369
}
365370

366371
if previousMessage.type == .error
367372
|| previousMessage.type == .ephemeral
368373
|| previousMessage.type == .system {
369-
temp[message.id] = [primary]
374+
temp[message.id] = [firstMessageKey]
370375
continue
371376
}
372377

373378
let delay = previousMessage.createdAt.timeIntervalSince(date)
374379

375380
if delay > utils.messageListConfig.maxTimeIntervalBetweenMessagesInGroup {
376-
temp[message.id] = [primary]
381+
temp[message.id]?.append(firstMessageKey)
382+
var prevInfo = temp[previousMessage.id] ?? []
383+
prevInfo.append(lastMessageKey)
384+
temp[previousMessage.id] = prevInfo
385+
}
386+
387+
if temp[message.id]?.isEmpty == true {
388+
temp[message.id] = nil
377389
}
378390
}
379391

@@ -533,3 +545,6 @@ enum AnimationChange {
533545
case notAnimated
534546
case skip
535547
}
548+
549+
let firstMessageKey = "firstMessage"
550+
let lastMessageKey = "lastMessage"

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public struct MessageDisplayOptions {
7979
let showAuthorName: Bool
8080
let animateChanges: Bool
8181
let dateLabelSize: CGFloat
82+
let lastInGroupHeaderSize: CGFloat
8283
let minimumSwipeGestureDistance: CGFloat
8384
let currentUserMessageTransition: AnyTransition
8485
let otherUserMessageTransition: AnyTransition
@@ -90,6 +91,7 @@ public struct MessageDisplayOptions {
9091
showAuthorName: Bool = true,
9192
animateChanges: Bool = true,
9293
overlayDateLabelSize: CGFloat = 40,
94+
lastInGroupHeaderSize: CGFloat = 0,
9395
minimumSwipeGestureDistance: CGFloat = 10,
9496
currentUserMessageTransition: AnyTransition = .identity,
9597
otherUserMessageTransition: AnyTransition = .identity,
@@ -105,6 +107,7 @@ public struct MessageDisplayOptions {
105107
self.currentUserMessageTransition = currentUserMessageTransition
106108
self.otherUserMessageTransition = otherUserMessageTransition
107109
self.messageLinkDisplayResolver = messageLinkDisplayResolver
110+
self.lastInGroupHeaderSize = lastInGroupHeaderSize
108111
}
109112

110113
public static var defaultLinkDisplay: (ChatMessage) -> [NSAttributedString.Key: Any] {

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import SwiftUI
99
public struct MessageAuthorAndDateView: View {
1010

1111
@Injected(\.utils) private var utils
12-
@Injected(\.fonts) private var fonts
13-
@Injected(\.colors) private var colors
1412

1513
var message: ChatMessage
1614

@@ -20,10 +18,7 @@ public struct MessageAuthorAndDateView: View {
2018

2119
public var body: some View {
2220
HStack {
23-
Text(utils.messageCachingUtils.authorName(for: message))
24-
.lineLimit(1)
25-
.font(fonts.footnoteBold)
26-
.foregroundColor(Color(colors.textLowEmphasis))
21+
MessageAuthorView(message: message)
2722
if utils.messageListConfig.messageDisplayOptions.showMessageDate {
2823
MessageDateView(message: message)
2924
}
@@ -32,6 +27,27 @@ public struct MessageAuthorAndDateView: View {
3227
}
3328
}
3429

30+
/// View that displays the message author.
31+
public struct MessageAuthorView: View {
32+
33+
@Injected(\.utils) private var utils
34+
@Injected(\.fonts) private var fonts
35+
@Injected(\.colors) private var colors
36+
37+
var message: ChatMessage
38+
39+
public init(message: ChatMessage) {
40+
self.message = message
41+
}
42+
43+
public var body: some View {
44+
Text(utils.messageCachingUtils.authorName(for: message))
45+
.lineLimit(1)
46+
.font(fonts.footnoteBold)
47+
.foregroundColor(Color(colors.textLowEmphasis))
48+
}
49+
}
50+
3551
/// View that displays the sending date of a message.
3652
struct MessageDateView: View {
3753
@Injected(\.utils) private var utils

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListView.swift

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
5151
&& channel.config.typingEventsEnabled
5252
}
5353

54+
private var lastInGroupHeaderSize: CGFloat {
55+
messageListConfig.messageDisplayOptions.lastInGroupHeaderSize
56+
}
57+
5458
private let scrollAreaId = "scrollArea"
5559

5660
public init(
@@ -112,6 +116,7 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
112116
ForEach(messages, id: \.messageId) { message in
113117
var index: Int? = messageListDateUtils.indexForMessageDate(message: message, in: messages)
114118
let messageDate: Date? = messageListDateUtils.showMessageDate(for: index, in: messages)
119+
let showsLastInGroupInfo = showsLastInGroupInfo(for: message, channel: channel)
115120
factory.makeMessageContainerView(
116121
channel: channel,
117122
message: message,
@@ -121,7 +126,7 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
121126
scrolledId: $scrolledId,
122127
quotedMessage: $quotedMessage,
123128
onLongPress: handleLongPress(messageDisplayInfo:),
124-
isLast: message == messages.last
129+
isLast: !showsLastInGroupInfo && message == messages.last
125130
)
126131
.onAppear {
127132
if index == nil {
@@ -131,16 +136,29 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
131136
onMessageAppear(index)
132137
}
133138
}
134-
.overlay(
139+
.padding(
140+
.top,
135141
messageDate != nil ?
136-
VStack {
137-
factory.makeMessageListDateIndicator(date: messageDate!)
138-
.offset(y: -messageListConfig.messageDisplayOptions.dateLabelSize)
142+
offsetForDateIndicator(showsLastInGroupInfo: showsLastInGroupInfo) :
143+
additionalTopPadding(showsLastInGroupInfo: showsLastInGroupInfo)
144+
)
145+
.overlay(
146+
(messageDate != nil || showsLastInGroupInfo) ?
147+
VStack(spacing: 0) {
148+
messageDate != nil ?
149+
factory.makeMessageListDateIndicator(date: messageDate!)
150+
.frame(maxHeight: messageListConfig.messageDisplayOptions.dateLabelSize)
151+
: nil
152+
153+
showsLastInGroupInfo ?
154+
factory.makeLastInGroupHeaderView(for: message)
155+
.frame(maxHeight: lastInGroupHeaderSize)
156+
: nil
157+
139158
Spacer()
140159
}
141160
: nil
142161
)
143-
.padding(.top, messageDate != nil ? messageListConfig.messageDisplayOptions.dateLabelSize : 0)
144162
.flippedUpsideDown()
145163
}
146164
.id(listId)
@@ -227,11 +245,35 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
227245
}
228246
}
229247

248+
private func additionalTopPadding(showsLastInGroupInfo: Bool) -> CGFloat {
249+
showsLastInGroupInfo ? lastInGroupHeaderSize : 0
250+
}
251+
252+
private func offsetForDateIndicator(showsLastInGroupInfo: Bool) -> CGFloat {
253+
var offset = messageListConfig.messageDisplayOptions.dateLabelSize
254+
offset += additionalTopPadding(showsLastInGroupInfo: showsLastInGroupInfo)
255+
return offset
256+
}
257+
230258
private func showsAllData(for message: ChatMessage) -> Bool {
231259
if !messageListConfig.groupMessages {
232260
return true
233261
}
234-
return messagesGroupingInfo[message.id] != nil
262+
let groupInfo = messagesGroupingInfo[message.id] ?? []
263+
return groupInfo.contains(firstMessageKey) == true
264+
}
265+
266+
private func showsLastInGroupInfo(
267+
for message: ChatMessage,
268+
channel: ChatChannel
269+
) -> Bool {
270+
guard channel.memberCount > 2
271+
&& !message.isSentByCurrentUser
272+
&& (lastInGroupHeaderSize > 0) else {
273+
return false
274+
}
275+
let groupInfo = messagesGroupingInfo[message.id] ?? []
276+
return groupInfo.contains(lastMessageKey) == true
235277
}
236278

237279
private func handleLongPress(messageDisplayInfo: MessageDisplayInfo) {

Sources/StreamChatSwiftUI/DefaultViewFactory.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ extension ViewFactory {
299299
MessageAuthorAndDateView(message: message)
300300
}
301301

302+
public func makeLastInGroupHeaderView(for message: ChatMessage) -> some View {
303+
EmptyView()
304+
}
305+
302306
public func makeImageAttachmentView(
303307
for message: ChatMessage,
304308
isFirst: Bool,

Sources/StreamChatSwiftUI/ViewFactory.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ public protocol ViewFactory: AnyObject {
293293
/// - Returns: view shown in the date and author indicator slot.
294294
func makeMessageAuthorAndDateView(for message: ChatMessage) -> MessageAuthorAndDateViewType
295295

296+
associatedtype LastInGroupHeaderView: View
297+
/// Creates a view shown as a header of the last message in a group.
298+
/// - Parameter message: the chat message for which the header will be displayed.
299+
/// - Returns: view shown in the header of the last message.
300+
func makeLastInGroupHeaderView(for message: ChatMessage) -> LastInGroupHeaderView
301+
296302
associatedtype ImageAttachmentViewType: View
297303
/// Creates the image attachment view.
298304
/// - Parameters:

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@
309309
84C94D62275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C94D61275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift */; };
310310
84C94D66275A660B007FE2B9 /* MessageActionsViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C94D65275A660B007FE2B9 /* MessageActionsViewModel_Tests.swift */; };
311311
84C94D68275A6AFD007FE2B9 /* ChannelHeaderLoader_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C94D67275A6AFD007FE2B9 /* ChannelHeaderLoader_Tests.swift */; };
312+
84CAD77B284E5AAA00F28C17 /* MessageListViewLastGroupHeader_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAD77A284E5AAA00F28C17 /* MessageListViewLastGroupHeader_Tests.swift */; };
312313
84D6B55A27DF6EC7009C6D07 /* LoadingView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6B55927DF6EC7009C6D07 /* LoadingView_Tests.swift */; };
313314
84DEC8DB27609FA200172876 /* MoreChannelActionsView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEC8DA27609FA200172876 /* MoreChannelActionsView_Tests.swift */; };
314315
84DEC8DD2760A10500172876 /* NoChannelsView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEC8DC2760A10500172876 /* NoChannelsView_Tests.swift */; };
@@ -686,6 +687,7 @@
686687
84C94D61275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelNamer_Tests.swift; sourceTree = "<group>"; };
687688
84C94D65275A660B007FE2B9 /* MessageActionsViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsViewModel_Tests.swift; sourceTree = "<group>"; };
688689
84C94D67275A6AFD007FE2B9 /* ChannelHeaderLoader_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelHeaderLoader_Tests.swift; sourceTree = "<group>"; };
690+
84CAD77A284E5AAA00F28C17 /* MessageListViewLastGroupHeader_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListViewLastGroupHeader_Tests.swift; sourceTree = "<group>"; };
689691
84D6B55927DF6EC7009C6D07 /* LoadingView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView_Tests.swift; sourceTree = "<group>"; };
690692
84DEC8DA27609FA200172876 /* MoreChannelActionsView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreChannelActionsView_Tests.swift; sourceTree = "<group>"; };
691693
84DEC8DC2760A10500172876 /* NoChannelsView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoChannelsView_Tests.swift; sourceTree = "<group>"; };
@@ -1385,7 +1387,8 @@
13851387
847305BA28241D8D004AC770 /* ChatChannelHeader_Tests.swift */,
13861388
847305BC28243D25004AC770 /* WebView_Tests.swift */,
13871389
84BB4C4D284115C200CBE004 /* MessageListDateUtils_Tests.swift */,
1388-
844D1D672851DE58000CCCB9 /* ChannelControllerFactory_Tests.swift */,
1390+
84CAD77A284E5AAA00F28C17 /* MessageListViewLastGroupHeader_Tests.swift */,
1391+
844D1D672851DE58000CCCB9 /* ChannelControllerFactory_Tests.swift */
13891392
);
13901393
path = ChatChannel;
13911394
sourceTree = "<group>";
@@ -1798,6 +1801,7 @@
17981801
84B2B5D02819629400479CEE /* PinnedMessagesView_Tests.swift in Sources */,
17991802
84C94D1427578BF3007FE2B9 /* TemporaryData.swift in Sources */,
18001803
84C94D4F2758FE59007FE2B9 /* ChatChannelTestHelpers.swift in Sources */,
1804+
84CAD77B284E5AAA00F28C17 /* MessageListViewLastGroupHeader_Tests.swift in Sources */,
18011805
91CC203C283C4C250049A146 /* URLUtils_Tests.swift in Sources */,
18021806
84C94CCC27578B92007FE2B9 /* ChatClientUpdater_Mock.swift in Sources */,
18031807
84B2B5DA281985DA00479CEE /* FileAttachmentsView_Tests.swift in Sources */,

StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ChatChannelViewModel_Tests: StreamChatTestCase {
4747
let messagesGroupingInfo = viewModel.messagesGroupingInfo
4848

4949
// Then
50-
XCTAssert(messagesGroupingInfo.count == 1)
50+
XCTAssert(messagesGroupingInfo.count == 2)
5151
for (_, groupingInfo) in messagesGroupingInfo {
5252
XCTAssert(groupingInfo.count == 1)
5353
}

0 commit comments

Comments
 (0)