Skip to content

Commit 8dbcad5

Browse files
Implemented updating channels before screen display
1 parent e2cb4f3 commit 8dbcad5

File tree

4 files changed

+136
-31
lines changed

4 files changed

+136
-31
lines changed

Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public struct ChatChannelListItem: View {
1616

1717
var channel: ChatChannel
1818
var channelName: String
19+
var injectedChannelInfo: InjectedChannelInfo?
1920
var avatar: UIImage
2021
var onlineIndicatorShown: Bool
2122
var disabled = false
@@ -37,7 +38,7 @@ public struct ChatChannelListItem: View {
3738

3839
Spacer()
3940

40-
if channel.unreadCount != .noUnread {
41+
if injectedChannelInfo == nil && channel.unreadCount != .noUnread {
4142
UnreadIndicatorView(
4243
unreadCount: channel.unreadCount.messages
4344
)
@@ -59,7 +60,7 @@ public struct ChatChannelListItem: View {
5960
showReadCount: false
6061
)
6162
}
62-
SubtitleText(text: timestampText)
63+
SubtitleText(text: injectedChannelInfo?.timestamp ?? channel.timestampText)
6364
}
6465
}
6566
}
@@ -79,27 +80,15 @@ public struct ChatChannelListItem: View {
7980
.frame(maxHeight: 12)
8081
.foregroundColor(Color(colors.subtitleText))
8182
} else {
82-
if shouldShowTypingIndicator {
83+
if channel.shouldShowTypingIndicator {
8384
TypingIndicatorView()
8485
}
8586
}
86-
SubtitleText(text: subtitleText)
87+
SubtitleText(text: injectedChannelInfo?.subtitle ?? channel.subtitleText)
8788
Spacer()
8889
}
8990
}
9091

91-
private var subtitleText: String {
92-
if channel.isMuted {
93-
return L10n.Channel.Item.muted
94-
} else if shouldShowTypingIndicator {
95-
return channel.typingIndicatorString(currentUserId: chatClient.currentUserId)
96-
} else if let lastMessageText = channel.lastMessageText {
97-
return lastMessageText
98-
} else {
99-
return L10n.Channel.Item.emptyMessages
100-
}
101-
}
102-
10392
private var shouldShowReadEvents: Bool {
10493
if let message = channel.latestMessages.first,
10594
message.isSentByCurrentUser,
@@ -110,26 +99,12 @@ public struct ChatChannelListItem: View {
11099
return false
111100
}
112101

113-
private var shouldShowTypingIndicator: Bool {
114-
!channel.currentlyTypingUsersFiltered(
115-
currentUserId: chatClient.currentUserId
116-
).isEmpty && channel.config.typingEventsEnabled
117-
}
118-
119102
private var image: UIImage? {
120103
if channel.isMuted {
121104
return images.muted
122105
}
123106
return nil
124107
}
125-
126-
private var timestampText: String {
127-
if let lastMessageAt = channel.lastMessageAt {
128-
return utils.dateFormatter.string(from: lastMessageAt)
129-
} else {
130-
return ""
131-
}
132-
}
133108
}
134109

135110
/// View for the avatar used in channels (includes online indicator overlay).
@@ -206,6 +181,14 @@ public struct UnreadIndicatorView: View {
206181
}
207182
}
208183

184+
public struct InjectedChannelInfo {
185+
public var subtitle: String?
186+
public var unreadCount: Int
187+
public var timestamp: String?
188+
public var lastMessageAt: Date?
189+
public var latestMessages: [ChatMessage]?
190+
}
191+
209192
extension ChatChannel {
210193

211194
var lastMessageText: String? {
@@ -215,4 +198,30 @@ extension ChatChannel {
215198
return nil
216199
}
217200
}
201+
202+
var shouldShowTypingIndicator: Bool {
203+
!currentlyTypingUsersFiltered(
204+
currentUserId: InjectedValues[\.chatClient].currentUserId
205+
).isEmpty && config.typingEventsEnabled
206+
}
207+
208+
var subtitleText: String {
209+
if isMuted {
210+
return L10n.Channel.Item.muted
211+
} else if shouldShowTypingIndicator {
212+
return typingIndicatorString(currentUserId: InjectedValues[\.chatClient].currentUserId)
213+
} else if let lastMessageText = lastMessageText {
214+
return lastMessageText
215+
} else {
216+
return L10n.Channel.Item.emptyMessages
217+
}
218+
}
219+
220+
var timestampText: String {
221+
if let lastMessageAt = lastMessageAt {
222+
return InjectedValues[\.utils].dateFormatter.string(from: lastMessageAt)
223+
} else {
224+
return ""
225+
}
226+
}
218227
}

Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,17 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController
3838
/// Checks if internet connection is available.
3939
private let networkReachability = NetworkReachability()
4040

41+
/// Checks if the queued changes are completely applied.
42+
private var markDirty = false
43+
4144
/// Published variables.
4245
@Published public var channels = LazyCachedMapCollection<ChatChannel>() {
4346
didSet {
44-
queuedChannelsChanges = []
47+
if !markDirty {
48+
queuedChannelsChanges = []
49+
} else {
50+
markDirty = false
51+
}
4552
}
4653
}
4754

@@ -355,6 +362,33 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController
355362
private func handleChannelAppearance() {
356363
if !queuedChannelsChanges.isEmpty && selectedChannel == nil && deeplinkChannel == nil {
357364
channels = queuedChannelsChanges
365+
} else if !queuedChannelsChanges.isEmpty {
366+
let selected = selectedChannel != nil ? selectedChannel?.channel : deeplinkChannel?.channel
367+
var index: Int?
368+
var temp = Array(queuedChannelsChanges)
369+
for i in 0..<temp.count {
370+
let current = temp[i]
371+
if current.cid == selected?.cid {
372+
index = i
373+
selectedChannel?.injectedChannelInfo = InjectedChannelInfo(
374+
subtitle: current.subtitleText,
375+
unreadCount: 0,
376+
timestamp: current.timestampText,
377+
lastMessageAt: current.lastMessageAt,
378+
latestMessages: current.latestMessages
379+
)
380+
break
381+
}
382+
}
383+
if let index = index, let selected = selected {
384+
temp[index] = selected
385+
}
386+
markDirty = true
387+
channels = LazyCachedMapCollection(source: temp, map: { $0 })
388+
} else if queuedChannelsChanges.isEmpty && (selectedChannel != nil || deeplinkChannel != nil) {
389+
if selectedChannel?.injectedChannelInfo == nil {
390+
selectedChannel?.injectedChannelInfo = InjectedChannelInfo(unreadCount: 0)
391+
}
358392
}
359393
}
360394

Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelNavigatableListItem.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public struct ChatChannelNavigatableListItem<ChannelDestination: View>: View {
4242
ChatChannelListItem(
4343
channel: channel,
4444
channelName: channelName,
45+
injectedChannelInfo: injectedChannelInfo,
4546
avatar: avatar,
4647
onlineIndicatorShown: onlineIndicatorShown,
4748
disabled: disabled,
@@ -59,6 +60,10 @@ public struct ChatChannelNavigatableListItem<ChannelDestination: View>: View {
5960
}
6061
.id("\(channel.id)-navigatable")
6162
}
63+
64+
private var injectedChannelInfo: InjectedChannelInfo? {
65+
selectedChannel?.channel.cid.rawValue == channel.cid.rawValue ? selectedChannel?.injectedChannelInfo : nil
66+
}
6267
}
6368

6469
/// Used for representing selection of an item in the channel list.
@@ -68,6 +73,7 @@ public struct ChannelSelectionInfo: Identifiable {
6873
public let id: String
6974
public let channel: ChatChannel
7075
public let message: ChatMessage?
76+
public var injectedChannelInfo: InjectedChannelInfo?
7177

7278
public init(channel: ChatChannel, message: ChatMessage?) {
7379
self.channel = channel

StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,62 @@ class ChatChannelListViewModel_Tests: StreamChatTestCase {
178178
XCTAssert(viewModel.channels.isEmpty)
179179
}
180180

181+
func test_channelListVM_queuedChangesUpdate() {
182+
// Given
183+
let channelId = ChannelId.unique
184+
var channel = ChatChannel.mock(cid: channelId)
185+
let channelListController = makeChannelListController(channels: [channel])
186+
let viewModel = ChatChannelListViewModel(
187+
channelListController: channelListController,
188+
selectedChannelId: nil
189+
)
190+
viewModel.selectedChannel = ChannelSelectionInfo(channel: channel, message: nil)
191+
channelListController.simulateInitial(channels: [channel], state: .remoteDataFetched)
192+
193+
// When
194+
let message = ChatMessage.mock(
195+
id: .unique,
196+
cid: channelId,
197+
text: "Test message",
198+
author: .mock(id: .unique, name: "Martin")
199+
)
200+
channel = ChatChannel.mock(cid: channelId, unreadCount: .mock(messages: 1), latestMessages: [message])
201+
channelListController.simulate(
202+
channels: [channel],
203+
changes: [.update(channel, index: .init(row: 0, section: 0))]
204+
)
205+
viewModel.checkForChannels(index: 0)
206+
207+
// Then
208+
let injectedChannelInfo = viewModel.selectedChannel?.injectedChannelInfo!
209+
let presentedSubtitle = injectedChannelInfo!.subtitle!
210+
let unreadCount = injectedChannelInfo!.unreadCount
211+
XCTAssert(presentedSubtitle == channel.subtitleText)
212+
XCTAssert(viewModel.channels[0].subtitleText == "No messages")
213+
XCTAssert(unreadCount == 0)
214+
XCTAssert(channel.shouldShowTypingIndicator == false)
215+
}
216+
217+
func test_channelListVM_badgeCountUpdate() {
218+
// Given
219+
let channelId = ChannelId.unique
220+
let channel = ChatChannel.mock(cid: channelId, unreadCount: .mock(messages: 1))
221+
let channelListController = makeChannelListController(channels: [channel])
222+
let viewModel = ChatChannelListViewModel(
223+
channelListController: channelListController,
224+
selectedChannelId: nil
225+
)
226+
viewModel.selectedChannel = ChannelSelectionInfo(channel: channel, message: nil)
227+
228+
// When
229+
viewModel.checkForChannels(index: 0)
230+
231+
// Then
232+
let injectedChannelInfo = viewModel.selectedChannel?.injectedChannelInfo!
233+
let unreadCount = injectedChannelInfo!.unreadCount
234+
XCTAssert(unreadCount == 0)
235+
}
236+
181237
// MARK: - private
182238

183239
private func makeChannelListController(

0 commit comments

Comments
 (0)