Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions .github/workflows/smoke-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ jobs:
- run: bundle exec fastlane validate_public_interface
- run: bundle exec fastlane pod_lint
if: startsWith(github.event.pull_request.head.ref, 'release/')
- run: bundle exec fastlane validate_generated_code

build-old-xcode:
name: Build SDKs (Old Xcode)
Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
excluded:
- Scripts
- Sources/StreamChat/Generated
- Sources/StreamChatUI/Generated
- Sources/StreamChatUI/StreamSwiftyGif
- Sources/StreamChatUI/StreamNuke
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming

## StreamChat
### βœ… Added
- Add `ChannelListQuery(predefinedFilter:filterValues:sortValues:)` for creating channel list queries with predefined filters [#4120](https://github.com/GetStream/stream-chat-swift/pull/4120)
### 🐞 Fixed
- Fix WebSocket reconnection getting stuck in `.disconnecting` after the device temporarily loses network connectivity [#4109](https://github.com/GetStream/stream-chat-swift/pull/4109)

Expand Down
87 changes: 86 additions & 1 deletion DemoApp/StreamChat/Components/DemoChatChannelListVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,36 @@ final class DemoChatChannelListVC: ChatChannelListVC {

lazy var premiumTaggedChannelsQuery: ChannelListQuery = .init(filter: .in(.filterTags, values: ["premium"]))

lazy var predefinedMessagingChannelsQuery: ChannelListQuery = .init(
predefinedFilter: "user_per_channel_type_channels",
filterValues: ["channel_type": .string(ChannelType.messaging.rawValue), "user_id": .string(currentUserId)],
sortValues: nil
)

lazy var predefinedArchivedChannelsQuery: ChannelListQuery = .init(
predefinedFilter: "user_per_channel_type_archived_hidden",
filterValues: [
"channel_type": .string(ChannelType.messaging.rawValue),
"hidden": .bool(false),
"user_id": .string(currentUserId),
"archived": .bool(true)
],
sortValues: nil
)

lazy var predefinedHiddenChannelsQuery: ChannelListQuery = .init(
predefinedFilter: "user_per_channel_type_archived_hidden",
filterValues: [
"channel_type": .string(ChannelType.messaging.rawValue),
"hidden": .bool(true),
"user_id": .string(currentUserId),
"archived": .bool(false)
],
sortValues: nil
)

lazy var livestreamChannelsQuery: ChannelListQuery = .init(filter: .equal(.type, to: .livestream))

var demoRouter: DemoChatChannelListRouter? {
router as? DemoChatChannelListRouter
}
Expand Down Expand Up @@ -268,6 +298,41 @@ final class DemoChatChannelListVC: ChatChannelListVC {
}
)

let predefinedMessagingChannelsAction = UIAlertAction(
title: "Messaging (Predefined)",
style: .default,
handler: { [weak self] _ in
self?.title = "Messaging (Predefined)"
self?.setPredefinedMessagingChannelsQuery()
}
)

let predefinedArchivedChannelsAction = UIAlertAction(
title: "Archived (Predefined)",
style: .default,
handler: { [weak self] _ in
self?.title = "Archived (Predefined)"
self?.setPredefinedArchivedChannelsQuery()
}
)

let predefinedHiddenChannelsAction = UIAlertAction(
title: "Hidden (Predefined)",
style: .default,
handler: { [weak self] _ in
self?.title = "Hidden (Predefined)"
self?.setPredefinedHiddenChannelsQuery()
}
)

let livestreamChannelsAction = UIAlertAction(
title: "Livestream Channels",
style: .default
) { [weak self] _ in
self?.title = "Livestream Channels"
self?.setLivestreamChannelsQuery()
}

presentAlert(
title: "Filter Channels",
actions: [
Expand All @@ -283,7 +348,11 @@ final class DemoChatChannelListVC: ChatChannelListVC {
archivedChannelsAction,
equalMembersAction,
channelRoleChannelsAction,
taggedChannelsAction
taggedChannelsAction,
predefinedMessagingChannelsAction,
predefinedArchivedChannelsAction,
predefinedHiddenChannelsAction,
livestreamChannelsAction
].sorted(by: { $0.title ?? "" < $1.title ?? "" }),
preferredStyle: .actionSheet,
sourceView: filterChannelsButton
Expand Down Expand Up @@ -344,6 +413,22 @@ final class DemoChatChannelListVC: ChatChannelListVC {
replaceQuery(premiumTaggedChannelsQuery)
}

func setPredefinedMessagingChannelsQuery() {
replaceQuery(predefinedMessagingChannelsQuery)
}

func setPredefinedArchivedChannelsQuery() {
replaceQuery(predefinedArchivedChannelsQuery)
}

func setPredefinedHiddenChannelsQuery() {
replaceQuery(predefinedHiddenChannelsQuery)
}

func setLivestreamChannelsQuery() {
replaceQuery(livestreamChannelsQuery)
}

func setInitialChannelsQuery() {
replaceQuery(initialQuery)
}
Expand Down
4 changes: 2 additions & 2 deletions DemoApp/StreamChat/StreamChatWrapper+DemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ extension StreamChatWrapper {
// Instantiates chat client
func setUpChat() {
// Set the log level
LogConfig.level = StreamRuntimeCheck.logLevel ?? .warning
LogConfig.level = StreamRuntimeCheck.logLevel ?? .debug
LogConfig.formatters = [
PrefixLogFormatter(prefixes: [.info: "ℹ️", .debug: "πŸ› ", .warning: "⚠️", .error: "🚨"])
]
if let subsystems = StreamRuntimeCheck.subsystems {
LogConfig.subsystems = subsystems
}

LogConfig.subsystems = .httpRequests
Comment thread
laevandus marked this conversation as resolved.
Outdated
// Create Client
if client == nil {
client = ChatClient(config: config)
Expand Down
1 change: 1 addition & 0 deletions Githubfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export INTERFACE_ANALYZER_VERSION='1.0.7'
export SWIFT_LINT_VERSION='0.59.1'
export SWIFT_FORMAT_VERSION='0.58.2'
export SWIFT_GEN_VERSION='6.5.1'
export SOURCERY_VERSION='2.3.0'
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ MAKEFLAGS += --silent
bootstrap:
./Scripts/bootstrap.sh

generate:
sourcery --config Sources/StreamChat/.sourcery.yml

all_artifacts:
echo "🏁 Starting at $$(date +%T)"
make frameworks
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let package = Package(
targets: [
.target(
name: "StreamChat",
exclude: ["Info.plist"],
exclude: ["Info.plist", "Generated/PredefinedFilter.stencil"],
resources: [.copy("Database/StreamChatModel.xcdatamodeld")]
),
.target(
Expand Down
13 changes: 13 additions & 0 deletions Scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ if [ "${SKIP_SWIFT_BOOTSTRAP:-}" != true ]; then
sudo sudo rm -f "$BIN_PATH"
sudo sudo ln -s "$INSTALL_DIR/bin/swiftgen" "$BIN_PATH"
swiftgen --version

puts "Install Sourcery v${SOURCERY_VERSION}"
DOWNLOAD_URL="https://github.com/krzysztofzablocki/Sourcery/releases/download/${SOURCERY_VERSION}/sourcery-${SOURCERY_VERSION}.zip"
DOWNLOAD_PATH="/tmp/sourcery-${SOURCERY_VERSION}.zip"
INSTALL_DIR="/usr/local/lib/sourcery"
BIN_PATH="/usr/local/bin/sourcery"
wget "$DOWNLOAD_URL" -O "$DOWNLOAD_PATH"
sudo rm -rf "$INSTALL_DIR"
sudo mkdir -p "$INSTALL_DIR"
sudo unzip -o "$DOWNLOAD_PATH" -d "$INSTALL_DIR"
sudo rm -f "$BIN_PATH"
sudo ln -s "$INSTALL_DIR/bin/sourcery" "$BIN_PATH"
sourcery --version
fi

if [[ ${INSTALL_SONAR-default} == true ]]; then
Expand Down
7 changes: 7 additions & 0 deletions Sources/StreamChat/.sourcery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
sources:
- ./Query/ChannelListQuery.swift
- ./Query/Sorting/ChannelListSortingKey.swift
templates:
- ./Generated/PredefinedFilter.stencil
output:
./Generated/PredefinedFilter+Generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,48 @@ import Foundation
struct ChannelListPayload {
/// A list of channels response (see `ChannelQuery`).
let channels: [ChannelPayload]

/// Server-resolved predefined filter, present only when the query was made with a predefined filter.
let predefinedFilter: PredefinedFilterPayload?

init(channels: [ChannelPayload], predefinedFilter: PredefinedFilterPayload? = nil) {
self.channels = channels
self.predefinedFilter = predefinedFilter
}
}

extension ChannelListPayload: Decodable {
enum CodingKeys: String, CodingKey {
case channels
case predefinedFilter = "predefined_filter"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let channels = try container
.decodeArrayIgnoringFailures([ChannelPayload].self, forKey: .channels)
let predefinedFilter = try container
.decodeIfPresent(PredefinedFilterPayload.self, forKey: .predefinedFilter)

self.init(
channels: channels
channels: channels,
predefinedFilter: predefinedFilter
)
}
}

final class PredefinedFilterPayload: Decodable, Sendable {
let name: String
let filter: [String: RawJSON]
let sort: [[String: RawJSON]]

init(name: String, filter: [String: RawJSON], sort: [[String: RawJSON]]) {
self.name = name
self.filter = filter
self.sort = sort
}
}

struct ChannelPayload {
let channel: ChannelDetailPayload

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
/// - Note: For an async-await alternative of the `ChatChannelListController`, please check ``ChannelList`` in the async-await supported [state layer](https://getstream.io/chat/docs/sdk/ios/client/state-layer/state-layer-overview/).
public class ChatChannelListController: DataController, DelegateCallable, DataStoreProvider {
/// The query specifying and filtering the list of channels.
public let query: ChannelListQuery
public internal(set) var query: ChannelListQuery

/// The `ChatClient` instance this controller belongs to.
public let client: ChatClient
Expand Down Expand Up @@ -82,8 +82,15 @@
}

private(set) lazy var channelListObserver: BackgroundListDatabaseObserver<ChatChannel, ChannelDTO> = {
if let updated = worker.loadPredefinedFilter(for: query) {
query = updated
}
return makeChannelListObserver()
}()

private func makeChannelListObserver() -> BackgroundListDatabaseObserver<ChatChannel, ChannelDTO> {
let request = ChannelDTO.channelListFetchRequest(query: self.query, chatClientConfig: client.config)
let observer = self.environment.createChannelListDatabaseObserver(
let observer = environment.createChannelListDatabaseObserver(
client.databaseContainer,
request,
{ try $0.asModel() },
Expand Down Expand Up @@ -113,7 +120,7 @@
}

return observer
}()
}

var _basePublishers: Any?
/// An internal backing object for all publicly available Combine publishers. We use it to simplify the way we expose
Expand Down Expand Up @@ -158,7 +165,9 @@
startChannelListObserverIfNeeded()
channelListLinker.start(with: client.eventNotificationCenter)
client.syncRepository.startTrackingChannelListController(self)
updateChannelList(completion)
updateChannelList { [weak self] result in
self?.callback { completion?(result.error) }
}
}

// MARK: - Actions
Expand All @@ -184,9 +193,9 @@
updatedQuery.pagination = Pagination(pageSize: limit, offset: channels.count)
worker.update(channelListQuery: updatedQuery) { result in
switch result {
case let .success(channels):
self.markChannelsAsDeliveredIfNeeded(channels: channels)
self.hasLoadedAllPreviousChannels = channels.count < limit
case let .success(updateResult):
self.markChannelsAsDeliveredIfNeeded(channels: updateResult.channels)
self.hasLoadedAllPreviousChannels = updateResult.channels.count < limit
self.callback { completion?(nil) }
case let .failure(error):
self.callback { completion?(error) }
Expand All @@ -213,24 +222,30 @@
// MARK: - Helpers

private func updateChannelList(
_ completion: ((_ error: Error?) -> Void)? = nil
_ completion: ((Result<ChannelListUpdateResult, Error>) -> Void)? = nil
) {
let limit = query.pagination.pageSize
worker.update(
channelListQuery: query
) { [weak self] result in
switch result {
case let .success(channels):
case let .success(updateResult):
self?.state = .remoteDataFetched
self?.hasLoadedAllPreviousChannels = channels.count < limit
self?.hasLoadedAllPreviousChannels = updateResult.channels.count < limit

// Mark channels as delivered if synchronization was successful
self?.markChannelsAsDeliveredIfNeeded(channels: channels)
self?.markChannelsAsDeliveredIfNeeded(channels: updateResult.channels)

// Predefined filters can update local query representation (query gets backend defined filter and sort which must be set to FRC)
if let updatedQuery = updateResult.updatedQuery {
self?.query = updatedQuery
self?.updateChannelListObserver()
}

self?.callback { completion?(nil) }
self?.callback { completion?(.success(updateResult)) }
case let .failure(error):
self?.state = .remoteDataFetchFailed(ClientError(with: error))
self?.callback { completion?(error) }
self?.callback { completion?(.failure(error)) }
}
}
}
Expand Down Expand Up @@ -278,6 +293,16 @@
log.error("Failed to perform fetch request with error: \(error). This is an internal error.")
}
}

private func updateChannelListObserver() {
channelListObserver = makeChannelListObserver()
do {
try channelListObserver.startObserving()
} catch {
state = .localDataFetchFailed(ClientError(with: error))
log.error("Failed to update the channel list observer: \(error)")
}
}
}

extension ChatChannelListController {
Expand Down Expand Up @@ -352,7 +377,7 @@
}

public extension ChatChannelListControllerDelegate {
func controllerWillChangeChannels(_ controller: ChatChannelListController) {}

Check warning on line 380 in Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "controller" or name it "_".

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-chat-swift&issues=AZ6It891xd6poH0YmJba&open=AZ6It891xd6poH0YmJba&pullRequest=4120

func controller(
_ controller: ChatChannelListController,
Expand Down
6 changes: 3 additions & 3 deletions Sources/StreamChat/Database/DTOs/ChannelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ extension NSManagedObjectContext {
// the query won't be saved, which will cause any future
// channels to not become linked to this query
if let query = query {
_ = saveQuery(query: query)
_ = saveQuery(query: query, predefinedFilter: payload.predefinedFilter)
}

return payload.channels.compactMapLoggingError { channelPayload in
Expand Down Expand Up @@ -441,7 +441,7 @@ extension NSManagedObjectContext {
}

func delete(query: ChannelListQuery) {
guard let dto = channelListQuery(filterHash: query.filter.filterHash) else { return }
guard let dto = channelListQuery(query) else { return }

delete(dto)
}
Expand Down Expand Up @@ -474,7 +474,7 @@ extension ChannelDTO {

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

let matchingQuery = NSPredicate(format: "ANY queries.filterHash == %@", query.filter.filterHash)
let matchingQuery = NSPredicate(format: "ANY queries.filterHash == %@", query.queryHash)
let notDeleted = NSPredicate(format: "deletedAt == nil")

var subpredicates: [NSPredicate] = [
Expand Down
Loading
Loading