Skip to content

Commit f3ebdcb

Browse files
committed
Merge branch 'develop' into fix/ios-26-swipe-when-sheet
# Conflicts: # CHANGELOG.md
2 parents 0926cc2 + 92aa8e9 commit f3ebdcb

21 files changed

+543
-99
lines changed

CHANGELOG.md

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

44
# Upcoming
55

6+
### ✅ Added
7+
- Open `ChatChannelInfoViewModel.leaveButtonTitle` and `ChatChannelInfoViewModel.leaveConversationDescription` [#1018](https://github.com/GetStream/stream-chat-swiftui/pull/1018)
68
### 🐞 Fixed
9+
- Use `muteChannel` capability for showing mute channel button in the `ChatChannelInfoView` [#1018](https://github.com/GetStream/stream-chat-swiftui/pull/1018)
10+
- Fix `PollOptionAllVotesViewModel` not loading more votes [#1067](https://github.com/GetStream/stream-chat-swiftui/pull/1067)
11+
- Fix `ViewFactory.makeMessageAvatarView()` not used in some views [#1068](https://github.com/GetStream/stream-chat-swiftui/pull/1068)
12+
- `MessageRepliesView`
13+
- `ReactionsUsersView`
14+
- `MentionUsersView`
15+
- `ParticipantInfoView`
16+
- `ChatThreadListItem`
717
- Fix scrolling in the message list when presented with a sheet on iOS 26 [#1065](https://github.com/GetStream/stream-chat-swiftui/pull/1065)
818

919
# [4.94.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.94.0)

Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoHelperViews.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,15 @@ public struct ChatInfoOptionsView<Factory: ViewFactory>: View {
7676

7777
Divider()
7878

79-
ChannelInfoItemView(
80-
icon: images.muted,
81-
title: viewModel.mutedText,
82-
verticalPadding: 12
83-
) {
84-
Toggle(isOn: $viewModel.muted) {
85-
EmptyView()
79+
if viewModel.shouldShowMuteChannelButton {
80+
ChannelInfoItemView(
81+
icon: images.muted,
82+
title: viewModel.mutedText,
83+
verticalPadding: 12
84+
) {
85+
Toggle(isOn: $viewModel.muted) {
86+
EmptyView()
87+
}
8688
}
8789
}
8890

Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public struct ChatChannelInfoView<Factory: ViewFactory>: View, KeyboardReadable
132132

133133
if let selectedParticipant = viewModel.selectedParticipant {
134134
ParticipantInfoView(
135+
factory: factory,
135136
participant: selectedParticipant,
136137
actions: viewModel.participantActions(for: selectedParticipant)
137138
) {

Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoViewModel.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele
4343
channel.ownCapabilities.contains(.leaveChannel)
4444
}
4545
}
46+
47+
open var shouldShowMuteChannelButton: Bool {
48+
channel.ownCapabilities.contains(.muteChannel)
49+
}
4650

4751
open var canRenameChannel: Bool {
4852
channel.ownCapabilities.contains(.updateChannel)
@@ -87,15 +91,15 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele
8791
}
8892
}
8993

90-
public var leaveButtonTitle: String {
94+
open var leaveButtonTitle: String {
9195
if channel.isDirectMessageChannel {
9296
L10n.Alert.Actions.deleteChannelTitle
9397
} else {
9498
L10n.Alert.Actions.leaveGroupTitle
9599
}
96100
}
97101

98-
public var leaveConversationDescription: String {
102+
open var leaveConversationDescription: String {
99103
if channel.isDirectMessageChannel {
100104
L10n.Alert.Actions.deleteChannelMessage
101105
} else {

Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ParticipantInfoView.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,28 @@
44

55
import SwiftUI
66

7-
struct ParticipantInfoView: View {
7+
struct ParticipantInfoView<Factory: ViewFactory>: View {
88
@Injected(\.fonts) var fonts
99
@Injected(\.colors) var colors
1010

11+
var factory: Factory
1112
let participant: ParticipantInfo
1213
var actions: [ParticipantAction]
1314

1415
var onDismiss: () -> Void
1516

17+
init(
18+
factory: Factory = DefaultViewFactory.shared,
19+
participant: ParticipantInfo,
20+
actions: [ParticipantAction],
21+
onDismiss: @escaping () -> Void
22+
) {
23+
self.factory = factory
24+
self.participant = participant
25+
self.actions = actions
26+
self.onDismiss = onDismiss
27+
}
28+
1629
@State private var alertShown = false
1730
@State private var alertAction: ParticipantAction? {
1831
didSet {
@@ -31,13 +44,15 @@ struct ParticipantInfoView: View {
3144
.font(fonts.footnote)
3245
.foregroundColor(Color(colors.textLowEmphasis))
3346

34-
MessageAvatarView(
35-
avatarURL: participant.chatUser.imageURL,
36-
size: CGSize(width: 64, height: 64),
37-
showOnlineIndicator: participant.chatUser.isOnline
47+
let displayInfo = UserDisplayInfo(
48+
id: participant.chatUser.id,
49+
name: participant.chatUser.name ?? participant.chatUser.id,
50+
imageURL: participant.chatUser.imageURL,
51+
size: CGSize(width: 64, height: 64)
3852
)
39-
.padding()
40-
53+
factory.makeMessageAvatarView(for: displayInfo)
54+
.padding()
55+
4156
VStack {
4257
ForEach(actions) { action in
4358
Divider()

Sources/StreamChatSwiftUI/ChatChannel/Composer/Suggestions/CommandsContainerView.swift

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

88
/// Default implementation of the commands container.
9-
struct CommandsContainerView: View {
9+
struct CommandsContainerView<Factory: ViewFactory>: View {
10+
var factory: Factory
1011
var suggestions: [String: Any]
1112
var handleCommand: ([String: Any]) -> Void
1213

14+
init(
15+
factory: Factory = DefaultViewFactory.shared,
16+
suggestions: [String: Any],
17+
handleCommand: @escaping ([String: Any]) -> Void
18+
) {
19+
self.factory = factory
20+
self.suggestions = suggestions
21+
self.handleCommand = handleCommand
22+
}
23+
1324
var body: some View {
1425
ZStack {
1526
if let suggestedUsers = suggestions["mentions"] as? [ChatUser] {
1627
MentionUsersView(
28+
factory: factory,
1729
users: suggestedUsers,
1830
userSelected: { user in
1931
handleCommand(["chatUser": user])

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,31 @@ import StreamChat
66
import SwiftUI
77

88
/// View for the mentioned users.
9-
public struct MentionUsersView: View {
9+
public struct MentionUsersView<Factory: ViewFactory>: View {
1010
@Injected(\.colors) private var colors
1111

12+
var factory: Factory
1213
private let itemHeight: CGFloat = 60
1314

1415
var users: [ChatUser]
1516
var userSelected: (ChatUser) -> Void
1617

18+
public init(
19+
factory: Factory = DefaultViewFactory.shared,
20+
users: [ChatUser],
21+
userSelected: @escaping (ChatUser) -> Void
22+
) {
23+
self.factory = factory
24+
self.users = users
25+
self.userSelected = userSelected
26+
}
27+
1728
public var body: some View {
1829
ScrollView {
1930
LazyVStack {
2031
ForEach(users) { user in
2132
MentionUserView(
33+
factory: factory,
2234
user: user,
2335
userSelected: userSelected
2436
)
@@ -43,20 +55,33 @@ public struct MentionUsersView: View {
4355
}
4456

4557
/// View for one user that can be mentioned.
46-
public struct MentionUserView: View {
58+
public struct MentionUserView<Factory: ViewFactory>: View {
4759
@Injected(\.fonts) private var fonts
4860
@Injected(\.colors) private var colors
4961
@Injected(\.utils) private var utils
5062

63+
var factory: Factory
5164
var user: ChatUser
5265
var userSelected: (ChatUser) -> Void
5366

67+
public init(
68+
factory: Factory = DefaultViewFactory.shared,
69+
user: ChatUser,
70+
userSelected: @escaping (ChatUser) -> Void
71+
) {
72+
self.factory = factory
73+
self.user = user
74+
self.userSelected = userSelected
75+
}
76+
5477
public var body: some View {
5578
HStack {
56-
MessageAvatarView(
57-
avatarURL: user.imageURL,
58-
showOnlineIndicator: true
79+
let displayInfo = UserDisplayInfo(
80+
id: user.id,
81+
name: user.name ?? user.id,
82+
imageURL: user.imageURL
5983
)
84+
factory.makeMessageAvatarView(for: displayInfo)
6085
Text(user.name ?? user.id)
6186
.lineLimit(1)
6287
.font(fonts.bodyBold)

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,12 @@ public struct MessageRepliesView<Factory: ViewFactory>: View {
6060
} label: {
6161
HStack {
6262
if !isRightAligned {
63-
MessageAvatarView(
64-
avatarURL: message.threadParticipants.first?.imageURL,
65-
size: .init(width: 16, height: 16)
66-
)
63+
messageAvatarView
6764
}
6865
Text(title)
6966
.font(fonts.footnoteBold)
7067
if isRightAligned {
71-
MessageAvatarView(
72-
avatarURL: message.threadParticipants.first?.imageURL,
73-
size: .init(width: 16, height: 16)
74-
)
68+
messageAvatarView
7569
}
7670
}
7771
.padding(.horizontal, 16)
@@ -122,6 +116,28 @@ public struct MessageRepliesView<Factory: ViewFactory>: View {
122116
return L10n.Message.Threads.replies
123117
}
124118
}
119+
120+
private var messageAvatarView: some View {
121+
// This is just a fallback for backwards compatibility
122+
// In practice thread participants will never be empty.
123+
// So, the factory method will always run.
124+
Group {
125+
if let participant = message.threadParticipants.first {
126+
let displayInfo = UserDisplayInfo(
127+
id: participant.id,
128+
name: participant.name ?? participant.id,
129+
imageURL: participant.imageURL,
130+
size: .init(width: 16, height: 16)
131+
)
132+
factory.makeMessageAvatarView(for: displayInfo)
133+
} else {
134+
MessageAvatarView(
135+
avatarURL: message.threadParticipants.first?.imageURL,
136+
size: .init(width: 16, height: 16)
137+
)
138+
}
139+
}
140+
}
125141
}
126142

127143
/// Lazy view that uses the message controller to fetch the parent message before creating message replies view.

Sources/StreamChatSwiftUI/ChatChannel/MessageList/Polls/PollOptionAllVotesViewModel.swift

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@ class PollOptionAllVotesViewModel: ObservableObject, PollVoteListControllerDeleg
1616

1717
private var cancellables = Set<AnyCancellable>()
1818
private(set) var animateChanges = false
19-
private var loadingVotes = false
20-
21-
init(poll: Poll, option: PollOption) {
19+
var loadingVotes = false
20+
21+
init(poll: Poll, option: PollOption, controller: PollVoteListController? = nil) {
2222
self.poll = poll
2323
self.option = option
2424
let query = PollVoteListQuery(
2525
pollId: poll.id,
26-
optionId: option.id
26+
optionId: option.id,
27+
pagination: .init(pageSize: 25)
2728
)
28-
controller = InjectedValues[\.chatClient].pollVoteListController(query: query)
29-
controller.delegate = self
29+
self.controller = controller ?? InjectedValues[\.chatClient].pollVoteListController(query: query)
30+
self.controller.delegate = self
3031
refresh()
3132

3233
// No animation for initial load
@@ -41,20 +42,21 @@ class PollOptionAllVotesViewModel: ObservableObject, PollVoteListControllerDeleg
4142
controller.synchronize { [weak self] error in
4243
guard let self else { return }
4344
self.pollVotes = Array(self.controller.votes)
44-
if self.pollVotes.isEmpty {
45-
self.loadVotes()
46-
}
4745
if error != nil {
4846
self.errorShown = true
4947
}
5048
}
5149
}
5250

5351
func onAppear(vote: PollVote) {
54-
guard !loadingVotes,
55-
let index = pollVotes.firstIndex(where: { $0 == vote }),
56-
index > pollVotes.count - 10 else { return }
57-
52+
guard let index = pollVotes.firstIndex(where: { $0 == vote }) else {
53+
return
54+
}
55+
56+
guard index > pollVotes.count - 10 && pollVotes.count > 25 else {
57+
return
58+
}
59+
5860
loadVotes()
5961
}
6062

@@ -76,6 +78,10 @@ class PollOptionAllVotesViewModel: ObservableObject, PollVoteListControllerDeleg
7678
}
7779

7880
private func loadVotes() {
81+
if loadingVotes || controller.hasLoadedAllVotes {
82+
return
83+
}
84+
7985
loadingVotes = true
8086

8187
controller.loadMoreVotes { [weak self] error in

0 commit comments

Comments
 (0)