Skip to content

Commit f7d2763

Browse files
Added a configurable separator view for new messages
1 parent 718c9ca commit f7d2763

15 files changed

+260
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
### ✅ Added
77
- Possibility to customize message reactions top padding (for grid-based reaction containers)
88
- Custom sorting of reactions
9+
- Added a configurable separator view for new messages
910

1011
# [4.26.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.26.0)
1112
_January 16, 2023_

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public struct MessageListConfig {
2323
maxTimeIntervalBetweenMessagesInGroup: TimeInterval = 60,
2424
cacheSizeOnChatDismiss: Int = 1024 * 1024 * 100,
2525
iPadSplitViewEnabled: Bool = true,
26-
scrollingAnchor: UnitPoint = .bottom
26+
scrollingAnchor: UnitPoint = .bottom,
27+
showNewMessagesSeparator: Bool = false
2728
) {
2829
self.messageListType = messageListType
2930
self.typingIndicatorPlacement = typingIndicatorPlacement
@@ -40,6 +41,7 @@ public struct MessageListConfig {
4041
self.cacheSizeOnChatDismiss = cacheSizeOnChatDismiss
4142
self.iPadSplitViewEnabled = iPadSplitViewEnabled
4243
self.scrollingAnchor = scrollingAnchor
44+
self.showNewMessagesSeparator = showNewMessagesSeparator
4345
}
4446

4547
public let messageListType: MessageListType
@@ -57,6 +59,7 @@ public struct MessageListConfig {
5759
public let cacheSizeOnChatDismiss: Int
5860
public let iPadSplitViewEnabled: Bool
5961
public let scrollingAnchor: UnitPoint
62+
public let showNewMessagesSeparator: Bool
6063
}
6164

6265
/// Contains information about the message paddings.
@@ -87,6 +90,7 @@ public struct MessageDisplayOptions {
8790
public let animateChanges: Bool
8891
public let dateLabelSize: CGFloat
8992
public let lastInGroupHeaderSize: CGFloat
93+
public let newMessagesSeparatorSize: CGFloat
9094
public let minimumSwipeGestureDistance: CGFloat
9195
public let currentUserMessageTransition: AnyTransition
9296
public let otherUserMessageTransition: AnyTransition
@@ -103,6 +107,7 @@ public struct MessageDisplayOptions {
103107
animateChanges: Bool = true,
104108
overlayDateLabelSize: CGFloat = 40,
105109
lastInGroupHeaderSize: CGFloat = 0,
110+
newMessagesSeparatorSize: CGFloat = 50,
106111
minimumSwipeGestureDistance: CGFloat = 10,
107112
currentUserMessageTransition: AnyTransition = .identity,
108113
otherUserMessageTransition: AnyTransition = .identity,
@@ -126,6 +131,7 @@ public struct MessageDisplayOptions {
126131
self.spacerWidth = spacerWidth
127132
self.showAvatarsInGroups = showAvatarsInGroups ?? showAvatars
128133
self.reactionsTopPadding = reactionsTopPadding
134+
self.newMessagesSeparatorSize = newMessagesSeparatorSize
129135
}
130136

131137
public func showAvatars(for channel: ChatChannel) -> Bool {

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListView.swift

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
3030
@State private var width: CGFloat?
3131
@State private var keyboardShown = false
3232
@State private var pendingKeyboardUpdate: Bool?
33+
@State private var newMessagesStartId: String?
3334

3435
private var messageRenderingUtil = MessageRenderingUtil.shared
3536
private var skipRenderingMessageIds = [String]()
@@ -49,6 +50,10 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
4950
private var lastInGroupHeaderSize: CGFloat {
5051
messageListConfig.messageDisplayOptions.lastInGroupHeaderSize
5152
}
53+
54+
private var newMessagesSeparatorSize: CGFloat {
55+
messageListConfig.messageDisplayOptions.newMessagesSeparatorSize
56+
}
5257

5358
private let scrollAreaId = "scrollArea"
5459

@@ -95,6 +100,14 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
95100
map: { $0 }
96101
)
97102
}
103+
if messageListConfig.showNewMessagesSeparator && channel.unreadCount.messages > 0 {
104+
let index = channel.unreadCount.messages - 1
105+
if index < messages.count {
106+
_newMessagesStartId = .init(wrappedValue: messages[index].id)
107+
}
108+
} else {
109+
_newMessagesStartId = .init(wrappedValue: nil)
110+
}
98111
}
99112

100113
public var body: some View {
@@ -113,6 +126,7 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
113126
ForEach(messages, id: \.messageId) { message in
114127
var index: Int? = messageListDateUtils.indexForMessageDate(message: message, in: messages)
115128
let messageDate: Date? = messageListDateUtils.showMessageDate(for: index, in: messages)
129+
let showUnreadSeparator = message.id == newMessagesStartId
116130
let showsLastInGroupInfo = showsLastInGroupInfo(for: message, channel: channel)
117131
factory.makeMessageContainerView(
118132
channel: channel,
@@ -136,16 +150,29 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
136150
.padding(
137151
.top,
138152
messageDate != nil ?
139-
offsetForDateIndicator(showsLastInGroupInfo: showsLastInGroupInfo) :
140-
additionalTopPadding(showsLastInGroupInfo: showsLastInGroupInfo)
153+
offsetForDateIndicator(
154+
showsLastInGroupInfo: showsLastInGroupInfo,
155+
showUnreadSeparator: showUnreadSeparator
156+
) :
157+
additionalTopPadding(
158+
showsLastInGroupInfo: showsLastInGroupInfo,
159+
showUnreadSeparator: showUnreadSeparator
160+
)
141161
)
142162
.overlay(
143-
(messageDate != nil || showsLastInGroupInfo) ?
163+
(messageDate != nil || showsLastInGroupInfo || showUnreadSeparator) ?
144164
VStack(spacing: 0) {
145165
messageDate != nil ?
146166
factory.makeMessageListDateIndicator(date: messageDate!)
147167
.frame(maxHeight: messageListConfig.messageDisplayOptions.dateLabelSize)
148168
: nil
169+
170+
showUnreadSeparator ?
171+
factory.makeNewMessagesIndicatorView(
172+
newMessagesStartId: $newMessagesStartId,
173+
count: newMessagesCount(for: index)
174+
)
175+
: nil
149176

150177
showsLastInGroupInfo ?
151178
factory.makeLastInGroupHeaderView(for: message)
@@ -247,15 +274,27 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
247274
.accessibilityIdentifier("MessageListView")
248275
}
249276

250-
private func additionalTopPadding(showsLastInGroupInfo: Bool) -> CGFloat {
251-
showsLastInGroupInfo ? lastInGroupHeaderSize : 0
277+
private func additionalTopPadding(showsLastInGroupInfo: Bool, showUnreadSeparator: Bool) -> CGFloat {
278+
var padding = showsLastInGroupInfo ? lastInGroupHeaderSize : 0
279+
if showUnreadSeparator {
280+
padding += newMessagesSeparatorSize
281+
}
282+
return padding
252283
}
253284

254-
private func offsetForDateIndicator(showsLastInGroupInfo: Bool) -> CGFloat {
285+
private func offsetForDateIndicator(showsLastInGroupInfo: Bool, showUnreadSeparator: Bool) -> CGFloat {
255286
var offset = messageListConfig.messageDisplayOptions.dateLabelSize
256-
offset += additionalTopPadding(showsLastInGroupInfo: showsLastInGroupInfo)
287+
offset += additionalTopPadding(showsLastInGroupInfo: showsLastInGroupInfo, showUnreadSeparator: showUnreadSeparator)
257288
return offset
258289
}
290+
291+
private func newMessagesCount(for index: Int?) -> Int {
292+
if let index = index {
293+
return index + 1
294+
} else {
295+
return channel.unreadCount.messages
296+
}
297+
}
259298

260299
private func showsAllData(for message: ChatMessage) -> Bool {
261300
if !messageListConfig.groupMessages {
@@ -303,6 +342,34 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
303342
}
304343
}
305344

345+
public struct NewMessagesIndicator: View {
346+
347+
@Injected(\.colors) var colors
348+
349+
@Binding var newMessagesStartId: String?
350+
var count: Int
351+
352+
public init(newMessagesStartId: Binding<String?>, count: Int) {
353+
_newMessagesStartId = newMessagesStartId
354+
self.count = count
355+
}
356+
357+
public var body: some View {
358+
HStack {
359+
Text("\(L10n.MessageList.newMessages(count))")
360+
.foregroundColor(Color(colors.textLowEmphasis))
361+
.font(.headline)
362+
.padding(.all, 8)
363+
}
364+
.frame(maxWidth: .infinity)
365+
.background(Color(colors.background8))
366+
.padding(.top, 4)
367+
.onDisappear {
368+
newMessagesStartId = nil
369+
}
370+
}
371+
}
372+
306373
public struct ScrollToBottomButton: View {
307374
@Injected(\.images) private var images
308375
@Injected(\.colors) private var colors

Sources/StreamChatSwiftUI/DefaultViewFactory.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,16 @@ extension ViewFactory {
823823
localState: message.localState
824824
)
825825
}
826+
827+
public func makeNewMessagesIndicatorView(
828+
newMessagesStartId: Binding<String?>,
829+
count: Int
830+
) -> some View {
831+
NewMessagesIndicator(
832+
newMessagesStartId: newMessagesStartId,
833+
count: count
834+
)
835+
}
826836
}
827837

828838
/// Default class conforming to `ViewFactory`, used throughout the SDK.

Sources/StreamChatSwiftUI/Generated/L10n.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ internal enum L10n {
359359
}
360360

361361
internal enum MessageList {
362+
/// Plural format key: "%#@messages@"
363+
internal static func newMessages(_ p1: Int) -> String {
364+
return L10n.tr("Localizable", "messageList.newMessages", p1)
365+
}
362366
internal enum TypingIndicator {
363367
/// Someone is typing
364368
internal static var typingUnknown: String { L10n.tr("Localizable", "messageList.typingIndicator.typing-unknown") }

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@
3838
<string>%d results</string>
3939
</dict>
4040
</dict>
41+
<key>messageList.newMessages</key>
42+
<dict>
43+
<key>NSStringLocalizedFormatKey</key>
44+
<string>%#@messages@</string>
45+
<key>messages</key>
46+
<dict>
47+
<key>NSStringFormatSpecTypeKey</key>
48+
<string>NSStringPluralRuleType</string>
49+
<key>NSStringFormatValueTypeKey</key>
50+
<string>d</string>
51+
<key>zero</key>
52+
<string>%d new messages</string>
53+
<key>one</key>
54+
<string>%d new message</string>
55+
<key>other</key>
56+
<string>%d new messages</string>
57+
</dict>
58+
</dict>
4159
<key>messageList.typingIndicator.users</key>
4260
<dict>
4361
<key>NSStringLocalizedFormatKey</key>

Sources/StreamChatSwiftUI/ViewFactory.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,4 +812,15 @@ public protocol ViewFactory: AnyObject {
812812
channel: ChatChannel,
813813
message: ChatMessage
814814
) -> MessageReadIndicatorViewType
815+
816+
associatedtype NewMessagesIndicatorViewType: View
817+
/// Creates a separator view showing the number of new messages in the message list.
818+
/// - Parameters:
819+
/// - newMessagesStartId: the id of the message where the new messages start.
820+
/// - count: the number of unread messages.
821+
/// - Returns: view shown in the new messages indicator slot.
822+
func makeNewMessagesIndicatorView(
823+
newMessagesStartId: Binding<String?>,
824+
count: Int
825+
) -> NewMessagesIndicatorViewType
815826
}

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@
224224
84A1CACF2816BCF00046595A /* AddUsersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1CACE2816BCF00046595A /* AddUsersView.swift */; };
225225
84A1CAD12816C6900046595A /* AddUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1CAD02816C6900046595A /* AddUsersViewModel.swift */; };
226226
84A75FBB274EA29B00225CE8 /* GiphyAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A75FBA274EA29B00225CE8 /* GiphyAttachmentView.swift */; };
227+
84AA6EAA2987EE51005732EF /* MessageListViewNewMessages_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA6EA92987EE51005732EF /* MessageListViewNewMessages_Tests.swift */; };
227228
84AB7B1D2771F4AA00631A10 /* DiscardButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AB7B1C2771F4AA00631A10 /* DiscardButtonView.swift */; };
228229
84AB7B21277203EF00631A10 /* GalleryView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AB7B20277203EF00631A10 /* GalleryView_Tests.swift */; };
229230
84AB7B242773528300631A10 /* CommandsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AB7B232773528200631A10 /* CommandsContainerView.swift */; };
@@ -633,6 +634,7 @@
633634
84A1CACE2816BCF00046595A /* AddUsersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddUsersView.swift; sourceTree = "<group>"; };
634635
84A1CAD02816C6900046595A /* AddUsersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddUsersViewModel.swift; sourceTree = "<group>"; };
635636
84A75FBA274EA29B00225CE8 /* GiphyAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiphyAttachmentView.swift; sourceTree = "<group>"; };
637+
84AA6EA92987EE51005732EF /* MessageListViewNewMessages_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListViewNewMessages_Tests.swift; sourceTree = "<group>"; };
636638
84AB7B1C2771F4AA00631A10 /* DiscardButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscardButtonView.swift; sourceTree = "<group>"; };
637639
84AB7B20277203EF00631A10 /* GalleryView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryView_Tests.swift; sourceTree = "<group>"; };
638640
84AB7B232773528200631A10 /* CommandsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandsContainerView.swift; sourceTree = "<group>"; };
@@ -1463,6 +1465,7 @@
14631465
842F036C288E93BF00496D49 /* ChatMessage_AdjustedText_Tests.swift */,
14641466
84D419B928EAD20C00F574F9 /* ChatMessageBubbles_Tests.swift */,
14651467
84E1D8252976B3F100060491 /* MessageViewMultiRowReactions_Tests.swift */,
1468+
84AA6EA92987EE51005732EF /* MessageListViewNewMessages_Tests.swift */,
14661469
);
14671470
path = ChatChannel;
14681471
sourceTree = "<group>";
@@ -2030,6 +2033,7 @@
20302033
848399EC275FB41B003075E4 /* ChatChannelListView_Tests.swift in Sources */,
20312034
84C94D54275A1380007FE2B9 /* DateUtils_Tests.swift in Sources */,
20322035
84DEC8DB27609FA200172876 /* MoreChannelActionsView_Tests.swift in Sources */,
2036+
84AA6EAA2987EE51005732EF /* MessageListViewNewMessages_Tests.swift in Sources */,
20332037
84C94D56275A1AE1007FE2B9 /* StringExtensions_Tests.swift in Sources */,
20342038
847305BB28241D8D004AC770 /* ChatChannelHeader_Tests.swift in Sources */,
20352039
84DEC8DD2760A10500172876 /* NoChannelsView_Tests.swift in Sources */,

0 commit comments

Comments
 (0)