Skip to content

Commit 97a328d

Browse files
Implement date separators in message list (#80)
1 parent e2638c4 commit 97a328d

File tree

17 files changed

+365
-18
lines changed

17 files changed

+365
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

66
### ✅ Added
77
- Possibility to view channel info on channel options
8+
- Date separators in the message list
89

910
### 🐞 Fixed
1011
- Bug about link attachments not opening when the URL was missing the scheme

DemoAppSwiftUI/AppDelegate.swift

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

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

6768
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
6869
withAnimation {

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
2828
private var isActive = true
2929
private var readsString = ""
3030

31-
private let messageListDateOverlay: DateFormatter = {
32-
let df = DateFormatter()
33-
df.setLocalizedDateFormatFromTemplate("MMMdd")
34-
df.locale = .autoupdatingCurrent
35-
return df
36-
}()
31+
private let messageListDateOverlay: DateFormatter = DateFormatter.messageListDateOverlay
3732

3833
private lazy var messagesDateFormatter = utils.dateFormatter
3934
private lazy var messageCachingUtils = utils.messageCachingUtils
@@ -150,6 +145,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
150145
if index >= messages.count {
151146
return
152147
}
148+
153149
let message = messages[index]
154150
checkForNewMessages(index: index)
155151
if utils.messageListConfig.dateIndicatorPlacement == .overlay {

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public struct MessagePaddings {
6262
public enum DateIndicatorPlacement {
6363
case none
6464
case overlay
65-
case messageList // Not supported yet.
65+
case messageList
6666
}
6767

6868
/// Used to show and hide different helper views around the message.
@@ -72,6 +72,7 @@ public struct MessageDisplayOptions {
7272
let showMessageDate: Bool
7373
let showAuthorName: Bool
7474
let animateChanges: Bool
75+
let dateLabelSize: CGFloat
7576
let currentUserMessageTransition: AnyTransition
7677
let otherUserMessageTransition: AnyTransition
7778
var messageLinkDisplayResolver: (ChatMessage) -> [NSAttributedString.Key: Any]
@@ -81,6 +82,7 @@ public struct MessageDisplayOptions {
8182
showMessageDate: Bool = true,
8283
showAuthorName: Bool = true,
8384
animateChanges: Bool = true,
85+
overlayDateLabelSize: CGFloat = 40,
8486
currentUserMessageTransition: AnyTransition = .identity,
8587
otherUserMessageTransition: AnyTransition = .identity,
8688
messageLinkDisplayResolver: @escaping (ChatMessage) -> [NSAttributedString.Key: Any] = MessageDisplayOptions
@@ -90,6 +92,7 @@ public struct MessageDisplayOptions {
9092
self.showAuthorName = showAuthorName
9193
self.showMessageDate = showMessageDate
9294
self.animateChanges = animateChanges
95+
dateLabelSize = overlayDateLabelSize
9396
self.currentUserMessageTransition = currentUserMessageTransition
9497
self.otherUserMessageTransition = otherUserMessageTransition
9598
self.messageLinkDisplayResolver = messageLinkDisplayResolver
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// Copyright © 2022 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
import StreamChat
7+
8+
class MessageListDateUtils {
9+
10+
private let messageListConfig: MessageListConfig
11+
12+
init(messageListConfig: MessageListConfig) {
13+
self.messageListConfig = messageListConfig
14+
}
15+
16+
/// Returns index for a message, only if .messageList date indicator placement is enabled.
17+
/// - Parameters:
18+
/// - message, the message whose index is searched for.
19+
/// - messages: the list of messages.
20+
/// - Returns: optional index.
21+
func indexForMessageDate(
22+
message: ChatMessage,
23+
in messages: LazyCachedMapCollection<ChatMessage>
24+
) -> Int? {
25+
if messageListConfig.dateIndicatorPlacement != .messageList {
26+
// Index computation will be done onAppear.
27+
return nil
28+
}
29+
30+
return index(for: message, in: messages)
31+
}
32+
33+
/// Returns index for a message, if it exists.
34+
/// - Parameters:
35+
/// - message, the message whose index is searched for.
36+
/// - messages: the list of messages.
37+
/// - Returns: optional index.
38+
func index(
39+
for message: ChatMessage,
40+
in messages: LazyCachedMapCollection<ChatMessage>
41+
) -> Int? {
42+
let index = messages.firstIndex { msg in
43+
msg.id == message.id
44+
}
45+
46+
return index
47+
}
48+
49+
/// Returns whether a date should be presented above a message.
50+
/// - Parameters:
51+
/// - index, the index of a message.
52+
/// - messages: the list of messages.
53+
/// - Returns: optional date, shown above a message.
54+
func showMessageDate(
55+
for index: Int?,
56+
in messages: LazyCachedMapCollection<ChatMessage>
57+
) -> Date? {
58+
guard let index = index else {
59+
return nil
60+
}
61+
62+
let message = messages[index]
63+
let previousIndex = index + 1
64+
if previousIndex < messages.count {
65+
let previous = messages[previousIndex]
66+
let isDifferentDay = !Calendar.current.isDate(
67+
message.createdAt,
68+
equalTo: previous.createdAt,
69+
toGranularity: .day
70+
)
71+
if isDifferentDay {
72+
return message.createdAt
73+
} else {
74+
return nil
75+
}
76+
} else {
77+
return message.createdAt
78+
}
79+
}
80+
}

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListView.swift

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
4141
utils.messageListConfig
4242
}
4343

44+
private var messageListDateUtils: MessageListDateUtils {
45+
utils.messageListDateUtils
46+
}
47+
4448
private var shouldShowTypingIndicator: Bool {
4549
!channel.currentlyTypingUsersFiltered(currentUserId: chatClient.currentUserId).isEmpty
4650
&& messageListConfig.typingIndicatorPlacement == .bottomOverlay
@@ -106,6 +110,8 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
106110

107111
LazyVStack(spacing: 0) {
108112
ForEach(messages, id: \.messageId) { message in
113+
var index: Int? = messageListDateUtils.indexForMessageDate(message: message, in: messages)
114+
let messageDate: Date? = messageListDateUtils.showMessageDate(for: index, in: messages)
109115
factory.makeMessageContainerView(
110116
channel: channel,
111117
message: message,
@@ -117,16 +123,25 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
117123
onLongPress: handleLongPress(messageDisplayInfo:),
118124
isLast: message == messages.last
119125
)
120-
.flippedUpsideDown()
121126
.onAppear {
122-
let index = messages.firstIndex { msg in
123-
msg.id == message.id
127+
if index == nil {
128+
index = messageListDateUtils.index(for: message, in: messages)
124129
}
125-
126130
if let index = index {
127131
onMessageAppear(index)
128132
}
129133
}
134+
.overlay(
135+
messageDate != nil ?
136+
VStack {
137+
factory.makeMessageListDateIndicator(date: messageDate!)
138+
.offset(y: -messageListConfig.messageDisplayOptions.dateLabelSize)
139+
Spacer()
140+
}
141+
: nil
142+
)
143+
.padding(.top, messageDate != nil ? messageListConfig.messageDisplayOptions.dateLabelSize : 0)
144+
.flippedUpsideDown()
130145
}
131146
.id(listId)
132147
}
@@ -303,11 +318,19 @@ public struct DateIndicatorView: View {
303318
@Injected(\.colors) private var colors
304319
@Injected(\.fonts) private var fonts
305320

306-
var date: String
321+
var dateString: String
322+
323+
public init(date: Date) {
324+
dateString = DateFormatter.messageListDateOverlay.string(from: date)
325+
}
326+
327+
public init(dateString: String) {
328+
self.dateString = dateString
329+
}
307330

308331
public var body: some View {
309332
VStack {
310-
Text(date)
333+
Text(dateString)
311334
.font(fonts.footnote)
312335
.padding(.vertical, 4)
313336
.padding(.horizontal, 8)

Sources/StreamChatSwiftUI/DefaultViewFactory.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,11 @@ extension ViewFactory {
412412
}
413413

414414
public func makeDateIndicatorView(dateString: String) -> some View {
415-
DateIndicatorView(date: dateString)
415+
DateIndicatorView(dateString: dateString)
416+
}
417+
418+
public func makeMessageListDateIndicator(date: Date) -> some View {
419+
DateIndicatorView(date: date)
416420
}
417421

418422
public func makeGiphyBadgeViewType(

Sources/StreamChatSwiftUI/Utils.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class Utils {
2424
public var shouldSyncChannelControllerOnAppear: (ChatChannelController) -> Bool
2525

2626
var messageCachingUtils = MessageCachingUtils()
27+
var messageListDateUtils: MessageListDateUtils
2728

2829
public init(
2930
dateFormatter: DateFormatter = .makeDefault(),
@@ -55,5 +56,6 @@ public class Utils {
5556
self.messageListConfig = messageListConfig
5657
self.composerConfig = composerConfig
5758
self.shouldSyncChannelControllerOnAppear = shouldSyncChannelControllerOnAppear
59+
messageListDateUtils = MessageListDateUtils(messageListConfig: messageListConfig)
5860
}
5961
}

Sources/StreamChatSwiftUI/Utils/Common/DateFormatter+Extensions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension DateFormatter {
1515

1616
/// Formatter that is used to format date for scrolling overlay that should display
1717
/// day when message below was sent
18-
public static let messageListDateOverlay: DateFormatter = {
18+
public static var messageListDateOverlay: DateFormatter = {
1919
let df = DateFormatter()
2020
df.setLocalizedDateFormatFromTemplate("MMMdd")
2121
df.locale = .autoupdatingCurrent

Sources/StreamChatSwiftUI/ViewFactory.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,12 @@ public protocol ViewFactory: AnyObject {
418418
/// - Parameter dateString: the displayed date string.
419419
/// - Returns: view in the date indicator slot.
420420
func makeDateIndicatorView(dateString: String) -> DateIndicatorViewType
421+
422+
associatedtype MessageListDateIndicatorViewType: View
423+
/// Creates the date indicator view in the message list.
424+
/// - Parameter date: the date that will be displayed.
425+
/// - Returns: view shown above messages separated by date.
426+
func makeMessageListDateIndicator(date: Date) -> MessageListDateIndicatorViewType
421427

422428
associatedtype GiphyBadgeViewType: View
423429
/// Creates giphy badge view.

0 commit comments

Comments
 (0)