Skip to content

Commit

Permalink
[CIS-2152] Add support for CDN v2 (#2339)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuno-vieira authored Oct 26, 2022
1 parent 2ef479c commit 6a058c9
Show file tree
Hide file tree
Showing 66 changed files with 1,278 additions and 521 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming

## StreamChat
### ✅ Added
- Added support for Stream's Image CDN v2 [#2339](https://github.com/GetStream/stream-chat-swift/pull/2339)

### 🐞 Fixed
- Fix CurrentChatUserController+Combine initialValue hard coded to `.noUnread` instead of using the initial value from the current user data model [#2334](https://github.com/GetStream/stream-chat-swift/pull/2334)

## StreamChatUI
### ✅ Added
- Uses Stream's Image CDN v2 to reduce the memory footprint [#2339](https://github.com/GetStream/stream-chat-swift/pull/2339)
- Make ChatMessageListVC.tableView(heightForRowAt:) open [#2342](https://github.com/GetStream/stream-chat-swift/pull/2342)
### 🐞 Fixed
- Fix message text not dynamically scalable with content size category changes [#2328](https://github.com/GetStream/stream-chat-swift/pull/2328)

### 🚨 Minor Breaking Changes
Although we don't usually ship breaking changes in minor releases, in some cases where they are minimal and important, we have to do them to keep improving the SDK long-term. Either way, these changes are for advanced customizations which won't affect most of the customers.

- The `ImageCDN` protocol has some minor breaking changes that were needed to support the new Stream CDN v2 and to make it more scalable in the future.
- `urlRequest(forImage:)` -> `urlRequest(forImageUrl:resize:)`.
- `cachingKey(forImage:)` -> `cachingKey(forImageUrl:)`.
- Removed `thumbnail(originalURL:preferreSize:)`. This is now handled by `urlRequest(forImageUrl:resize:)` as well. If your CDN does not support resizing, you can ignore the resize parameter.

# [4.22.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.22.0)
_September 26, 2022_
## StreamChat
Expand Down
2 changes: 1 addition & 1 deletion Examples/YouTubeClone/YTChatMessageContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class YTChatMessageContentView: ChatMessageContentView {
}

override var messageAuthorAvatarSize: CGSize {
.init(width: 40, height: 40)
components.avatarThumbnailSize
}

override func layout(options: ChatMessageLayoutOptions) {
Expand Down
1 change: 0 additions & 1 deletion Sources/StreamChat/Config/ChatClientConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ public struct ChatClientConfig {

/// Returns max possible attachment size in bytes.
/// The value is taken from custom `maxAttachmentSize` type custom `CDNClient` type.
/// The default value is 20 MiB.
public var maxAttachmentSize: Int64 {
if let customCDNClient = customCDNClient {
return type(of: customCDNClient).maxAttachmentSize
Expand Down
15 changes: 13 additions & 2 deletions Sources/StreamChat/Models/Attachments/AnyAttachmentPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ public struct AnyAttachmentPayload {
public let localFileURL: URL?
}

/// Local Metadata related to an attachment.
/// It is used to describe additional information of a local attachment.
public struct AnyAttachmentLocalMetadata {
/// The original width and height of an image or video attachment in Pixels.
public var originalResolution: (width: Double, height: Double)?

public init() {}
}

public extension AnyAttachmentPayload {
/// Creates an instance of `AnyAttachmentPayload` with the given payload.
///
Expand Down Expand Up @@ -64,8 +73,9 @@ public extension AnyAttachmentPayload {
/// - Throws: The error if `localFileURL` is not the file URL or if `extraData` can not be represented as
/// a dictionary.
init(
localFileURL: URL,
attachmentType: AttachmentType,
localFileURL: URL,
localMetadata: AnyAttachmentLocalMetadata? = nil,
extraData: Encodable? = nil
) throws {
let file = try AttachmentFile(url: localFileURL)
Expand All @@ -79,7 +89,8 @@ public extension AnyAttachmentPayload {
payload = ImageAttachmentPayload(
title: localFileURL.lastPathComponent,
imageRemoteURL: localFileURL,
imagePreviewRemoteURL: localFileURL,
originalWidth: localMetadata?.originalResolution?.width,
originalHeight: localMetadata?.originalResolution?.height,
extraData: extraData
)
case .video:
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamChat/Models/Attachments/AttachmentTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ enum AttachmentCodingKeys: String, CodingKey, CaseIterable {
case imageURL = "image_url"
case assetURL = "asset_url"
case titleLink = "title_link"
case originalWidth = "original_width"
case originalHeight = "original_height"
}

/// A local state of the attachment. Applies only for attachments linked to the new messages sent from current device.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public struct ImageAttachmentPayload: AttachmentPayload {
public var imageURL: URL
/// A link to the image preview.
public var imagePreviewURL: URL
/// The original width of the image in pixels.
public var originalWidth: Double?
/// The original height of the image in pixels.
public var originalHeight: Double?
/// An extra data.
public var extraData: [String: RawJSON]?

Expand All @@ -37,10 +41,19 @@ public struct ImageAttachmentPayload: AttachmentPayload {
/// Creates `ImageAttachmentPayload` instance.
///
/// Use this initializer if the attachment is already uploaded and you have the remote URLs.
public init(title: String?, imageRemoteURL: URL, imagePreviewRemoteURL: URL? = nil, extraData: [String: RawJSON]?) {
public init(
title: String?,
imageRemoteURL: URL,
imagePreviewRemoteURL: URL? = nil,
originalWidth: Double? = nil,
originalHeight: Double? = nil,
extraData: [String: RawJSON]? = nil
) {
self.title = title
imageURL = imageRemoteURL
imagePreviewURL = imagePreviewRemoteURL ?? imageRemoteURL
self.originalWidth = originalWidth
self.originalHeight = originalHeight
self.extraData = extraData
}
}
Expand All @@ -54,6 +67,12 @@ extension ImageAttachmentPayload: Encodable {
var values = extraData ?? [:]
values[AttachmentCodingKeys.title.rawValue] = title.map { .string($0) }
values[AttachmentCodingKeys.imageURL.rawValue] = .string(imageURL.absoluteString)

if let originalWidth = self.originalWidth, let originalHeight = self.originalHeight {
values[AttachmentCodingKeys.originalWidth.rawValue] = .double(originalWidth)
values[AttachmentCodingKeys.originalHeight.rawValue] = .double(originalHeight)
}

try values.encode(to: encoder)
}
}
Expand All @@ -75,11 +94,17 @@ extension ImageAttachmentPayload: Decodable {
container.decodeIfPresent(String.self, forKey: .name)
)?.trimmingCharacters(in: .whitespacesAndNewlines)

let previewUrl = try container.decodeIfPresent(URL.self, forKey: .thumbURL) ?? imageURL

let originalWidth = try container.decodeIfPresent(Double.self, forKey: .originalWidth)
let originalHeight = try container.decodeIfPresent(Double.self, forKey: .originalHeight)

self.init(
title: title,
imageRemoteURL: imageURL,
imagePreviewRemoteURL: try container
.decodeIfPresent(URL.self, forKey: .thumbURL) ?? imageURL,
imagePreviewRemoteURL: previewUrl,
originalWidth: originalWidth,
originalHeight: originalHeight,
extraData: try Self.decodeExtraData(from: decoder)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,12 @@ extension ChatMessageGalleryView {
loadingIndicator.isVisible = true
imageTask = components.imageLoader.loadImage(
into: imageView,
url: attachment?.payload.imagePreviewURL,
imageCDN: components.imageCDN,
completion: { [weak self] _ in
self?.loadingIndicator.isVisible = false
self?.imageTask = nil
}
)
from: attachment?.payload,
maxResolutionInPixels: components.imageAttachmentMaxPixels
) { [weak self] _ in
self?.loadingIndicator.isVisible = false
self?.imageTask = nil
}

uploadingOverlay.content = content?.uploadingState
uploadingOverlay.isVisible = attachment?.uploadingState != nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ open class ChatMessageLinkPreviewView: _Control, ThemeProvider {

components.imageLoader.loadImage(
into: imagePreview,
url: payload?.previewURL,
imageCDN: components.imageCDN
from: payload?.previewURL,
with: ImageLoaderOptions()
)
imagePreview.isHidden = isImageHidden

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,11 @@ open class ChatMessageContentView: _View, ThemeProvider, UITextViewDelegate {
if let imageURL = content?.author.imageURL, let imageView = authorAvatarView?.imageView {
components.imageLoader.loadImage(
into: imageView,
url: imageURL,
imageCDN: components.imageCDN,
placeholder: placeholder,
preferredSize: .avatarThumbnailSize
from: imageURL,
with: ImageLoaderOptions(
resize: .init(components.avatarThumbnailSize),
placeholder: placeholder
)
)
} else {
authorAvatarView?.imageView.image = placeholder
Expand Down Expand Up @@ -595,10 +596,11 @@ open class ChatMessageContentView: _View, ThemeProvider, UITextViewDelegate {
if let imageView = threadAvatarView?.imageView {
components.imageLoader.loadImage(
into: imageView,
url: threadAvatarUrl,
imageCDN: components.imageCDN,
placeholder: appearance.images.userAvatarPlaceholder4,
preferredSize: .avatarThumbnailSize
from: threadAvatarUrl,
with: ImageLoaderOptions(
resize: .init(components.avatarThumbnailSize),
placeholder: appearance.images.userAvatarPlaceholder4
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ open class ChatMessageReactionAuthorViewCell: _CollectionViewCell, ThemeProvider
let placeholder = appearance.images.userAvatarPlaceholder1
components.imageLoader.loadImage(
into: authorAvatarView.imageView,
url: content.reaction.author.imageURL,
imageCDN: components.imageCDN,
placeholder: placeholder,
preferredSize: authorAvatarSize
from: content.reaction.author.imageURL,
with: ImageLoaderOptions(
resize: .init(authorAvatarSize),
placeholder: placeholder
)
)

let reactionAuthor = content.reaction.author
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ open class ImageAttachmentComposerPreview: _View, ThemeProvider {

override open func updateContent() {
super.updateContent()

let size = CGSize(width: width, height: height)
components.imageLoader.loadImage(
into: imageView,
url: content,
imageCDN: components.imageCDN
from: content,
with: ImageLoaderOptions(resize: ImageResize(size))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,15 @@ open class ChatChannelAvatarView: _View, ThemeProvider, SwiftUIRepresentable {
placeholderAvatars.append(placeholderImages.removeFirst())
}
}

components.imageLoader.loadImages(
from: avatarUrls,
placeholders: placeholderImages,
imageCDN: components.imageCDN
) { images in

let avatarSize = components.avatarThumbnailSize
let requests = avatarUrls.map {
ImageDownloadRequest(url: $0, options: ImageDownloadOptions(resize: .init(avatarSize)))
}

components.imageLoader.downloadMultipleImages(with: requests) { results in
let imagesMapper = ImageResultsMapper(results: results)
let images = imagesMapper.mapErrors(with: placeholderImages)
completion(images, channelId)
}
}
Expand All @@ -162,11 +165,12 @@ open class ChatChannelAvatarView: _View, ThemeProvider, SwiftUIRepresentable {
let imageProcessor = components.imageProcessor

let images = avatars.map {
imageProcessor.scale(image: $0, to: .avatarThumbnailSize)
imageProcessor.scale(image: $0, to: components.avatarThumbnailSize)
}

// The half of the width of the avatar
let halfContainerSize = CGSize(width: CGSize.avatarThumbnailSize.width / 2, height: CGSize.avatarThumbnailSize.height)
let size = components.avatarThumbnailSize
let halfContainerSize = CGSize(width: size.width / 2, height: size.height)

if images.count == 1, let image = images.first {
combinedImage = image
Expand Down Expand Up @@ -195,10 +199,13 @@ open class ChatChannelAvatarView: _View, ThemeProvider, SwiftUIRepresentable {
],
orientation: .vertical
)

let rightImage = imageProcessor.crop(
image: imageProcessor
.scale(image: rightCollage ?? appearance.images.userAvatarPlaceholder3, to: .avatarThumbnailSize),
.scale(
image: rightCollage ?? appearance.images.userAvatarPlaceholder3,
to: components.avatarThumbnailSize
),
to: halfContainerSize
)

Expand All @@ -225,7 +232,10 @@ open class ChatChannelAvatarView: _View, ThemeProvider, SwiftUIRepresentable {

let leftImage = imageProcessor.crop(
image: imageProcessor
.scale(image: leftCollage ?? appearance.images.userAvatarPlaceholder1, to: .avatarThumbnailSize),
.scale(
image: leftCollage ?? appearance.images.userAvatarPlaceholder1,
to: components.avatarThumbnailSize
),
to: halfContainerSize
)

Expand All @@ -239,7 +249,10 @@ open class ChatChannelAvatarView: _View, ThemeProvider, SwiftUIRepresentable {

let rightImage = imageProcessor.crop(
image: imageProcessor
.scale(image: rightCollage ?? appearance.images.userAvatarPlaceholder2, to: .avatarThumbnailSize),
.scale(
image: rightCollage ?? appearance.images.userAvatarPlaceholder2,
to: components.avatarThumbnailSize
),
to: halfContainerSize
)

Expand All @@ -265,10 +278,11 @@ open class ChatChannelAvatarView: _View, ThemeProvider, SwiftUIRepresentable {
open func loadIntoAvatarImageView(from url: URL?, placeholder: UIImage?) {
components.imageLoader.loadImage(
into: presenceAvatarView.avatarView.imageView,
url: url,
imageCDN: components.imageCDN,
placeholder: placeholder,
preferredSize: .avatarThumbnailSize
from: url,
with: ImageLoaderOptions(
resize: .init(components.avatarThumbnailSize),
placeholder: placeholder
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ open class ChatUserAvatarView: _View, ThemeProvider {
override open func updateContent() {
components.imageLoader.loadImage(
into: presenceAvatarView.avatarView.imageView,
url: content?.imageURL,
imageCDN: components.imageCDN,
placeholder: appearance.images.userAvatarPlaceholder1,
preferredSize: .avatarThumbnailSize
from: content?.imageURL,
with: ImageLoaderOptions(
resize: .init(components.avatarThumbnailSize),
placeholder: appearance.images.userAvatarPlaceholder1
)
)

presenceAvatarView.isOnlineIndicatorVisible = content?.isOnline ?? false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ open class CurrentChatUserAvatarView: _Control, ThemeProvider {
@objc override open func updateContent() {
let currentUserImageUrl = controller?.currentUser?.imageURL
let placeholderImage = appearance.images.userAvatarPlaceholder1

components.imageLoader.loadImage(
into: avatarView.imageView,
url: currentUserImageUrl,
imageCDN: components.imageCDN,
placeholder: placeholderImage,
preferredSize: .avatarThumbnailSize
from: currentUserImageUrl,
with: ImageLoaderOptions(
resize: ImageResize(components.avatarThumbnailSize),
placeholder: placeholderImage
)
)

alpha = state == .normal ? 1 : 0.5
Expand Down
Loading

0 comments on commit 6a058c9

Please sign in to comment.