Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Expose `QuotedMessageViewContainer` [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056)
- Add `QuotedMessageContentView` and `ViewFactory.makeQuotedMessageContentView()` [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056)
- Allow customizing the attachment size and avatar size of the quoted message view [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056)
### 🐞 Fixed
- Fix channel list skipping some updates on iPad [#1059](https://github.com/GetStream/stream-chat-swiftui/pull/1059)

# [4.93.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.93.0)
_November 18, 2025_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,42 +25,40 @@

/// Used when screen is shown from a deeplink.
private var selectedChannelId: String?

/// Temporarly holding changes while message list is shown.
private var queuedChannelsChanges = LazyCachedMapCollection<ChatChannel>()


private var timer: Timer?

/// Controls loading the channels.
public private(set) var loadingNextChannels: Bool = false

/// Checks if the queued changes are completely applied.
private var markDirty = false

/// True, if channel updates were skipped and are applied when selectedChannel is set to nil
private var skippedChannelUpdates = false

/// True, if channel updates can be skipped for optimizing view refreshes while showing message list.
///
/// - Important: Only meant for stacked navigation view style.
private var canSkipChannelUpdates: Bool {
guard isIphone || !utils.messageListConfig.iPadSplitViewEnabled else { return false }
guard selectedChannel != nil || !searchText.isEmpty else { return false }
return true
}

/// Index of the selected channel.
private var selectedChannelIndex: Int?

/// When set, scrolls to the specified channel id (if it exists).
@Published public var scrolledChannelId: String?

/// Published variables.
@Published public var channels = LazyCachedMapCollection<ChatChannel>() {
didSet {
if !markDirty {
queuedChannelsChanges = []
} else {
markDirty = false
}
}
}
@Published public var channels = LazyCachedMapCollection<ChatChannel>()

@Published public var selectedChannel: ChannelSelectionInfo? {
willSet {
hideTabBar = newValue != nil
if selectedChannel != nil && newValue == nil {
// pop happened, apply the queued changes.
if !queuedChannelsChanges.isEmpty {
channels = queuedChannelsChanges
if skippedChannelUpdates {
updateChannels()
}
}
if newValue == nil {
Expand Down Expand Up @@ -317,8 +315,8 @@
// MARK: - private

private func handleChannelListChanges(_ controller: ChatChannelListController) {
if selectedChannel != nil || !searchText.isEmpty {
queuedChannelsChanges = controller.channels
if canSkipChannelUpdates {
skippedChannelUpdates = true
updateChannelsIfNeeded()
} else {
channels = controller.channels
Expand Down Expand Up @@ -537,15 +535,16 @@
}

private func updateChannels() {
skippedChannelUpdates = false
channels = controller?.channels ?? LazyCachedMapCollection<ChatChannel>()
}

private func handleChannelAppearance() {
if !queuedChannelsChanges.isEmpty && selectedChannel == nil {
channels = queuedChannelsChanges
} else if !queuedChannelsChanges.isEmpty {
handleQueuedChanges()
} else if queuedChannelsChanges.isEmpty && selectedChannel != nil {
if skippedChannelUpdates && selectedChannel == nil {
updateChannels()
} else if skippedChannelUpdates {
updateSelectedChannelData()
} else if !skippedChannelUpdates && selectedChannel != nil {

Check warning on line 547 in Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this if statement with the nested one.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-chat-swiftui&issues=AZrEoajjBsDskKfEPySw&open=AZrEoajjBsDskKfEPySw&pullRequest=1059
if selectedChannel?.injectedChannelInfo == nil {
selectedChannel?.injectedChannelInfo = InjectedChannelInfo(unreadCount: 0)
}
Expand All @@ -562,10 +561,10 @@
}
}

private func handleQueuedChanges() {
private func updateSelectedChannelData() {
let selected = selectedChannel?.channel
var index: Int?
var temp = Array(queuedChannelsChanges)
var temp = Array(controller?.channels ?? [])
for i in 0..<temp.count {
let current = temp[i]
if current.cid == selected?.cid {
Expand All @@ -583,7 +582,6 @@
if let index = index, let selected = selected {
temp[index] = selected
}
markDirty = true
channels = LazyCachedMapCollection(source: temp, map: { $0 })
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,36 @@ class ChatChannelListViewModel_Tests: StreamChatTestCase {
// Then
XCTAssertNil(viewModel.selectedChannel, "selectedChannel should be cleared immediately when opening another channel")
}

// MARK: - Optimized Channel List Updates

func test_channelListOptimizedUpdates_whenStackedViewAndSelection_thenUpdatesSkipped() {
// Given
let config = MessageListConfig(updateChannelsFromMessageList: false, iPadSplitViewEnabled: false)
streamChat = StreamChat(chatClient: chatClient, utils: Utils(messageListConfig: config))
let existingChannel = ChatChannel.mockDMChannel()
let channelListController = makeChannelListController(channels: [existingChannel])

// When
let viewModel = ChatChannelListViewModel(
channelListController: channelListController,
selectedChannelId: nil
)
viewModel.selectedChannel = .init(channel: existingChannel, message: nil)
let insertedChannel = ChatChannel.mockDMChannel()
channelListController.simulate(
channels: [insertedChannel, existingChannel],
changes: [.insert(insertedChannel, index: IndexPath(item: 0, section: 0))]
)

// Then
XCTAssertEqual(viewModel.channels.count, 1)

// When selection is popped, changes are applied
viewModel.selectedChannel = nil

XCTAssertEqual(viewModel.channels.count, 2)
}

// MARK: - private

Expand Down
Loading