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.63.0 Release #3412

Closed
wants to merge 17 commits into from
Closed
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
9 changes: 2 additions & 7 deletions .github/actions/setup-ios-runtime/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,13 @@ description: 'Download and Install requested iOS Runtime'
runs:
using: "composite"
steps:
- name: Cache iOS Simulator Runtime
uses: actions/cache@v4
id: runtime-cache
with:
path: ./*.dmg
key: ipsw-runtime-ios-${{ inputs.version }}
restore-keys: ipsw-runtime-ios-${{ inputs.version }}
- name: Setup iOS Simulator Runtime
shell: bash
run: |
sudo rm -rfv ~/Library/Developer/CoreSimulator/* || true
brew install blacktop/tap/ipsw
bundle exec fastlane install_runtime ios:${{ inputs.version }}
sudo rm -rfv *.dmg || true
xcrun simctl list runtimes
- name: Create Custom iOS Simulator
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- uses: ./.github/actions/ruby-cache

- name: Merge
run: bundle exec fastlane merge_release_to_main author:"$USER_LOGIN" --verbose
run: bundle exec fastlane merge_release author:"$USER_LOGIN" --verbose
env:
GITHUB_TOKEN: ${{ secrets.ADMIN_API_TOKEN }} # A token with the "admin:org" scope to get the list of the team members on GitHub
GITHUB_PR_NUM: ${{ github.event.issue.number }}
Expand Down
32 changes: 4 additions & 28 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -1,56 +1,32 @@
name: "Publish new release"

on:
pull_request:
push:
branches:
- main
types:
- closed

workflow_dispatch:
inputs:
version:
description: 'Release version'
type: string
required: true

jobs:
release:
name: Publish new release
runs-on: macos-12
if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true }} # only merged pull requests must trigger this job
steps:
- name: Connect Bot
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }}

- uses: actions/[email protected]
with:
fetch-depth: 0

- uses: ./.github/actions/ruby-cache

- name: Extract version from input (for workflow dispatch)
if: ${{ github.event_name == 'workflow_dispatch' }}
run: |
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH_NAME" != "main" ]; then
echo "This workflow can only be run on the main branch."
exit 1
fi
echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV

- name: Extract version from branch name (for release branches)
if: ${{ github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') }}
run: |
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
VERSION=${BRANCH_NAME#release/}
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV

- name: "Fastlane - Publish Release"
if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.pull_request.head.ref, 'release/') }}
env:
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }}
run: bundle exec fastlane publish_release version:${{ env.RELEASE_VERSION }} --verbose
run: bundle exec fastlane publish_release --verbose
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### 🔄 Changed

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

## StreamChat
### ✅ Added
- Local attachment downloads ([docs](https://getstream.io/chat/docs/sdk/ios/client/attachment-downloads)) [#3393](https://github.com/GetStream/stream-chat-swift/pull/3393)
- Add `downloadAttachment(_:)` and `deleteLocalAttachmentDownload(for:)` to `Chat` and `MessageController`
- Add `deleteAllLocalAttachmentDownloads()` to `ConnectedUser` and `CurrentUserController`
- Add `unset` argument to `CurrentChatUserController.updateUserData` and `ConnectedUser.update` for clearing user data fields [#3404](https://github.com/GetStream/stream-chat-swift/pull/3404)
### 🐞 Fixed
- Fix Logger printing the incorrect thread name [#3382](https://github.com/GetStream/stream-chat-swift/pull/3382)
- Channel watching did not resume on web-socket reconnection [#3409](https://github.com/GetStream/stream-chat-swift/pull/3409)
### 🔄 Changed
- Discard offline state changes when saving database changes fails [#3399](https://github.com/GetStream/stream-chat-swift/pull/3399)

## StreamChatUI
### ✅ Added
- Downloading and sharing file attachments in the message list [#3393](https://github.com/GetStream/stream-chat-swift/pull/3393)
- Feature toggle for download and share buttons: `Components.default.isDownloadFileAttachmentsEnabled` (default is `false`)

# [4.62.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.62.0)
_August 15, 2024_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class AppConfigViewController: UITableViewController {
case mentionAllAppUsers
case isBlockingUsersEnabled
case isMessageListAnimationsEnabled
case isDownloadFileAttachmentsEnabled
}

enum ChatClientConfigOption: String, CaseIterable {
Expand Down Expand Up @@ -477,6 +478,10 @@ class AppConfigViewController: UITableViewController {
cell.accessoryView = makeSwitchButton(Components.default.isMessageListAnimationsEnabled) { newValue in
Components.default.isMessageListAnimationsEnabled = newValue
}
case .isDownloadFileAttachmentsEnabled:
cell.accessoryView = makeSwitchButton(Components.default.isDownloadFileAttachmentsEnabled) { newValue in
Components.default.isDownloadFileAttachmentsEnabled = newValue
}
}
}

Expand Down
28 changes: 28 additions & 0 deletions DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,34 @@ final class DemoChatChannelListRouter: ChatChannelListRouter {
self?.showChannel(for: cid, at: message?.id)
}
}
}),
.init(title: "Delete Downloaded Attachments", handler: { [unowned self] _ in
do {
let connectedUser = try self.rootViewController.controller.client.makeConnectedUser()
Task {
do {
try await connectedUser.deleteAllLocalAttachmentDownloads()
} catch {
self.rootViewController.presentAlert(title: error.localizedDescription)
}
}
} catch {
self.rootViewController.presentAlert(title: error.localizedDescription)
}
}),
.init(title: "Reset User Image", handler: { [unowned self] _ in
do {
let connectedUser = try self.rootViewController.controller.client.makeConnectedUser()
Task {
do {
try await connectedUser.update(unset: ["image"])
} catch {
self.rootViewController.presentAlert(title: error.localizedDescription)
}
}
} catch {
self.rootViewController.presentAlert(title: error.localizedDescription)
}
})
])
}
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.60)
fastlane-plugin-stream_actions (0.3.63)
xctest_list (= 1.2.1)
fastlane-plugin-versioning (0.5.2)
ffi (1.17.0)
Expand Down Expand Up @@ -330,7 +330,7 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.3.3)
rexml (3.3.6)
strscan
rouge (2.0.7)
rubocop (1.38.0)
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.60)
fastlane-plugin-stream_actions (= 0.3.63)
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.75%20MB-blue"/>
<img id="stream-chat-ui-label" alt="StreamChatUI" src="https://img.shields.io/badge/StreamChatUI-4.41%20MB-blue"/>
<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"/>
</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
41 changes: 32 additions & 9 deletions Sources/StreamChat/APIClient/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class APIClient {
/// Used to queue requests that happen while we are offline
var queueOfflineRequest: QueueOfflineRequestBlock?

/// The attachment downloader.
let attachmentDownloader: AttachmentDownloader

/// The attachment uploader.
let attachmentUploader: AttachmentUploader

Expand Down Expand Up @@ -59,11 +62,13 @@ class APIClient {
sessionConfiguration: URLSessionConfiguration,
requestEncoder: RequestEncoder,
requestDecoder: RequestDecoder,
attachmentDownloader: AttachmentDownloader,
attachmentUploader: AttachmentUploader
) {
encoder = requestEncoder
decoder = requestDecoder
session = URLSession(configuration: sessionConfiguration)
self.attachmentDownloader = attachmentDownloader
self.attachmentUploader = attachmentUploader
}

Expand Down Expand Up @@ -226,19 +231,12 @@ class APIClient {
return
}

log.debug(
"Making URL request: \(endpoint.method.rawValue.uppercased()) \(endpoint.path)\n"
+ "Headers:\n\(String(describing: urlRequest.allHTTPHeaderFields))\n"
+ "Body:\n\(urlRequest.httpBody?.debugPrettyPrintedJSON ?? "<Empty>")\n"
+ "Query items:\n\(urlRequest.queryItems.prettyPrinted)",
subsystems: .httpRequests
)

guard let self = self else {
log.warning("Callback called while self is nil", subsystems: .httpRequests)
completion(.failure(ClientError("APIClient was deallocated")))
return
}
log.debug(urlRequest.cURLRepresentation(for: self.session), subsystems: .httpRequests)

let task = self.session.dataTask(with: urlRequest) { [decoder = self.decoder] (data, response, error) in
do {
Expand Down Expand Up @@ -288,7 +286,32 @@ class APIClient {
// We only retry transient errors like connectivity stuff or HTTP 5xx errors
ClientError.isEphemeral(error: error)
}


func downloadFile(
from remoteURL: URL,
to localURL: URL,
progress: ((Double) -> Void)?,
completion: @escaping (Error?) -> Void
) {
let downloadOperation = AsyncOperation(maxRetries: maximumRequestRetries) { [weak self] operation, done in
self?.attachmentDownloader.download(from: remoteURL, to: localURL, progress: progress) { error in
if let error, self?.isConnectionError(error) == true {
// Do not retry unless its a connection problem and we still have retries left
if operation.canRetry {
done(.retry)
} else {
completion(error)
done(.continue)
}
} else {
completion(error)
done(.continue)
}
}
}
operationQueue.addOperation(downloadOperation)
}

func uploadAttachment(
_ attachment: AnyChatMessageAttachment,
progress: ((Double) -> Void)?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// Copyright © 2024 Stream.io Inc. All rights reserved.
//

import Foundation

/// The component responsible for downloading files.
protocol AttachmentDownloader {
/// Downloads a file attachment to the specified local URL.
///
/// - Parameters:
/// - remoteURL: A remote URL of the file.
/// - localURL: The destination URL of the download.
/// - progress: The progress of the download.
/// - completion: The callback with an error if a failure occured.
func download(
from remoteURL: URL,
to localURL: URL,
progress: ((Double) -> Void)?,
completion: @escaping (Error?) -> Void
)
}

final class StreamAttachmentDownloader: AttachmentDownloader {
private let session: URLSession
@Atomic private var taskProgressObservers: [Int: NSKeyValueObservation] = [:]

init(sessionConfiguration: URLSessionConfiguration) {
session = URLSession(configuration: sessionConfiguration)
}

func download(
from remoteURL: URL,
to localURL: URL,
progress: ((Double) -> Void)?,
completion: @escaping (Error?) -> Void
) {
let request = URLRequest(url: remoteURL)
let task = session.downloadTask(with: request) { temporaryURL, _, downloadError in
if let downloadError {
completion(downloadError)
} else if let temporaryURL {
do {
try FileManager.default.createDirectory(at: localURL.deletingLastPathComponent(), withIntermediateDirectories: true)
if FileManager.default.fileExists(atPath: localURL.path) {
try FileManager.default.removeItem(at: localURL)
}
try FileManager.default.moveItem(at: temporaryURL, to: localURL)
completion(nil)
} catch {
completion(error)
}
}
}
if let progressHandler = progress {
let taskID = task.taskIdentifier
_taskProgressObservers.mutate { observers in
observers[taskID] = task.progress.observe(\.fractionCompleted, options: [.initial]) { [weak self] progress, _ in
progressHandler(progress.fractionCompleted)
if progress.isFinished || progress.isCancelled {
self?._taskProgressObservers.mutate { observers in
observers[taskID]?.invalidate()
observers[taskID] = nil
}
}
}
}
}
task.resume()
}
}
6 changes: 4 additions & 2 deletions Sources/StreamChat/APIClient/Endpoints/UserEndpoints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ extension Endpoint {

static func updateUser(
id: UserId,
payload: UserUpdateRequestBody
payload: UserUpdateRequestBody,
unset: [String]
) -> Endpoint<CurrentUserUpdateResponse> {
let users: [String: AnyEncodable] = [
"id": AnyEncodable(id),
"set": AnyEncodable(payload)
"set": AnyEncodable(payload),
"unset": AnyEncodable(unset)
]
let body: [String: AnyEncodable] = [
"users": AnyEncodable([users])
Expand Down
10 changes: 2 additions & 8 deletions Sources/StreamChat/ChatClient+Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,9 @@ extension ChatClient {
_ sessionConfiguration: URLSessionConfiguration,
_ requestEncoder: RequestEncoder,
_ requestDecoder: RequestDecoder,
_ attachmentDownloader: AttachmentDownloader,
_ attachmentUploader: AttachmentUploader
) -> APIClient = {
APIClient(
sessionConfiguration: $0,
requestEncoder: $1,
requestDecoder: $2,
attachmentUploader: $3
)
}
) -> APIClient = APIClient.init

var webSocketClientBuilder: ((
_ sessionConfiguration: URLSessionConfiguration,
Expand Down
Loading
Loading