Skip to content

Commit 9938819

Browse files
authored
Improve list updates (#522)
1 parent 1059365 commit 9938819

File tree

8 files changed

+49
-36
lines changed

8 files changed

+49
-36
lines changed

CHANGELOG.md

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

44
# Upcoming
55

6-
### 🔄 Changed
6+
### 🐞 Fixed
7+
- Smoother and more performant view updates in channel and message lists [#522](https://github.com/GetStream/stream-chat-swiftui/pull/522)
78

89
# [4.58.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.58.0)
910
_June 27, 2024_

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,19 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
273273
scrolledId = nil
274274
return true
275275
} else {
276-
guard let baseId = messageId.components(separatedBy: "$").first else {
276+
let findBaseId: String? = {
277+
if StreamRuntimeCheck._isDatabaseObserverItemReusingEnabled {
278+
return messageId
279+
} else {
280+
return messageId.components(separatedBy: "$").first
281+
}
282+
}()
283+
guard let baseId = findBaseId else {
277284
scrolledId = nil
278285
return true
279286
}
280287
let alreadyLoaded = messages.map(\.id).contains(baseId)
281-
if alreadyLoaded && baseId != messageId {
288+
if alreadyLoaded {
282289
if scrolledId == nil {
283290
scrolledId = messageId
284291
}
@@ -405,12 +412,16 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
405412
return
406413
}
407414

408-
let animationState = shouldAnimate(changes: changes)
409-
if animationState == .animated {
415+
// Set unread state before updating messages for ensuring the state is up to date before `handleMessageAppear` is called
416+
if lastReadMessageId != nil && firstUnreadMessageId == nil {
417+
firstUnreadMessageId = channelDataSource.firstUnreadMessageId
418+
}
419+
420+
if shouldAnimate(changes: changes) {
410421
withAnimation {
411422
self.messages = messages
412423
}
413-
} else if animationState == .notAnimated {
424+
} else {
414425
self.messages = messages
415426
}
416427

@@ -419,10 +430,6 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
419430
if !showScrollToLatestButton && scrolledId == nil && !loadingNextMessages {
420431
updateScrolledIdToNewestMessage()
421432
}
422-
423-
if lastReadMessageId != nil && firstUnreadMessageId == nil {
424-
firstUnreadMessageId = channelDataSource.firstUnreadMessageId
425-
}
426433
}
427434

428435
func dataSource(
@@ -649,42 +656,35 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
649656
}
650657
}
651658

652-
private func shouldAnimate(changes: [ListChange<ChatMessage>]) -> AnimationChange {
659+
private func shouldAnimate(changes: [ListChange<ChatMessage>]) -> Bool {
653660
if !utils.messageListConfig.messageDisplayOptions.animateChanges || loadingNextMessages {
654-
return .notAnimated
661+
return false
655662
}
656663

657-
var skipChanges = true
658664
var animateChanges = false
659665
for change in changes {
660666
switch change {
661667
case .insert(_, index: _),
662668
.remove(_, index: _):
663-
return .animated
669+
return true
664670
case let .update(message, index: index):
671+
let animateReactions = message.reactionScoresId != messages[index.row].reactionScoresId
672+
&& utils.messageListConfig.messageDisplayOptions.shouldAnimateReactions
665673
if index.row < messages.count,
666674
message.messageId != messages[index.row].messageId
667675
|| message.type == .ephemeral
668676
|| !message.linkAttachments.isEmpty {
669-
skipChanges = false
670677
if index.row < messages.count
671-
&& (
672-
message.reactionScoresId != messages[index.row].reactionScoresId
673-
&& utils.messageListConfig.messageDisplayOptions.shouldAnimateReactions
674-
) {
678+
&& animateReactions {
675679
animateChanges = message.linkAttachments.isEmpty
676680
}
677681
}
678682
default:
679-
skipChanges = false
683+
break
680684
}
681685
}
682686

683-
if skipChanges {
684-
return .skip
685-
}
686-
687-
return animateChanges ? .animated : .notAnimated
687+
return animateChanges
688688
}
689689

690690
private func enableDateIndicator() {
@@ -814,12 +814,6 @@ public enum ChannelHeaderType {
814814
case typingIndicator
815815
}
816816

817-
enum AnimationChange {
818-
case animated
819-
case notAnimated
820-
case skip
821-
}
822-
823817
let firstMessageKey = "firstMessage"
824818
let lastMessageKey = "lastMessage"
825819

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageIdBuilder.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public class DefaultMessageIdBuilder: MessageIdBuilder {
1616
public init() { /* Public init. */ }
1717

1818
public func makeMessageId(for message: ChatMessage) -> String {
19+
if StreamRuntimeCheck._isDatabaseObserverItemReusingEnabled {
20+
return message.id
21+
}
1922
var statesId = "empty"
2023
if message.localState != nil {
2124
statesId = message.uploadingStatesId

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageView.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,19 @@ struct StreamTextView: View {
229229

230230
@Injected(\.fonts) var fonts
231231

232-
var message: ChatMessage
232+
let message: ChatMessage
233+
private let adjustedText: String
234+
235+
init(message: ChatMessage) {
236+
self.message = message
237+
adjustedText = message.adjustedText
238+
}
233239

234240
var body: some View {
235241
if #available(iOS 15, *) {
236242
LinkDetectionTextView(message: message)
237243
} else {
238-
Text(message.adjustedText)
244+
Text(adjustedText)
239245
.foregroundColor(textColor(for: message))
240246
.font(fonts.body)
241247
}

Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ extension MessageAction {
3333
)
3434
return messageActions
3535
} else if message.localState == .pendingSend
36-
&& message.messageId.contains("\(LocalAttachmentState.uploadingFailed)") {
36+
&& message.allAttachments.contains(where: { $0.uploadingState?.state == .uploadingFailed }) {
3737
messageActions = editAndDeleteActions(
3838
for: message,
3939
channel: channel,

Sources/StreamChatSwiftUI/StreamChat.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ public class StreamChat {
1717
appearance: Appearance = Appearance(),
1818
utils: Utils = Utils()
1919
) {
20-
StreamRuntimeCheck._isDatabaseObserverItemReusingEnabled = false
2120
self.chatClient = chatClient
2221
self.appearance = appearance
2322
self.utils = utils

StreamChatSwiftUITests/Tests/ChatChannel/ChatMessageIDs_Tests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import XCTest
88

99
class ChatMessageIDs_Tests: StreamChatTestCase {
1010

11+
override func setUpWithError() throws {
12+
try super.setUpWithError()
13+
StreamRuntimeCheck._isDatabaseObserverItemReusingEnabled = false
14+
}
15+
16+
override func tearDownWithError() throws {
17+
StreamRuntimeCheck._isDatabaseObserverItemReusingEnabled = true
18+
try super.tearDownWithError()
19+
}
20+
1121
func test_chatMessage_reactionScoresId() {
1222
// Given
1323
let id: String = .unique

StreamChatSwiftUITestsAppTests/Tests/Base TestCase/StreamTestCase.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ extension StreamTestCase {
9090
return
9191
}
9292
server.stop()
93-
MockServerConfiguration.port = UInt16(Int.random(in: 61000..<62000))
93+
MockServerConfiguration.port = UInt16.random(in: 61000..<62000)
9494
}
9595

9696
mockServerCrashed = true

0 commit comments

Comments
 (0)