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.61.0 Release #568

Merged
merged 10 commits into from
Jul 31, 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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
with:
ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }}
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Extract version from branch name (for release branches)
if: startsWith(github.event.pull_request.head.ref, 'release/')
run: |
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/sdk-size-metrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: SDK Size

on:
pull_request:

workflow_dispatch:

push:
branches:
- develop

env:
HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI

jobs:
sdk_size:
name: Metrics
runs-on: macos-14
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_GITHUB_TOKEN }}'
steps:
- name: Install Bot SSH Key
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }}

- uses: actions/[email protected]

- uses: ./.github/actions/bootstrap

- name: Get branch name
id: get_branch_name
run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT

- name: Run SDK Size Metrics
run: bundle exec fastlane show_frameworks_sizes
timeout-minutes: 30
env:
BRANCH_NAME: ${{ steps.get_branch_name.outputs.branch }}
GITHUB_PR_NUM: ${{ github.event.pull_request.number }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }}
1 change: 1 addition & 0 deletions .github/workflows/smoke-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ jobs:
env:
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUM: ${{ github.event.number }}
GITHUB_EVENT: ${{ toJson(github.event) }}
- id: get_launch_id
run: echo "launch_id=${{env.LAUNCH_ID}}" >> $GITHUB_OUTPUT
Expand Down
11 changes: 3 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,6 @@ playground.xcworkspace
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
Carthage/
!Sample/Carthage/

# Ignore Products folder
Products/

Expand All @@ -77,14 +71,13 @@ fastlane/screenshots
fastlane/test_output
fastlane/allurectl
fastlane/xcresults
fastlane/metrics
fastlane/recordings
StreamChatCore.framework.coverage.txt
StreamChatCoreTests.xctest.coverage.txt
vendor/bundle/
.bundle/
.swiftpm
Example/Carthage/.env
Example/Carthage/fastlane/report.xml
Sample/Cocoapods/Podfile.lock
docusaurus/.env
reports/
Expand All @@ -95,4 +88,6 @@ derived_data/
spm_cache/
.buildcache
buildcache
App Thinning Size Report.txt
app-thinning.plist
*.dmg
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### 🔄 Changed

# [4.61.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.61.0)
_July 31, 2024_

### ⚡ Performance
- Optimise channel list view updates [#561](https://github.com/GetStream/stream-chat-swiftui/pull/561)

### 🐞 Fixed
- Media and files attachments not showing in channel info view [#554](https://github.com/GetStream/stream-chat-swiftui/pull/554)
- Bottom reactions configuration not always updating reactions [#557](https://github.com/GetStream/stream-chat-swiftui/pull/557)

### 🔄 Changed
- Channel list views do not use explicit id anymore [#561](https://github.com/GetStream/stream-chat-swiftui/pull/561)
- Deprecate `ChannelAvatarView` initializer `init(avatar:showOnlineIndicator:size:)` in favor of `init(channel:showOnlineIndicator:size:)` [#561](https://github.com/GetStream/stream-chat-swiftui/pull/561)

# [4.60.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.60.0)
_July 19, 2024_

Expand Down
4 changes: 1 addition & 3 deletions DemoAppSwiftUI/PinChannelHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct DemoAppChatChannelListItem: View {
} label: {
HStack {
ChannelAvatarView(
avatar: avatar,
channel: channel,
showOnlineIndicator: onlineIndicatorShown
)

Expand Down Expand Up @@ -83,7 +83,6 @@ struct DemoAppChatChannelListItem: View {
.foregroundColor(.black)
.disabled(disabled)
.background(channel.isPinned ? Color(colors.pinnedBackground) : .clear)
.id("\(channel.id)-\(channel.isPinned ? "pinned" : "not-pinned")-base")
}

private var subtitleView: some View {
Expand Down Expand Up @@ -173,7 +172,6 @@ struct DemoAppChatChannelNavigatableListItem<ChannelDestination: View>: View {
EmptyView()
}
}
.id("\(channel.id)-\(channel.isPinned ? "pinned" : "not-pinned")-base")
}

private var injectedChannelInfo: InjectedChannelInfo? {
Expand Down
2 changes: 1 addition & 1 deletion DemoAppSwiftUI/WhatsAppChannelHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct WhatsAppChannelHeader: ToolbarContent {
ToolbarItem(placement: .principal) {
HStack {
ChannelAvatarView(
avatar: channelHeaderLoader.image(for: channel),
channel: channel,
showOnlineIndicator: false,
size: CGSize(width: 36, height: 36)
)
Expand Down
2 changes: 1 addition & 1 deletion DemoAppSwiftUI/iMessagePocView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct iMessagePocView: View {
HStack {
ForEach(viewModel.pinnedChannels) { channel in
ChannelAvatarView(
avatar: channelHeaderLoader.image(for: channel),
channel: channel,
showOnlineIndicator: false
)
.padding()
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.60.0"),
.package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.61.0"),
],
targets: [
.target(
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

<p align="center">
<a href="https://sonarcloud.io/summary/new_code?id=GetStream_stream-chat-swiftui"><img src="https://sonarcloud.io/api/project_badges/measure?project=GetStream_stream-chat-swiftui&metric=coverage" /></a>

<img id="stream-chat-swiftui-label" alt="StreamChatSwiftUI" src="https://img.shields.io/badge/StreamChatSwiftUI-7.01MB-blue"/>
</p>

## SwiftUI StreamChat SDK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import StreamChat
import SwiftUI

/// View model for the `FileAttachmentsView`.
class FileAttachmentsViewModel: ObservableObject {

class FileAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDelegate {
@Published var loading = false
@Published var attachmentsDataSource = [MonthlyFileAttachments]()
@Published var selectedAttachment: ChatMessageFileAttachment?
Expand All @@ -28,6 +28,7 @@ class FileAttachmentsViewModel: ObservableObject {

dateFormatter.dateFormat = "MMMM yyyy"
messageSearchController = chatClient.messageSearchController()
messageSearchController.delegate = self
loadMessages()
}

Expand Down Expand Up @@ -73,6 +74,12 @@ class FileAttachmentsViewModel: ObservableObject {
}
}
}

// MARK: - ChatMessageSearchControllerDelegate

func controller(_ controller: ChatMessageSearchController, didChangeMessages changes: [ListChange<ChatMessage>]) {
updateAttachments()
}

private func loadMessages() {
let query = MessageSearchQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import StreamChat
import SwiftUI

/// View model for the `MediaAttachmentsView`.
class MediaAttachmentsViewModel: ObservableObject {
class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDelegate {

@Published var mediaItems = [MediaItem]()
@Published var loading = false
Expand All @@ -27,6 +27,7 @@ class MediaAttachmentsViewModel: ObservableObject {
init(channel: ChatChannel) {
self.channel = channel
messageSearchController = chatClient.messageSearchController()
messageSearchController.delegate = self
loadMessages()
}

Expand All @@ -53,6 +54,12 @@ class MediaAttachmentsViewModel: ObservableObject {
}
}
}

// MARK: - ChatMessageSearchControllerDelegate

func controller(_ controller: ChatMessageSearchController, didChangeMessages changes: [ListChange<ChatMessage>]) {
updateAttachments()
}

private func loadMessages() {
let query = MessageSearchQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright © 2024 Stream.io Inc. All rights reserved.
//

import Combine
import Foundation
import StreamChat
import UIKit
Expand All @@ -15,10 +16,10 @@ open class ChannelHeaderLoader: ObservableObject {
private let maxNumberOfImagesInCombinedAvatar = 4

/// Prevents image requests to be executed if they failed previously.
private var failedImageLoads = Set<String>()
private var failedImageLoads = Set<ChannelId>()

/// Batches loaded images for update, to improve performance.
private var scheduledUpdate = false
private var scheduledUpdates = Set<ChannelId>()

/// Context provided utils.
internal lazy var imageLoader = utils.imageLoader
Expand All @@ -31,18 +32,9 @@ open class ChannelHeaderLoader: ObservableObject {
internal lazy var placeholder2 = images.userAvatarPlaceholder2
internal lazy var placeholder3 = images.userAvatarPlaceholder3
internal lazy var placeholder4 = images.userAvatarPlaceholder4

var loadedImages = [String: UIImage]() {
willSet {
if !scheduledUpdate {
scheduledUpdate = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.objectWillChange.send()
self?.scheduledUpdate = false
}
}
}
}

private var loadedImages = [ChannelId: UIImage]()
private let didLoadImage = PassthroughSubject<ChannelId, Never>()

public init() {
// Public init.
Expand All @@ -53,19 +45,19 @@ open class ChannelHeaderLoader: ObservableObject {
/// - Parameter channel: the provided channel.
/// - Returns: the available image.
public func image(for channel: ChatChannel) -> UIImage {
if let image = loadedImages[channel.cid.rawValue] {
if let image = loadedImages[channel.cid] {
return image
}

if let url = channel.imageURL {
loadChannelThumbnail(for: channel, from: url)
loadChannelThumbnail(for: channel.cid, from: url)
return placeholder4
}

if channel.isDirectMessageChannel {
let lastActiveMembers = self.lastActiveMembers(for: channel)
if let otherMember = lastActiveMembers.first, let url = otherMember.imageURL {
loadChannelThumbnail(for: channel, from: url)
loadChannelThumbnail(for: channel.cid, from: url)
return placeholder3
} else {
return placeholder4
Expand All @@ -84,16 +76,38 @@ open class ChannelHeaderLoader: ObservableObject {
if urls.isEmpty {
return placeholder3
} else {
loadMergedAvatar(from: channel, urls: Array(urls))
loadMergedAvatar(from: channel.cid, urls: Array(urls))
return placeholder4
}
}
}

func channelAvatarChanged(_ cid: ChannelId?) -> AnyPublisher<Void, Never> {
didLoadImage
.filter { $0 == cid }
.map { _ in () }
.eraseToAnyPublisher()
}

// MARK: - private

private func loadMergedAvatar(from channel: ChatChannel, urls: [URL]) {
if failedImageLoads.contains(channel.cid.rawValue) {
private func didFinishedLoading(for cid: ChannelId, image: UIImage) {
loadedImages[cid] = image

if scheduledUpdates.isEmpty {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self else { return }
let updates = self.scheduledUpdates
self.scheduledUpdates.removeAll()
updates.forEach { self.didLoadImage.send($0) }
}
}

scheduledUpdates.insert(cid)
}

private func loadMergedAvatar(from cid: ChannelId, urls: [URL]) {
if failedImageLoads.contains(cid) {
return
}

Expand All @@ -109,20 +123,20 @@ open class ChannelHeaderLoader: ObservableObject {
let image = self.channelAvatarsMerger.createMergedAvatar(from: images)
DispatchQueue.main.async {
if let image = image {
self.loadedImages[channel.cid.rawValue] = image
self.didFinishedLoading(for: cid, image: image)
} else {
self.failedImageLoads.insert(channel.cid.rawValue)
self.failedImageLoads.insert(cid)
}
}
}
}
}

private func loadChannelThumbnail(
for channel: ChatChannel,
for cid: ChannelId,
from url: URL
) {
if failedImageLoads.contains(channel.cid.rawValue) {
if failedImageLoads.contains(cid) {
return
}

Expand All @@ -136,10 +150,10 @@ open class ChannelHeaderLoader: ObservableObject {
switch result {
case let .success(image):
DispatchQueue.main.async {
self.loadedImages[channel.cid.rawValue] = image
self.didFinishedLoading(for: cid, image: image)
}
case let .failure(error):
self.failedImageLoads.insert(channel.cid.rawValue)
self.failedImageLoads.insert(cid)
log.error("error loading image: \(error.localizedDescription)")
}
}
Expand Down
Loading
Loading