Skip to content

[Polls] Comments + Suggestions + Anonymous Polls + LLC Fixes #3398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d5c3302
Refactor PollResultsVC Section views to be more simple and flat hiera…
nuno-vieira Aug 22, 2024
bdd22b9
Fix bottom spacing in Poll Results Section Header
nuno-vieira Aug 22, 2024
cf545fb
Add comment buttons to `PollAttachmentView`
nuno-vieira Aug 22, 2024
44546a6
Add comment to poll implementation
nuno-vieira Aug 22, 2024
1cd973f
Fix `poll.latestAnswers` being reset after poll event did not retreiv…
nuno-vieira Aug 22, 2024
0f33849
Add `PollCommentListVC` implementation
nuno-vieira Aug 23, 2024
95c5ce8
Fix `PollVoteListQueryDTO.filterHash` using filterHash only instead o…
nuno-vieira Aug 23, 2024
e97952e
Make diffable data sources more stable
nuno-vieira Aug 26, 2024
3056dfb
Fix first page of poll vote list resetting all votes
nuno-vieira Aug 26, 2024
0b8e1c7
Fix the default vote list sorting
nuno-vieira Aug 26, 2024
5fe83a3
Add poll suggestions feature
nuno-vieira Aug 26, 2024
4d1cf6e
Add anonymous polls support
nuno-vieira Aug 26, 2024
995cab8
Fix AlertsRouter docs
nuno-vieira Aug 26, 2024
1eea6a0
Add test coverage to `PollCommentListVC`
nuno-vieira Aug 27, 2024
444ed6f
Add test coverage when poll comments are 0
nuno-vieira Aug 27, 2024
b4df08e
Add test coverage to allow suggestions
nuno-vieira Aug 27, 2024
836b1f9
Add test coverage to anonymous polls
nuno-vieira Aug 27, 2024
a6eac93
Re-record snapshots after renaming test file
nuno-vieira Aug 27, 2024
64a7e1e
Fix snapshot on CI
nuno-vieira Aug 27, 2024
476b340
Update CHANGELOG.md
nuno-vieira Aug 27, 2024
a9bd9f2
Update CHANGELOG.md
nuno-vieira Aug 27, 2024
6d7aac0
Update CHANGELOG.md
nuno-vieira Aug 27, 2024
b86e5d0
Update Sources/StreamChatUI/ChatMessageList/Attachments/Poll/PollAtta…
nuno-vieira Aug 27, 2024
84332fb
Update Sources/StreamChatUI/ChatMessageList/Attachments/Poll/PollComm…
nuno-vieira Aug 27, 2024
e2ad088
Fix comments and suggestion buttons showing when poll is closed
nuno-vieira Aug 28, 2024
6c07207
Update CHANGELOG.md
nuno-vieira Aug 28, 2024
a8dcb63
Change alert titles to follow Apples guideline
nuno-vieira Aug 28, 2024
d3bd7cf
Improve comments view spacing
nuno-vieira Aug 28, 2024
d3db24f
Optimize a bit to check if a user has already a comment
nuno-vieira Aug 28, 2024
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
29 changes: 20 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix `PollOption.latestVotes` sorting [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374)
- Fix `Poll.latestAnswers` sorting [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374)
- Fix `Poll` updates not triggering message updates in `ChannelController` [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374)
- Fix `Poll.latestAnswers` being reset on events, causing "Add a comment" button to not update in the UI SDKs [#3398](https://github.com/GetStream/stream-chat-swift/pull/3398)
- Fix `PollVoteListController` resetting the first page when loading a new page [#3398](https://github.com/GetStream/stream-chat-swift/pull/3398)
- Fix `PollVoteListController` default sorting being from oldest to newest from the server response [#3398](https://github.com/GetStream/stream-chat-swift/pull/3398)
- Fix `PollVoteListQuery.pollId` not limiting the votes query to the given poll id [#3398](https://github.com/GetStream/stream-chat-swift/pull/3398)
### 🔄 Changed
- Deprecates `PollVoteListQuery(pollId:optionId:pagination:filter:)` initializer in favor of `(pollId:filter:pagination:)` [#3381](https://github.com/GetStream/stream-chat-swift/pull/3381)

Expand All @@ -19,23 +23,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- ✨ Introducing `ViewContainerBuilder`, a new, easier way to customize views [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374) (Learn more by reading the docs [here](https://getstream.io/chat/docs/sdk/ios/uikit/custom-components/))
- Add `PollAttachmentView` component to render polls in the message list [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374)
- Add `PollResultsVC` component to show the results of a poll [#3381](https://github.com/GetStream/stream-chat-swift/pull/3381)
- Add `PollCommentListVC` component to show the comments of a poll [#3398](https://github.com/GetStream/stream-chat-swift/pull/3398)
- Add `ChatUserAvatarView.shouldShowOnlineIndicator` to disable the online indicator easily [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374)
### 🎭 New Localizations
- `message.polls.subtitle.selectOne`
- `message.polls.subtitle.selectOneOrMore`
- `message.polls.subtitle.selectUpTo`
- `message.polls.subtitle.voteEnded`
- `message.polls.button.endVote`
- `message.polls.button.viewResults`
- `message.polls.button.showAll`
- `message.polls.votes`
- `message.polls.results-title`
- `polls.subtitle.selectOne`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is becoming too big, and making the changelog not really readable - can we shorten it? Possible ideas, move it into an array in one line and use wildcard at the end to group keys (E.g. polls.subtitle.*).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, let me know what you think 👍

- `polls.subtitle.selectOneOrMore`
- `polls.subtitle.selectUpTo`
- `polls.subtitle.voteEnded`
- `polls.button.endVote`
- `polls.button.viewResults`
- `polls.button.show-all`
- `polls.votes`
- `polls.results-title`
- `polls.comments-title`
- `polls.update-comment`
- `message.preview.poll-someone-voted`
- `message.preview.poll-you-voted`
- `message.preview.poll-someone-created`
- `message.preview.poll-you-created`
- `alert.poll.end-title`
- `alert.poll.end`
- `alert.poll.add-comment`
- `alert.poll.suggest-option`
- `alert.poll.update-comment`
- `alert.poll.send`

# [4.62.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.62.0)
_August 15, 2024_
Expand Down
23 changes: 13 additions & 10 deletions Sources/StreamChat/Database/DTOs/PollDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,17 +167,20 @@ extension NSManagedObjectContext {
return optionDto
} ?? []
)
pollDto.latestAnswers = try Set(
payload.latestAnswers?.compactMap { payload in
if let payload {
let answerDto = try savePollVote(payload: payload, query: nil, cache: cache)
answerDto.poll = pollDto
return answerDto
} else {
return nil

if let latestAnswers = payload.latestAnswers {
pollDto.latestAnswers = try Set(
latestAnswers.compactMap { payload in
if let payload {
let answerDto = try savePollVote(payload: payload, query: nil, cache: cache)
answerDto.poll = pollDto
return answerDto
} else {
return nil
}
}
} ?? []
)
)
}

if let payloadOwnVotes = payload.ownVotes, !payload.fromEvent {
pollDto.ownVotes = try Set(
Expand Down
12 changes: 2 additions & 10 deletions Sources/StreamChat/Database/DTOs/PollVoteDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ extension PollVoteDTO {
extension NSManagedObjectContext {
@discardableResult
func savePollVotes(payload: PollVoteListResponse, query: PollVoteListQuery?, cache: PreWarmedCache?) -> [PollVoteDTO] {
let isFirstPage = query?.pagination.offset == 0
let isFirstPage = query?.pagination.cursor == nil
if let filterHash = query?.queryHash, isFirstPage {
let queryDTO = PollVoteListQueryDTO.load(filterHash: filterHash, context: self)
queryDTO?.votes = []
Expand Down Expand Up @@ -247,16 +247,8 @@ extension NSManagedObjectContext {
extension PollVoteDTO {
static func pollVoteListFetchRequest(query: PollVoteListQuery) -> NSFetchRequest<PollVoteDTO> {
let request = NSFetchRequest<PollVoteDTO>(entityName: PollVoteDTO.entityName)

// Fetch results controller requires at least one sorting descriptor.
// At the moment, we do not allow changing the query sorting.
request.sortDescriptors = [.init(key: #keyPath(PollVoteDTO.createdAt), ascending: false)]

// If a filter exists, use is for the predicate. Otherwise, `nil` filter matches all reactions.
if let filterHash = query.filter?.filterHash {
request.predicate = NSPredicate(format: "ANY queries.filterHash == %@", filterHash)
}

request.predicate = NSPredicate(format: "ANY queries.filterHash == %@", query.queryHash)
return request
}
}
Expand Down
11 changes: 3 additions & 8 deletions Sources/StreamChat/Database/DTOs/PollVoteListQueryDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,16 @@ extension NSManagedObjectContext {
}

func saveQuery(query: PollVoteListQuery) throws -> PollVoteListQueryDTO? {
guard let filterHash = query.filter?.filterHash else {
// A query without a filter doesn't have to be saved to the DB because it matches all users by default.
return nil
}

if let existingDTO = PollVoteListQueryDTO.load(filterHash: filterHash, context: self) {
if let existingDTO = PollVoteListQueryDTO.load(filterHash: query.queryHash, context: self) {
return existingDTO
}

let request = PollVoteListQueryDTO.fetchRequest(
keyPath: #keyPath(PollVoteListQueryDTO.filterHash),
equalTo: filterHash
equalTo: query.queryHash
)
let newDTO = NSEntityDescription.insertNewObject(into: self, for: request)
newDTO.filterHash = filterHash
newDTO.filterHash = query.queryHash

do {
newDTO.filterJSONData = try JSONEncoder.default.encode(query.filter)
Expand Down
6 changes: 3 additions & 3 deletions Sources/StreamChat/Models/Poll.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import Foundation

/// The model for a Poll.
public struct Poll: Equatable {
/// A boolean indicating whether the poll allows answers.
/// A boolean indicating whether the poll allows answers/comments.
public let allowAnswers: Bool

/// A boolean indicating whether the poll allows user-suggested options.
public let allowUserSuggestedOptions: Bool

/// The count of answers received for the poll.
/// The count of answers/comments received for the poll.
public let answersCount: Int

/// The date and time when the poll was created.
Expand Down Expand Up @@ -60,7 +60,7 @@ public struct Poll: Equatable {
/// This property is optional and may be `nil`.
public let createdBy: ChatUser?

/// A list of the latest answers received for the poll.
/// A list of the latest answers/comments received for the poll.
public let latestAnswers: [PollVote]

/// An array of options available in the poll.
Expand Down
30 changes: 28 additions & 2 deletions Sources/StreamChat/Query/PollVoteListQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public struct PollVoteListQuery: Encodable {
public var optionId: String?
/// The pagination information to query the votes.
public var pagination: Pagination
// The sorting parameter. By default votes are sorted by newest first.
public var sorting: [Sorting<PollVoteListSortingKey>]
/// The filter details to query the votes.
public var filter: Filter<VoteListFilterScope>?

Expand All @@ -27,49 +29,73 @@ public struct PollVoteListQuery: Encodable {
pollId: String,
optionId: String?,
pagination: Pagination = .init(pageSize: 10, offset: 0),
sorting: [Sorting<PollVoteListSortingKey>] = [.init(key: .createdAt, isAscending: false)],
filter: Filter<VoteListFilterScope>? = nil
) {
self.pollId = pollId
self.optionId = optionId
self.pagination = pagination
self.sorting = sorting
self.filter = filter
}

/// Creates a vote list query for the given pollId and the provided filter.
public init(
pollId: String,
filter: Filter<VoteListFilterScope>? = nil,
pagination: Pagination = .init(pageSize: 10, offset: 0)
pagination: Pagination = .init(pageSize: 10, offset: 0),
sorting: [Sorting<PollVoteListSortingKey>] = [.init(key: .createdAt, isAscending: false)]
) {
self.pollId = pollId
self.pagination = pagination
self.sorting = sorting
self.filter = filter
}

/// Creates a vote list query for the given pollId and optionId.
public init(
pollId: String,
optionId: String,
pagination: Pagination = .init(pageSize: 10, offset: 0)
pagination: Pagination = .init(pageSize: 10, offset: 0),
sorting: [Sorting<PollVoteListSortingKey>] = [.init(key: .createdAt, isAscending: false)]
) {
self.pollId = pollId
self.optionId = optionId
self.pagination = pagination
self.sorting = sorting
filter = .equal(.optionId, to: optionId)
}

enum CodingKeys: CodingKey {
case pagination
case filter
case sort
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(filter, forKey: .filter)
if !sorting.isEmpty {
try container.encode(sorting, forKey: .sort)
}
try pagination.encode(to: encoder)
}
}

/// The type describing a value that can be used as a sorting when paginating a list of votes in a poll.
public struct PollVoteListSortingKey: RawRepresentable, Hashable, SortingKey {
public let rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}
}

public extension PollVoteListSortingKey {
/// Sorts votes by `created_at` field.
static let createdAt = Self(rawValue: PollVotePayload.CodingKeys.createdAt.rawValue)
}

/// A namespace for the `FilterKey`s suitable to be used for `PollVoteListQuery`.
public protocol AnyVoteListFilterScope {}

Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamChat/Repositories/PollsRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ class PollsRepository {
try session.linkVote(
with: pollVote.id,
in: pollVote.pollId,
to: query.filter?.filterHash
to: query.queryHash
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ open class PollAttachmentOptionListItemView: _View, ThemeProvider {

optionNameLabel.text = content.option.text
votesCountLabel.text = "\(content.voteCount)"

latestVotesAuthorsView.content = .init(users: latestVotesAuthors)
latestVotesAuthorsView.isHidden = content.poll.votingVisibility == .anonymous

if content.isVotedByCurrentUser {
voteCheckboxButton.setCheckedState()
Expand Down
Loading
Loading