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

[CIS-2152] Add support for CDN v2 #2339

Merged
merged 31 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8ef27d2
Add `ImageLoaderOptions` to define image loading configuration
nuno-vieira Oct 10, 2022
c43cb7b
Fix Image Loader request not using resize parameters
nuno-vieira Oct 10, 2022
f3f2c5e
Parse original size of image attachment payload
nuno-vieira Oct 12, 2022
843c176
Add resize mode to thumbnailURL
nuno-vieira Oct 12, 2022
c8f208b
Replace Nuke LateResize with Resize Processor
nuno-vieira Oct 12, 2022
51299b9
Fix Image Attachment Payload JSON Encoding
nuno-vieira Oct 12, 2022
98fb134
Reorganize Utils Folder
nuno-vieira Oct 14, 2022
e854e65
Refactor ImageCDN
nuno-vieira Oct 14, 2022
d44444a
Replace `CGSize.avatarThumbnailSize` with `Components.avatarThumbnail…
nuno-vieira Oct 14, 2022
4b07ace
Add code documentation on the unit of image width and height
nuno-vieira Oct 14, 2022
bd8a735
Refactor `ImageLoading` and deprecate previous API
nuno-vieira Oct 14, 2022
2337994
Replace the deprecated ImageLoader API in the UI Components
nuno-vieira Oct 17, 2022
1516889
Add original resolution when uploading local file
nuno-vieira Oct 18, 2022
845230e
Add ability to load an image attachment and limit the resolution in `…
nuno-vieira Oct 18, 2022
d7adb40
Make the max resolution of image attachments configurable
nuno-vieira Oct 18, 2022
47ddb75
Add test coverage to ImageSizeCalculator
nuno-vieira Oct 18, 2022
2f6be7c
Add test coverage to StreamImageCDN
nuno-vieira Oct 18, 2022
90b67e1
Rename loadMultipleImages -> downloadMultipleImages + Reduce responsi…
nuno-vieira Oct 19, 2022
53a0a7d
Update CHANGELOG.md
nuno-vieira Oct 19, 2022
95f999c
Update CHANGELOG.md
nuno-vieira Oct 19, 2022
3ce95f9
Fix not saving the image task when loading attachments
nuno-vieira Oct 20, 2022
7b55128
Fix the LLC Tests
nuno-vieira Oct 20, 2022
f813005
Make sure the NukeImageLoader API is open
nuno-vieira Oct 20, 2022
1070c48
Add back the addAttachmentToContent(from:type:) to avoid breaking cha…
nuno-vieira Oct 20, 2022
275b872
Update CHANGELOG.md
nuno-vieira Oct 20, 2022
7183420
Reuse image placeholder replacing logic
nuno-vieira Oct 20, 2022
70103c0
Add formula documentation on how to calculate new resolution
nuno-vieira Oct 21, 2022
1802b0b
Improve `downloadImage()` API with `ImageDownloadRequest` structure
nuno-vieira Oct 25, 2022
19347d8
Merge branch 'develop' into add/cdn-v2-implementation
nuno-vieira Oct 25, 2022
a79ea55
Merge branch 'develop' into add/cdn-v2-implementation
nuno-vieira Oct 25, 2022
2459d11
Fix small typo
nuno-vieira Oct 25, 2022
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
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.
nuno-vieira marked this conversation as resolved.
Show resolved Hide resolved
public var maxAttachmentSize: Int64 {
if let customCDNClient = customCDNClient {
return type(of: customCDNClient).maxAttachmentSize
Expand Down
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
nuno-vieira marked this conversation as resolved.
Show resolved Hide resolved
}

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