Skip to content
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

4.64.0 Release #3442

Merged
merged 14 commits into from
Oct 2, 2024
Merged
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
4 changes: 0 additions & 4 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
name: "Publish new release"

on:
push:
branches:
- main

workflow_dispatch:

jobs:
Expand Down
4 changes: 3 additions & 1 deletion .styles/config/vocabularies/Base/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ swappable
telehealth
subclassing
[Ss]ubview
scrollable
scrollable
HContainer
VContainer
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ disabled_rules:
- type_body_length
- opening_brace
- line_length
- switch_case_alignment
- notification_center_detachment

# TODO: https://github.com/GetStream/ios-issues-tracking/issues/538
- attributes # it should be included in `opt_in_rules`
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,48 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### πŸ”„ Changed

# [4.64.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.64.0)
_October 02, 2024_

## StreamChat
### βœ… Added
- Add `ChannelMemberListSortingKey.userId` for sorting channel members by id [#3423](https://github.com/GetStream/stream-chat-swift/pull/3423)
- Add helper functions to `Poll` that extracts common domain logic [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374)
### 🐞 Fixed
- Fix old channel updates not being added to the channel list automatically [#3430](https://github.com/GetStream/stream-chat-swift/pull/3430)
- Keep consistent order in channel and member lists when sorting by key with many equal values [#3423](https://github.com/GetStream/stream-chat-swift/pull/3423)
- Recommendation: Always add at least one unique key to the query's sort
- Avoid rare optimistic locking failure by refreshing the object before saving to the persistent store [#3432](https://github.com/GetStream/stream-chat-swift/pull/3432)
- 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)

## StreamChatUI
### βœ… Added
- ✨ 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 `PollCreationVC` component to create a poll in a channel [#3433](https://github.com/GetStream/stream-chat-swift/pull/3433)
- Add `PollAllOptionsListVC` component to show all the options of a poll [#3435](https://github.com/GetStream/stream-chat-swift/pull/3435)
- Add `ChatUserAvatarView.shouldShowOnlineIndicator` to disable the online indicator easily [#3374](https://github.com/GetStream/stream-chat-swift/pull/3374)
### 🐞 Fixed
- Fix a crash with thematic breaks in markdown [#3437](https://github.com/GetStream/stream-chat-swift/pull/3437)
- Fix Message Actions Alert view not scrollable when the view has the exact same height as the screen [#3435](https://github.com/GetStream/stream-chat-swift/pull/3435)
### 🎭 New Localizations
Multiple localizations were added to Polls, for more details please check the strings file.
- `polls.subtitle.*`
- `polls.button.*`
- `polls.*`
- `alert.poll.*`
- `message.preview.poll-*`

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

Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ GEM
fastlane
pry
fastlane-plugin-sonarcloud_metric_kit (0.2.1)
fastlane-plugin-stream_actions (0.3.63)
fastlane-plugin-stream_actions (0.3.70)
xctest_list (= 1.2.1)
fastlane-plugin-versioning (0.5.2)
ffi (1.17.0)
Expand Down Expand Up @@ -308,7 +308,7 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (4.0.7)
puma (6.4.2)
puma (6.4.3)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.1.7)
Expand Down Expand Up @@ -437,7 +437,7 @@ DEPENDENCIES
fastlane-plugin-create_xcframework
fastlane-plugin-lizard
fastlane-plugin-sonarcloud_metric_kit
fastlane-plugin-stream_actions (= 0.3.63)
fastlane-plugin-stream_actions (= 0.3.70)
fastlane-plugin-versioning
jazzy
json
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<a href="https://sonarcloud.io/summary/new_code?id=GetStream_stream-chat-swift"><img src="https://sonarcloud.io/api/project_badges/measure?project=GetStream_stream-chat-swift&metric=coverage" /></a>
</p>
<p align="center">
<img id="stream-chat-label" alt="StreamChat" src="https://img.shields.io/badge/StreamChat-6.83%20MB-blue"/>
<img id="stream-chat-ui-label" alt="StreamChatUI" src="https://img.shields.io/badge/StreamChatUI-4.42%20MB-blue"/>
<img id="stream-chat-label" alt="StreamChat" src="https://img.shields.io/badge/StreamChat-6.9%20MB-blue"/>
<img id="stream-chat-ui-label" alt="StreamChatUI" src="https://img.shields.io/badge/StreamChatUI-4.95%20MB-blue"/>
</p>

This is the official iOS SDK for [Stream Chat](https://getstream.io/chat/sdk/ios/), a service for building chat and messaging applications. This library includes both a low-level SDK and a set of reusable UI components.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ struct PollPayload: Decodable {
var voteCount: Int
var latestAnswers: [PollVotePayload?]?
var options: [PollOptionPayload?]
var ownVotes: [PollVotePayload?]
var ownVotes: [PollVotePayload?]?
var custom: [String: RawJSON]?
var latestVotesByOption: [String: [PollVotePayload]]?
var voteCountsByOption: [String: Int]?
Expand All @@ -183,6 +183,11 @@ struct PollPayload: Decodable {
var votingVisibility: String?
var createdBy: UserPayload?

// Workaround for handling events. The backend always returns the `ownVotes` as an empty array.
// This would reset the ownVotes of a Poll, so we need to understand that this payload is from an event
// and ignore the `ownVotes` property.
var fromEvent: Bool = false

init(
allowAnswers: Bool,
allowUserSuggestedOptions: Bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1246,11 +1246,11 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP

// MARK: - Internal

func recoverWatchedChannel(completion: @escaping (Error?) -> Void) {
func recoverWatchedChannel(recovery: Bool, completion: @escaping (Error?) -> Void) {
if cid != nil, isChannelAlreadyCreated {
startWatching(isInRecoveryMode: true, completion: completion)
startWatching(isInRecoveryMode: recovery, completion: completion)
} else {
synchronize(isInRecoveryMode: true, completion)
synchronize(isInRecoveryMode: recovery, completion)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public class ChatChannelListController: DataController, DelegateCallable, DataSt
private let environment: Environment
private lazy var channelListLinker: ChannelListLinker = self.environment
.channelListLinkerBuilder(
query, filter, client.config, client.databaseContainer, worker
query, filter, { [weak self] in StreamCollection(self?.channels ?? []) }, client.config, client.databaseContainer, worker
)

/// Creates a new `ChannelListController`.
Expand Down Expand Up @@ -269,6 +269,7 @@ extension ChatChannelListController {
var channelListLinkerBuilder: (
_ query: ChannelListQuery,
_ filter: ((ChatChannel) -> Bool)?,
_ loadedChannels: @escaping () -> StreamCollection<ChatChannel>,
_ clientConfig: ChatClientConfig,
_ databaseContainer: DatabaseContainer,
_ worker: ChannelListUpdater
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,10 @@ public class PollController: DataController, DelegateCallable, DataStoreProvider
eventsController = client.eventsController()
ownVotesQuery = PollVoteListQuery(
pollId: pollId,
optionId: nil,
pagination: .init(pageSize: 25, cursor: nil),
filter: .and(
[.equal(.userId, to: client.currentUserId ?? ""), .equal(.pollId, to: pollId)]
)
),
pagination: .init(pageSize: 25, cursor: nil)
)
pollsRepository = client.pollsRepository
super.init()
Expand Down
10 changes: 9 additions & 1 deletion Sources/StreamChat/Database/DTOs/ChannelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,15 @@ extension ChannelDTO {
let request = NSFetchRequest<ChannelDTO>(entityName: ChannelDTO.entityName)

// Fetch results controller requires at least one sorting descriptor.
let sortDescriptors = query.sort.compactMap { $0.key.sortDescriptor(isAscending: $0.isAscending) }
var sortDescriptors = query.sort.compactMap { $0.key.sortDescriptor(isAscending: $0.isAscending) }

// For consistent order we need to have a sort descriptor which breaks ties
if !sortDescriptors.isEmpty, !sortDescriptors.contains(where: { $0.key == ChannelListSortingKey.updatedAt.localKey }) {
if let tieBreaker = ChannelListSortingKey.updatedAt.sortDescriptor(isAscending: false) {
sortDescriptors.append(tieBreaker)
}
}

request.sortDescriptors = sortDescriptors.isEmpty ? [ChannelListSortingKey.defaultSortDescriptor] : sortDescriptors

let matchingQuery = NSPredicate(format: "ANY queries.filterHash == %@", query.filter.filterHash)
Expand Down
7 changes: 6 additions & 1 deletion Sources/StreamChat/Database/DTOs/MemberModelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ extension MemberDTO {
static func members(matching query: ChannelMemberListQuery) -> NSFetchRequest<MemberDTO> {
let request = NSFetchRequest<MemberDTO>(entityName: MemberDTO.entityName)
request.predicate = NSPredicate(format: "ANY queries.queryHash == %@", query.queryHash)
request.sortDescriptors = query.sortDescriptors
var sortDescriptors = query.sortDescriptors
// For consistent order we need to have a sort descriptor which breaks ties
if !sortDescriptors.isEmpty, !sortDescriptors.contains(where: { $0.key == ChannelMemberListSortingKey.userId.rawValue }) {
sortDescriptors.append(ChannelMemberListSortingKey.userId.sortDescriptor(isAscending: true))
}
request.sortDescriptors = sortDescriptors
return request
}
}
Expand Down
66 changes: 53 additions & 13 deletions Sources/StreamChat/Database/DTOs/PollDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@ class PollDTO: NSManagedObject {
@NSManaged var maxVotesAllowed: NSNumber?
@NSManaged var votingVisibility: String?
@NSManaged var createdBy: UserDTO?
@NSManaged var latestAnswers: Set<PollVoteDTO>
@NSManaged var latestVotes: Set<PollVoteDTO>
@NSManaged var message: MessageDTO?
@NSManaged var options: NSOrderedSet
@NSManaged var latestVotesByOption: Set<PollOptionDTO>


override func willSave() {
super.willSave()

// When the poll is updated, trigger message FRC update.
if let message = self.message, hasPersistentChangedValues, !message.hasChanges, !message.isDeleted {
message.id = message.id
}
}

static func loadOrCreate(
pollId: String,
context: NSManagedObjectContext,
Expand Down Expand Up @@ -70,7 +79,8 @@ extension PollDTO {
}

let optionsArray = (options.array as? [PollOptionDTO]) ?? []

let currentUserId = managedObjectContext?.currentUser?.user.id

return try Poll(
allowAnswers: allowAnswers,
allowUserSuggestedOptions: allowUserSuggestedOptions,
Expand All @@ -88,9 +98,20 @@ extension PollDTO {
maxVotesAllowed: maxVotesAllowed?.intValue,
votingVisibility: votingVisibility(from: votingVisibility),
createdBy: createdBy?.asModel(),
latestAnswers: latestAnswers.map { try $0.asModel() },
latestAnswers: latestVotes
.filter { $0.isAnswer }
.map { try $0.asModel() }
.sorted(by: { $0.createdAt > $1.createdAt }),
options: optionsArray.map { try $0.asModel() },
latestVotesByOption: latestVotesByOption.map { try $0.asModel() }
latestVotesByOption: latestVotesByOption.map { try $0.asModel() },
latestVotes: latestVotesByOption
.map(\.latestVotes)
.joined()
.map { try $0.asModel() }
.sorted(by: { $0.createdAt > $1.createdAt }),
ownVotes: latestVotes
.filter { !$0.isAnswer && $0.user?.id == currentUserId }
.map { try $0.asModel() }
)
}

Expand Down Expand Up @@ -163,18 +184,37 @@ extension NSManagedObjectContext {
return optionDto
} ?? []
)
pollDto.latestAnswers = try Set(
payload.latestAnswers?.compactMap { payload in

if let latestAnswers = payload.latestAnswers {
pollDto.latestVotes
.filter { $0.isAnswer }
.forEach {
pollDto.latestVotes.remove($0)
}

try latestAnswers.forEach { 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.latestVotes
.filter { !$0.isAnswer }
.forEach {
pollDto.latestVotes.remove($0)
}

try payloadOwnVotes.forEach { payload in
if let payload {
let voteDto = try savePollVote(payload: payload, query: nil, cache: cache)
voteDto.poll = pollDto
}
}
}

return pollDto
}

Expand Down
8 changes: 7 additions & 1 deletion Sources/StreamChat/Database/DTOs/PollOptionDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class PollOptionDTO: NSManagedObject {
@NSManaged var text: String
@NSManaged var custom: Data?
@NSManaged var poll: PollDTO?

// It contains both latestAnswers and ownVotes, plus every other vote.
// We can't have separate properties unless they have different entities.
// So the only way it would work would be to add a new PollAnswerDTO entity.
@NSManaged var latestVotes: Set<PollVoteDTO>

static func loadOrCreate(
Expand Down Expand Up @@ -56,7 +60,9 @@ extension PollOptionDTO {
return PollOption(
id: id,
text: text,
latestVotes: try latestVotes.map { try $0.asModel() },
latestVotes: try latestVotes
.map { try $0.asModel() }
.sorted(by: { $0.createdAt > $1.createdAt }),
extraData: extraData
)
}
Expand Down
Loading
Loading