Skip to content

Commit 38a349b

Browse files
Merge branch 'performance-improvements'
2 parents 81ad26c + e3fc906 commit 38a349b

36 files changed

+473
-131
lines changed

DemoAppSwiftUI/AppDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import UIKit
1010
class AppDelegate: NSObject, UIApplicationDelegate {
1111

1212
var streamChat: StreamChat?
13-
13+
1414
var chatClient: ChatClient = {
1515
var config = ChatClientConfig(apiKey: .init(apiKeyString))
1616
// config.isLocalStorageEnabled = true

DemoAppSwiftUI/CreateGroupView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ struct SelectedUserGroupView: View {
8787
var body: some View {
8888
VStack {
8989
MessageAvatarView(
90-
author: user,
90+
avatarURL: user.imageURL,
9191
size: CGSize(width: avatarSize, height: avatarSize)
9292
)
9393

DemoAppSwiftUI/CustomChannelHeader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public struct CustomChannelHeader: ToolbarContent {
3535
logoutAlertShown = true
3636
} label: {
3737
LazyImage(source: currentUserController.currentUser?.imageURL)
38-
.onDisappear(.reset)
38+
.onDisappear(.cancel)
3939
.clipShape(Circle())
4040
.frame(
4141
width: 30,

DemoAppSwiftUI/LoginView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct DemoUserView: View {
5858
var body: some View {
5959
HStack {
6060
LazyImage(source: user.avatarURL)
61-
.onDisappear(.reset)
61+
.onDisappear(.cancel)
6262
.clipShape(Circle())
6363
.frame(
6464
width: imageSize,

DemoAppSwiftUI/NewChatView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ struct SelectedUserView: View {
113113
var body: some View {
114114
HStack {
115115
MessageAvatarView(
116-
author: user,
116+
avatarURL: user.imageURL,
117117
size: CGSize(width: 20, height: 20)
118118
)
119119

@@ -200,7 +200,7 @@ struct ChatUserView: View {
200200
var body: some View {
201201
HStack {
202202
LazyView(
203-
MessageAvatarView(author: user)
203+
MessageAvatarView(avatarURL: user.imageURL)
204204
)
205205

206206
VStack(alignment: .leading, spacing: 4) {

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelDataSource.swift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ protocol MessagesDataSource: AnyObject {
1414
/// - messages, the collection of updated messages.
1515
func dataSource(
1616
channelDataSource: ChannelDataSource,
17-
didUpdateMessages messages: LazyCachedMapCollection<ChatMessage>
17+
didUpdateMessages messages: LazyCachedMapCollection<ChatMessage>,
18+
changes: [ListChange<ChatMessage>]
1819
)
1920

2021
/// Called when the channel is updated.
@@ -70,7 +71,8 @@ class ChatChannelDataSource: ChannelDataSource, ChatChannelControllerDelegate {
7071
) {
7172
delegate?.dataSource(
7273
channelDataSource: self,
73-
didUpdateMessages: channelController.messages
74+
didUpdateMessages: channelController.messages,
75+
changes: changes
7476
)
7577
}
7678

@@ -117,22 +119,34 @@ class MessageThreadDataSource: ChannelDataSource, ChatMessageControllerDelegate
117119
self.messageController.delegate = self
118120
self.messageController.loadPreviousReplies { [weak self] _ in
119121
guard let self = self else { return }
120-
self.delegate?.dataSource(channelDataSource: self, didUpdateMessages: self.messages)
122+
self.delegate?.dataSource(
123+
channelDataSource: self,
124+
didUpdateMessages: self.messages,
125+
changes: []
126+
)
121127
}
122128
}
123129

124130
func messageController(
125131
_ controller: ChatMessageController,
126132
didChangeReplies changes: [ListChange<ChatMessage>]
127133
) {
128-
delegate?.dataSource(channelDataSource: self, didUpdateMessages: controller.replies)
134+
delegate?.dataSource(
135+
channelDataSource: self,
136+
didUpdateMessages: controller.replies,
137+
changes: changes
138+
)
129139
}
130140

131141
func messageController(
132142
_ controller: ChatMessageController,
133143
didChangeMessage change: EntityChange<ChatMessage>
134144
) {
135-
delegate?.dataSource(channelDataSource: self, didUpdateMessages: controller.replies)
145+
delegate?.dataSource(
146+
channelDataSource: self,
147+
didUpdateMessages: controller.replies,
148+
changes: []
149+
)
136150
}
137151

138152
func loadPreviousMessages(

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ public struct ChatChannelView<Factory: ViewFactory>: View, KeyboardReadable {
5555
}
5656
}
5757
)
58+
.overlay(
59+
viewModel.currentDateString != nil ?
60+
factory.makeDateIndicatorView(dateString: viewModel.currentDateString!)
61+
: nil
62+
)
5863

5964
Divider()
6065
.navigationBarBackButtonHidden(viewModel.reactionsShown)

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 84 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
2121
private var timer: Timer?
2222
private var currentDate: Date? {
2323
didSet {
24-
guard showScrollToLatestButton == true, let currentDate = currentDate else {
25-
currentDateString = nil
26-
return
27-
}
28-
currentDateString = messageListDateOverlay.string(from: currentDate)
24+
handleDateChange()
2925
}
3026
}
3127

@@ -39,9 +35,10 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
3935
}()
4036

4137
private lazy var messagesDateFormatter = utils.dateFormatter
38+
private lazy var messageCachingUtils = utils.messageCachingUtils
4239

43-
@Atomic private var loadingPreviousMessages: Bool = false
44-
@Atomic private var lastMessageRead: String?
40+
private var loadingPreviousMessages: Bool = false
41+
private var lastMessageRead: String?
4542

4643
public var channelController: ChatChannelController
4744
public var messageController: ChatMessageController?
@@ -54,27 +51,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
5451
@Published public var currentDateString: String?
5552
@Published public var messages = LazyCachedMapCollection<ChatMessage>() {
5653
didSet {
57-
var temp = [String: [String]]()
58-
for (index, message) in messages.enumerated() {
59-
let dateString = messagesDateFormatter.string(from: message.createdAt)
60-
let prefix = message.author.id
61-
let key = "\(prefix)-\(dateString)"
62-
if temp[key] == nil {
63-
temp[key] = [message.id]
64-
} else {
65-
// check if the previous message is not sent by the same user.
66-
let previousIndex = index - 1
67-
if previousIndex >= 0 {
68-
let previous = messages[previousIndex]
69-
70-
let shouldAddKey = message.author.id != previous.author.id
71-
if shouldAddKey {
72-
temp[key]?.append(message.id)
73-
}
74-
}
75-
}
54+
if utils.messageListConfig.groupMessages {
55+
groupMessages()
7656
}
77-
messagesGroupingInfo = temp
7857
}
7958
}
8059

@@ -144,6 +123,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
144123
@objc
145124
private func didReceiveMemoryWarning() {
146125
Nuke.ImageCache.shared.removeAll()
126+
messageCachingUtils.clearCache()
147127
}
148128

149129
public func scrollToLastMessage() {
@@ -155,15 +135,18 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
155135
public func handleMessageAppear(index: Int) {
156136
let message = messages[index]
157137
checkForNewMessages(index: index)
158-
save(lastDate: message.createdAt)
138+
if utils.messageListConfig.dateIndicatorPlacement == .overlay {
139+
save(lastDate: message.createdAt)
140+
}
159141
if index == 0 {
160142
maybeSendReadEvent(for: message)
161143
}
162144
}
163145

164146
func dataSource(
165147
channelDataSource: ChannelDataSource,
166-
didUpdateMessages messages: LazyCachedMapCollection<ChatMessage>
148+
didUpdateMessages messages: LazyCachedMapCollection<ChatMessage>,
149+
changes: [ListChange<ChatMessage>]
167150
) {
168151
if !isActive {
169152
return
@@ -172,11 +155,13 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
172155
if let message = messageController?.message {
173156
var array = Array(messages)
174157
array.append(message)
175-
withAnimation {
176-
self.messages = LazyCachedMapCollection(source: array, map: { $0 })
177-
}
158+
self.messages = LazyCachedMapCollection(source: array, map: { $0 })
178159
} else {
179-
withAnimation {
160+
if shouldAnimate(changes: changes) {
161+
withAnimation {
162+
self.messages = messages
163+
}
164+
} else {
180165
self.messages = messages
181166
}
182167
}
@@ -193,7 +178,6 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
193178
didUpdateChannel channel: EntityChange<ChatChannel>,
194179
channelController: ChatChannelController
195180
) {
196-
messages = channelController.messages
197181
checkHeaderType()
198182
}
199183

@@ -228,14 +212,15 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
228212
// MARK: - private
229213

230214
private func checkForNewMessages(index: Int) {
231-
if index < channelDataSource.messages.count - 20 {
215+
if index < channelDataSource.messages.count - 25 {
232216
return
233217
}
234218

235-
if _loadingPreviousMessages.compareAndSwap(old: false, new: true) {
219+
if !loadingPreviousMessages {
220+
loadingPreviousMessages = true
236221
channelDataSource.loadPreviousMessages(
237222
before: nil,
238-
limit: refreshThreshold,
223+
limit: utils.messageListConfig.pageSize,
239224
completion: { [weak self] _ in
240225
guard let self = self else { return }
241226
self.loadingPreviousMessages = false
@@ -296,20 +281,69 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
296281
}
297282
}
298283
}
284+
285+
private func groupMessages() {
286+
var temp = [String: [String]]()
287+
for (index, message) in messages.enumerated() {
288+
let dateString = messagesDateFormatter.string(from: message.createdAt)
289+
let prefix = messageCachingUtils.authorId(for: message)
290+
let key = "\(prefix)-\(dateString)"
291+
if temp[key] == nil {
292+
temp[key] = [message.id]
293+
} else {
294+
// check if the previous message is not sent by the same user.
295+
let previousIndex = index - 1
296+
if previousIndex >= 0 {
297+
let previous = messages[previousIndex]
298+
let previousAuthorId = messageCachingUtils.authorId(for: previous)
299+
let shouldAddKey = prefix != previousAuthorId
300+
if shouldAddKey {
301+
temp[key]?.append(message.id)
302+
}
303+
}
304+
}
305+
}
306+
307+
messagesGroupingInfo = temp
308+
}
309+
310+
private func handleDateChange() {
311+
guard showScrollToLatestButton == true, let currentDate = currentDate else {
312+
currentDateString = nil
313+
return
314+
}
315+
316+
let dateString = messageListDateOverlay.string(from: currentDate)
317+
if currentDateString != dateString {
318+
currentDateString = dateString
319+
}
320+
}
321+
322+
private func shouldAnimate(changes: [ListChange<ChatMessage>]) -> Bool {
323+
for change in changes {
324+
switch change {
325+
case .insert(_, index: _),
326+
.remove(_, index: _):
327+
return true
328+
default:
329+
log.debug("detected non-animatable change")
330+
}
331+
}
332+
333+
return false
334+
}
335+
336+
deinit {
337+
messageCachingUtils.clearCache()
338+
}
299339
}
300340

301341
extension ChatMessage: Identifiable {
302342
var messageId: String {
303-
let statesId = uploadingStatesId
304-
305-
if statesId.isEmpty {
306-
if !reactionScores.isEmpty {
307-
return baseId + reactionScoresId
308-
} else {
309-
return baseId
310-
}
343+
var statesId = "empty"
344+
if localState != nil {
345+
statesId = uploadingStatesId
311346
}
312-
313347
return baseId + statesId + reactionScoresId + repliesCountId + "\(updatedAt)" + pinStateId
314348
}
315349

@@ -337,7 +371,7 @@ extension ChatMessage: Identifiable {
337371
states += fileAttachments.compactMap { $0.uploadingState?.state }
338372

339373
if states.isEmpty {
340-
return ""
374+
return "empty"
341375
}
342376

343377
let strings = states.map { "\($0)" }
@@ -347,6 +381,9 @@ extension ChatMessage: Identifiable {
347381

348382
var reactionScoresId: String {
349383
var output = ""
384+
if reactionScores.isEmpty {
385+
return output
386+
}
350387
let sorted = reactionScores.keys.sorted { type1, type2 in
351388
type1.id > type2.id
352389
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/Suggestions/Mentions/MentionUsersView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public struct MentionUserView: View {
5555
public var body: some View {
5656
HStack {
5757
MessageAvatarView(
58-
author: user,
58+
avatarURL: user.imageURL,
5959
showOnlineIndicator: true
6060
)
6161
Text(user.name ?? user.id)

Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentView.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import StreamChat
66
import SwiftUI
77

88
public struct FileAttachmentsContainer: View {
9+
10+
@Injected(\.utils) private var utils
11+
912
var message: ChatMessage
1013
var width: CGFloat
1114
var isFirst: Bool
1215
@Binding var scrolledId: String?
1316

1417
public var body: some View {
1518
VStack(alignment: message.alignmentInBubble) {
16-
if let quotedMessage = message.quotedMessage {
19+
if let quotedMessage = utils.messageCachingUtils.quotedMessage(for: message) {
1720
QuotedMessageViewContainer(
1821
quotedMessage: quotedMessage,
1922
fillAvailableSpace: !message.attachmentCounts.isEmpty,

0 commit comments

Comments
 (0)