Skip to content

Commit e64c67f

Browse files
authored
Composable approach for ChatMessageHeaderDecoratorView (#2534)
* Follow composable approach to create ChatMessageHeaderDecoratorView * Fix flacky unit test * Address PR comments * Address PR comments * Update specs * Address PR comments * Address PR comments
1 parent f29ef69 commit e64c67f

File tree

17 files changed

+231
-107
lines changed

17 files changed

+231
-107
lines changed

Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ extension NSManagedObjectContext {
136136
return
137137
}
138138

139-
let lastReadAt = lastReadAt?.bridgeDate ?? message.createdAt
140-
read.lastReadAt = lastReadAt
139+
let lastReadAt = lastReadAt ?? message.createdAt.bridgeDate
140+
read.lastReadAt = lastReadAt.bridgeDate
141141

142142
let messagesCount = unreadMessagesCount ?? MessageDTO.countOtherUserMessages(
143143
in: read.channel.cid,

Sources/StreamChat/Database/DTOs/MessageDTO.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,12 +448,12 @@ class MessageDTO: NSManagedObject {
448448

449449
static func countOtherUserMessages(
450450
in cid: String,
451-
createdAtFrom: DBDate,
451+
createdAtFrom: Date,
452452
context: NSManagedObjectContext
453453
) -> Int {
454454
let subpredicates: [NSPredicate] = [
455455
sentMessagesPredicate(for: cid),
456-
.init(format: "createdAt >= %@", createdAtFrom),
456+
.init(format: "createdAt >= %@", createdAtFrom.bridgeDate),
457457
.init(format: "user.currentUser == nil")
458458
]
459459

Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -290,27 +290,22 @@ open class ChatChannelVC: _ViewController,
290290
headerViewForMessage message: ChatMessage,
291291
at indexPath: IndexPath
292292
) -> ChatMessageDecorationView? {
293-
let dateHeaderView = dateHeaderView(
294-
vc,
295-
headerViewForMessage: message,
296-
at: indexPath,
297-
components: components
298-
)
293+
let shouldShowDate = vc.shouldShowDateSeparator(forMessage: message, at: indexPath)
294+
let shouldShowUnreadMessages = message.id == firstUnreadMessageId
299295

300-
var unreadMessagesView: ChatMessageDecorationView?
301-
if message.id == firstUnreadMessageId, let unreadCount = channelController.channel?.unreadCount.messages {
302-
let view = components.unreadMessagesCounterDecorationView.init()
303-
view.content = L10n.Message.Unread.count(unreadCount)
304-
unreadMessagesView = view
296+
guard (shouldShowDate || shouldShowUnreadMessages), let channel = channelController.channel else {
297+
return nil
305298
}
306299

307-
guard let dateView = dateHeaderView, let unreadView = unreadMessagesView else {
308-
return dateHeaderView ?? unreadMessagesView
309-
}
310-
311-
let stackView = StackViewDecoratorView()
312-
stackView.content = [dateView, unreadView]
313-
return stackView
300+
let header = components.messageHeaderDecorationView.init()
301+
header.content = ChatChannelMessageHeaderDecoratorViewContent(
302+
message: message,
303+
channel: channel,
304+
dateFormatter: vc.dateSeparatorFormatter,
305+
shouldShowDate: shouldShowDate,
306+
shouldShowUnreadMessages: shouldShowUnreadMessages
307+
)
308+
return header
314309
}
315310

316311
open func chatMessageListVC(
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// Copyright © 2023 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamChat
6+
import UIKit
7+
8+
public struct ChatChannelMessageHeaderDecoratorViewContent {
9+
public let message: ChatMessage
10+
public let channel: ChatChannel
11+
public let dateFormatter: MessageDateSeparatorFormatter
12+
public let shouldShowDate: Bool
13+
public let shouldShowUnreadMessages: Bool
14+
15+
public init(
16+
message: ChatMessage,
17+
channel: ChatChannel,
18+
dateFormatter: MessageDateSeparatorFormatter,
19+
shouldShowDate: Bool,
20+
shouldShowUnreadMessages: Bool
21+
) {
22+
self.message = message
23+
self.channel = channel
24+
self.dateFormatter = dateFormatter
25+
self.shouldShowDate = shouldShowDate
26+
self.shouldShowUnreadMessages = shouldShowUnreadMessages
27+
}
28+
}
29+
30+
/// The decorator view that is used as a container for the chat message header view decorators.
31+
public final class ChatChannelMessageHeaderDecoratorView: ChatMessageDecorationView, ThemeProvider {
32+
/// The container for the stacked views.
33+
public private(set) lazy var container = UIStackView()
34+
.withoutAutoresizingMaskConstraints
35+
.withAccessibilityIdentifier(identifier: "chatMessageHeaderDecoratorView")
36+
37+
public private(set) lazy var dateView = components.messageListDateSeparatorView.init()
38+
.withoutAutoresizingMaskConstraints
39+
public private(set) lazy var unreadCountView = components.unreadMessagesCounterDecorationView.init()
40+
.withoutAutoresizingMaskConstraints
41+
42+
public var content: ChatChannelMessageHeaderDecoratorViewContent? {
43+
didSet {
44+
updateContentIfNeeded()
45+
}
46+
}
47+
48+
override public func setUpLayout() {
49+
super.setUpLayout()
50+
embed(container, insets: .init(top: 0, leading: 0, bottom: 0, trailing: 0))
51+
container.axis = .vertical
52+
container.spacing = 4
53+
54+
[dateView, unreadCountView].forEach(container.addArrangedSubview)
55+
}
56+
57+
override public func setUpAppearance() {
58+
super.setUpAppearance()
59+
backgroundColor = nil
60+
}
61+
62+
override public func updateContent() {
63+
super.updateContent()
64+
dateView.isVisible = content?.shouldShowDate ?? false
65+
unreadCountView.isVisible = content?.shouldShowUnreadMessages ?? false
66+
dateView.content = content.map { $0.dateFormatter.format($0.message.createdAt) }
67+
unreadCountView.content = content?.channel
68+
}
69+
}

Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessagesCountDecorationView.swift renamed to Sources/StreamChatUI/ChatMessageList/ChatMessage/Decorators/ChatMessagesCountDecorationView.swift

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ import UIKit
77

88
/// The view used to show a separator when there are unread messages.
99
open class ChatMessagesCountDecorationView: ChatMessageDecorationView, AppearanceProvider {
10-
/// The string to be shown in the view
11-
open var content: String? {
12-
didSet { updateContentIfNeeded() }
13-
}
14-
1510
/// The container that the contentTextLabel will be placed aligned to its centre.
1611
open private(set) lazy var container: UIView = UIView()
1712
.withoutAutoresizingMaskConstraints
@@ -27,7 +22,7 @@ open class ChatMessagesCountDecorationView: ChatMessageDecorationView, Appearanc
2722
override open func setUpLayout() {
2823
super.setUpLayout()
2924

30-
embed(container, insets: .init(top: 0, leading: 0, bottom: 8, trailing: 0))
25+
embed(container)
3126
container.embed(textLabel, insets: .init(top: 3, leading: 9, bottom: 3, trailing: 9))
3227
}
3328

@@ -41,10 +36,4 @@ open class ChatMessagesCountDecorationView: ChatMessageDecorationView, Appearanc
4136
textLabel.textColor = appearance.colorPalette.textLowEmphasis
4237
textLabel.textAlignment = .center
4338
}
44-
45-
override open func updateContent() {
46-
super.updateContent()
47-
48-
textLabel.text = content
49-
}
5039
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Copyright © 2023 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamChat
6+
import UIKit
7+
8+
/// The decorator view that is used to display the replies count in a thread
9+
open class ChatThreadRepliesCountDecorationView: ChatMessageDecorationView, ThemeProvider {
10+
public var content: ChatMessage? {
11+
didSet {
12+
updateContentIfNeeded()
13+
}
14+
}
15+
16+
lazy var messagesCountDecorationView = components.messagesCountDecorationView.init()
17+
.withoutAutoresizingMaskConstraints
18+
19+
override open func setUpLayout() {
20+
super.setUpLayout()
21+
22+
embed(messagesCountDecorationView, insets: .init(top: 0, leading: 0, bottom: 8, trailing: 0))
23+
}
24+
25+
override open func updateContent() {
26+
super.updateContent()
27+
28+
messagesCountDecorationView.textLabel.text = L10n.Message.Thread.Replies.count(content?.replyCount ?? 0)
29+
}
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// Copyright © 2023 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamChat
6+
import UIKit
7+
8+
/// The decorator view that is used to display the unread messages count in a channel.
9+
open class ChatUnreadMessagesCountDecorationView: ChatMessageDecorationView, ThemeProvider {
10+
public var content: ChatChannel? {
11+
didSet {
12+
updateContentIfNeeded()
13+
}
14+
}
15+
16+
lazy var messagesCountDecorationView = components.messagesCountDecorationView.init()
17+
.withoutAutoresizingMaskConstraints
18+
19+
override open func setUpLayout() {
20+
super.setUpLayout()
21+
22+
embed(messagesCountDecorationView, insets: .init(top: 8, leading: 0, bottom: 0, trailing: 0))
23+
}
24+
25+
override open func updateContent() {
26+
super.updateContent()
27+
28+
let unreadCount = content?.unreadCount.messages ?? 0
29+
messagesCountDecorationView.textLabel.text = L10n.Message.Unread.count(unreadCount)
30+
}
31+
}

Sources/StreamChatUI/ChatMessageList/ChatMessage/StackViewDecoratorView.swift

Lines changed: 0 additions & 38 deletions
This file was deleted.

Sources/StreamChatUI/ChatThread/ChatThreadVC.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ open class ChatThreadVC: _ViewController,
282282
return nil
283283
}
284284
let repliesCounterDecorationView = components.threadRepliesCounterDecorationView.init()
285-
repliesCounterDecorationView.content = L10n.Message.Thread.Replies.count(message.replyCount)
285+
repliesCounterDecorationView.content = message
286286
return repliesCounterDecorationView
287287
}
288288

0 commit comments

Comments
 (0)