Skip to content

Commit 5e9de53

Browse files
authored
Merge pull request #612 from GetStream/add/polls-improvements
Polls UI fixes and UX Improvements
2 parents f73f436 + 8678af8 commit 5e9de53

18 files changed

+132
-75
lines changed

CHANGELOG.md

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

44
# Upcoming
55

6+
### 🔄 Changed
7+
- Improves Poll voting UX by making it possible to tap on the whole option as well [#612](https://github.com/GetStream/stream-chat-swiftui/pull/612)
68
### 🐞 Fixed
79
- Rare crash when accessing frame of the view [#607](https://github.com/GetStream/stream-chat-swiftui/pull/607)
810
- `ChatChannelListView` navigation did not trigger when using a custom container and its body reloaded [#609](https://github.com/GetStream/stream-chat-swiftui/pull/609)
911
- Channel was sometimes not marked as read when tapping the x on the unread message pill in the message list [#610](https://github.com/GetStream/stream-chat-swiftui/pull/610)
12+
- Fix the poll vote progress view not having full width when the Poll is closed [#612](https://github.com/GetStream/stream-chat-swiftui/pull/612)
13+
- Fix the last vote author not accurate in the channel preview [#612](https://github.com/GetStream/stream-chat-swiftui/pull/612)
1014

1115
# [4.63.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.63.0)
1216
_September 12, 2024_

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ struct PollAllOptionsView: View {
3131
viewModel: viewModel,
3232
option: option,
3333
optionFont: fonts.headline,
34-
alternativeStyle: true
34+
alternativeStyle: true,
35+
checkboxButtonSpacing: 8
3536
)
3637
}
3738
}

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

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ public struct PollAttachmentView<Factory: ViewFactory>: View {
5555
PollOptionView(
5656
viewModel: viewModel,
5757
option: option,
58-
optionVotes: poll.voteCountsByOption?[option.id],
59-
maxVotes: poll.voteCountsByOption?.values.max()
58+
optionVotes: poll.voteCount(for: option),
59+
maxVotes: poll.currentMaximumVoteCount
6060
)
6161
.layoutPriority(1) // do not compress long text
6262
}
@@ -190,53 +190,63 @@ struct PollOptionView: View {
190190
var maxVotes: Int?
191191
/// If true, only option name and vote count is shown, otherwise votes indicator and avatars appear as well.
192192
var alternativeStyle: Bool = false
193-
193+
/// The spacing between the checkbox and the option name.
194+
/// By default it is 4. For All Options View is 8.
195+
var checkboxButtonSpacing: CGFloat = 4
196+
194197
var body: some View {
195-
VStack(spacing: 4) {
196-
HStack(alignment: .top) {
197-
if !viewModel.poll.isClosed {
198-
Button {
199-
if viewModel.optionVotedByCurrentUser(option) {
200-
viewModel.removePollVote(for: option)
201-
} else {
202-
viewModel.castPollVote(for: option)
203-
}
204-
} label: {
205-
if viewModel.optionVotedByCurrentUser(option) {
206-
Image(systemName: "checkmark.circle.fill")
207-
} else {
208-
Image(systemName: "circle")
209-
}
198+
HStack(alignment: .top, spacing: checkboxButtonSpacing) {
199+
if !viewModel.poll.isClosed {
200+
Button {
201+
togglePollVote()
202+
} label: {
203+
if viewModel.optionVotedByCurrentUser(option) {
204+
Image(systemName: "checkmark.circle.fill")
205+
} else {
206+
Image(systemName: "circle")
210207
}
211208
}
212-
213-
Text(option.text)
214-
.font(optionFont)
215-
Spacer()
216-
if !alternativeStyle, viewModel.showVoterAvatars {
217-
HStack(spacing: -4) {
218-
ForEach(
219-
option.latestVotes.sorted(by: { $0.createdAt > $1.createdAt }).suffix(2)
220-
) { vote in
221-
MessageAvatarView(
222-
avatarURL: vote.user?.imageURL,
223-
size: .init(width: 20, height: 20)
224-
)
209+
}
210+
VStack(spacing: 4) {
211+
HStack(alignment: .top) {
212+
Text(option.text)
213+
.font(optionFont)
214+
Spacer()
215+
if !alternativeStyle, viewModel.showVoterAvatars {
216+
HStack(spacing: -4) {
217+
ForEach(
218+
option.latestVotes.prefix(2)
219+
) { vote in
220+
MessageAvatarView(
221+
avatarURL: vote.user?.imageURL,
222+
size: .init(width: 20, height: 20)
223+
)
224+
}
225225
}
226226
}
227+
Text("\(viewModel.poll.voteCountsByOption?[option.id] ?? 0)")
228+
}
229+
if !alternativeStyle {
230+
PollVotesIndicatorView(
231+
alternativeStyle: viewModel.poll.isClosed && viewModel.hasMostVotes(for: option),
232+
optionVotes: optionVotes ?? 0,
233+
maxVotes: maxVotes ?? 0
234+
)
227235
}
228-
Text("\(viewModel.poll.voteCountsByOption?[option.id] ?? 0)")
229-
}
230-
231-
if !alternativeStyle {
232-
PollVotesIndicatorView(
233-
alternativeStyle: viewModel.poll.isClosed && viewModel.hasMostVotes(for: option),
234-
optionVotes: optionVotes ?? 0,
235-
maxVotes: maxVotes ?? 0
236-
)
237-
.padding(.leading, 24)
238236
}
239237
}
238+
.contentShape(Rectangle())
239+
.onTapGesture {
240+
togglePollVote()
241+
}
242+
}
243+
244+
func togglePollVote() {
245+
if viewModel.optionVotedByCurrentUser(option) {
246+
viewModel.removePollVote(for: option)
247+
} else {
248+
viewModel.castPollVote(for: option)
249+
}
240250
}
241251
}
242252

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public class PollAttachmentViewModel: ObservableObject, PollControllerDelegate {
198198

199199
/// True, if the current user has voted for the specified option, otherwise false.
200200
public func optionVotedByCurrentUser(_ option: PollOption) -> Bool {
201-
currentUserVote(for: option) != nil
201+
poll.hasCurrentUserVoted(for: option)
202202
}
203203

204204
/// Adds a new option to the poll.
@@ -219,13 +219,7 @@ public class PollAttachmentViewModel: ObservableObject, PollControllerDelegate {
219219
///
220220
/// - Note: When multiple options have the highest vote count, this function returns false.
221221
public func hasMostVotes(for option: PollOption) -> Bool {
222-
guard let allCounts = poll.voteCountsByOption else { return false }
223-
guard let optionVoteCount = allCounts[option.id], optionVoteCount > 0 else { return false }
224-
guard let highestVotePerOption = allCounts.values.max() else { return false }
225-
guard optionVoteCount == highestVotePerOption else { return false }
226-
// Check if only one option has highest number for votes
227-
let optionsByVoteCounts = Dictionary(grouping: allCounts, by: { $0.value })
228-
return optionsByVoteCounts[optionVoteCount]?.count == 1
222+
poll.isOptionWithMostVotes(option)
229223
}
230224

231225
// MARK: - PollControllerDelegate
@@ -244,7 +238,7 @@ public class PollAttachmentViewModel: ObservableObject, PollControllerDelegate {
244238
// MARK: - private
245239

246240
private func currentUserVote(for option: PollOption) -> PollVote? {
247-
currentUserVotes.first(where: { $0.optionId == option.id })
241+
poll.currentUserVote(for: option)
248242
}
249243

250244
private func notifySheetPresentation(shown: Bool) {

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ class PollCommentsViewModel: ObservableObject, PollVoteListControllerDelegate {
2525
convenience init(poll: Poll, pollController: PollController) {
2626
let query = PollVoteListQuery(
2727
pollId: poll.id,
28-
optionId: nil,
29-
filter: .and(
30-
[.equal(.pollId, to: poll.id), .equal(.isAnswer, to: true)]
31-
)
28+
filter: .equal(.isAnswer, to: true)
3229
)
3330
self.init(
3431
pollController: pollController,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class PollOptionAllVotesViewModel: ObservableObject, PollVoteListControllerDeleg
2424
self.option = option
2525
let query = PollVoteListQuery(
2626
pollId: poll.id,
27-
optionId: option.id, filter: .equal(.optionId, to: option.id)
27+
optionId: option.id
2828
)
2929
controller = InjectedValues[\.chatClient].pollVoteListController(query: query)
3030
controller.delegate = self

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ struct PollResultsView: View {
4545
poll: viewModel.poll,
4646
option: option,
4747
votes: Array(
48-
option.latestVotes.sorted(by: { $0.createdAt > $1.createdAt })
48+
option.latestVotes
4949
.prefix(numberOfItemsShown)
5050
),
5151
hasMostVotes: viewModel.hasMostVotes(for: option),

Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,8 @@ extension ChatChannel {
360360
private func pollMessageText(for previewMessage: ChatMessage) -> String? {
361361
guard let poll = previewMessage.poll, !previewMessage.isDeleted else { return nil }
362362
var components = ["📊"]
363-
if let latestVoter = poll.latestVotesByOption.first?.latestVotes.first?.user {
364-
if previewMessage.isSentByCurrentUser {
363+
if let latestVoter = poll.latestVotes.first?.user {
364+
if latestVoter.id == membership?.id {
365365
components.append(L10n.Channel.Item.pollYouVoted)
366366
} else {
367367
components.append(L10n.Channel.Item.pollSomeoneVoted(latestVoter.name ?? latestVoter.id))

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3720,8 +3720,8 @@
37203720
isa = XCRemoteSwiftPackageReference;
37213721
repositoryURL = "https://github.com/GetStream/stream-chat-swift.git";
37223722
requirement = {
3723-
kind = upToNextMajorVersion;
3724-
minimumVersion = 4.63.0;
3723+
branch = develop;
3724+
kind = branch;
37253725
};
37263726
};
37273727
E3A1C01A282BAC66002D1E26 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {

StreamChatSwiftUITests/Infrastructure/Mocks/Poll_Mock.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ extension Poll {
5151
createdBy: .mock(id: "test", name: "test"),
5252
latestAnswers: [],
5353
options: [option],
54-
latestVotesByOption: [option]
54+
latestVotesByOption: [option],
55+
latestVotes: [],
56+
ownVotes: []
5557
)
5658
return poll
5759
}
@@ -89,7 +91,9 @@ extension Poll {
8991
createdBy: .mock(id: "test", name: "test"),
9092
latestAnswers: [],
9193
options: options,
92-
latestVotesByOption: options
94+
latestVotesByOption: options,
95+
latestVotes: [],
96+
ownVotes: []
9397
)
9498
}
9599
}

0 commit comments

Comments
 (0)