Skip to content

Commit e79688b

Browse files
Implemented max file size checks
1 parent f17f6c3 commit e79688b

File tree

7 files changed

+235
-40
lines changed

7 files changed

+235
-40
lines changed

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
133133
}
134134

135135
public func handleMessageAppear(index: Int) {
136+
if index >= messages.count {
137+
return
138+
}
136139
let message = messages[index]
137140
checkForNewMessages(index: index)
138141
if utils.messageListConfig.dateIndicatorPlacement == .overlay {
@@ -200,7 +203,6 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
200203
}
201204

202205
public func onViewAppear() {
203-
reactionsShown = false
204206
isActive = true
205207
messages = channelDataSource.messages
206208
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ open class MessageComposerViewModel: ObservableObject {
6161

6262
@Published public var addedFileURLs = [URL]() {
6363
didSet {
64+
if totalAttachmentsCount > chatClient.config.maxAttachmentCountPerMessage
65+
|| !checkAttachmentSize(with: addedFileURLs.last) {
66+
addedFileURLs.removeLast()
67+
}
6468
checkPickerSelectionState()
6569
}
6670
}
@@ -142,6 +146,16 @@ open class MessageComposerViewModel: ObservableObject {
142146
}
143147
}
144148

149+
private var totalAttachmentsCount: Int {
150+
addedAssets.count +
151+
addedCustomAttachments.count +
152+
addedFileURLs.count
153+
}
154+
155+
private var canAddAdditionalAttachments: Bool {
156+
totalAttachmentsCount < chatClient.config.maxAttachmentCountPerMessage
157+
}
158+
145159
public init(
146160
channelController: ChatChannelController,
147161
messageController: ChatMessageController?
@@ -290,7 +304,7 @@ open class MessageComposerViewModel: ObservableObject {
290304
}
291305
}
292306

293-
if !imageRemoved {
307+
if !imageRemoved && canAddAttachment(with: addedAsset.url) {
294308
images.append(addedAsset)
295309
}
296310

@@ -318,7 +332,9 @@ open class MessageComposerViewModel: ObservableObject {
318332
}
319333

320334
public func cameraImageAdded(_ image: AddedAsset) {
321-
addedAssets.append(image)
335+
if canAddAttachment(with: image.url) {
336+
addedAssets.append(image)
337+
}
322338
pickerState = .photos
323339
}
324340

@@ -343,7 +359,7 @@ open class MessageComposerViewModel: ObservableObject {
343359
}
344360
}
345361

346-
if !attachmentRemoved {
362+
if !attachmentRemoved && canAddAdditionalAttachments {
347363
temp.append(attachment)
348364
}
349365

@@ -508,4 +524,25 @@ open class MessageComposerViewModel: ObservableObject {
508524
self?.text = ""
509525
}
510526
}
527+
528+
private func canAddAttachment(with url: URL) -> Bool {
529+
if !canAddAdditionalAttachments {
530+
return false
531+
}
532+
533+
return checkAttachmentSize(with: url)
534+
}
535+
536+
private func checkAttachmentSize(with url: URL?) -> Bool {
537+
guard let url = url else { return true }
538+
539+
_ = url.startAccessingSecurityScopedResource()
540+
541+
do {
542+
let fileSize = try AttachmentFile(url: url).size
543+
return fileSize < chatClient.config.maxAttachmentSize
544+
} catch {
545+
return false
546+
}
547+
}
511548
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/PhotoAssetsUtils.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
//
44

55
import Photos
6+
import StreamChat
67
import SwiftUI
78

89
/// Helper class that loads assets from the photo library.
910
public class PhotoAssetLoader: NSObject, ObservableObject {
11+
12+
@Injected(\.chatClient) private var chatClient
13+
1014
@Published var loadedImages = [String: UIImage]()
1115

1216
/// Loads an image from the provided asset.
@@ -30,6 +34,57 @@ public class PhotoAssetLoader: NSObject, ObservableObject {
3034
}
3135
}
3236

37+
func compressAsset(at url: URL, type: AssetType, completion: @escaping (URL?) -> Void) {
38+
if type == .video {
39+
let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
40+
compressVideo(inputURL: url, outputURL: compressedURL) { exportSession in
41+
guard let session = exportSession else {
42+
return
43+
}
44+
45+
switch session.status {
46+
case .completed:
47+
completion(compressedURL)
48+
default:
49+
completion(nil)
50+
}
51+
}
52+
}
53+
}
54+
55+
func assetExceedsAllowedSize(url: URL?) -> Bool {
56+
_ = url?.startAccessingSecurityScopedResource()
57+
if let assetURL = url,
58+
let file = try? AttachmentFile(url: assetURL),
59+
file.size >= chatClient.config.maxAttachmentSize {
60+
return true
61+
} else {
62+
return false
63+
}
64+
}
65+
66+
private func compressVideo(
67+
inputURL: URL,
68+
outputURL: URL,
69+
handler: @escaping (_ exportSession: AVAssetExportSession?) -> Void
70+
) {
71+
let urlAsset = AVURLAsset(url: inputURL, options: nil)
72+
73+
guard let exportSession = AVAssetExportSession(
74+
asset: urlAsset,
75+
presetName: AVAssetExportPresetMediumQuality
76+
) else {
77+
handler(nil)
78+
return
79+
}
80+
81+
exportSession.outputURL = outputURL
82+
exportSession.outputFileType = .mp4
83+
exportSession.exportAsynchronously {
84+
handler(exportSession)
85+
}
86+
}
87+
3388
/// Clears the cache when there's memory warning.
3489
func didReceiveMemoryWarning() {
3590
loadedImages = [String: UIImage]()

Sources/StreamChatSwiftUI/ChatChannel/Composer/PhotoAttachmentPickerView.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,17 @@ public struct PhotoAttachmentCell: View {
4343

4444
@StateObject var assetLoader: PhotoAssetLoader
4545

46-
@State var assetURL: URL?
46+
@State private var assetURL: URL?
47+
@State private var compressing = false
4748

4849
var asset: PHAsset
4950
var onImageTap: (AddedAsset) -> Void
5051
var imageSelected: (String) -> Bool
5152

53+
private var assetType: AssetType {
54+
asset.mediaType == .video ? .video : .image
55+
}
56+
5257
public var body: some View {
5358
ZStack {
5459
if let image = assetLoader.loadedImages[asset.localIdentifier] {
@@ -75,14 +80,17 @@ public struct PhotoAttachmentCell: View {
7580
image: image,
7681
id: asset.localIdentifier,
7782
url: assetURL,
78-
type: asset.mediaType == .video ? .video : .image,
83+
type: assetType,
7984
extraData: asset.mediaType == .video ? ["duration": asset.durationString] : [:]
8085
)
8186
)
8287
}
8388
}
8489
}
8590
}
91+
.overlay(
92+
compressing ? ProgressView() : nil
93+
)
8694
}
8795
} else {
8896
Color(colors.background1)
@@ -117,12 +125,26 @@ public struct PhotoAttachmentCell: View {
117125
)
118126
.onAppear {
119127
assetLoader.loadImage(from: asset)
128+
129+
if self.assetURL != nil {
130+
return
131+
}
132+
120133
asset.requestContentEditingInput(with: nil) { input, _ in
121134
if asset.mediaType == .image {
122135
self.assetURL = input?.fullSizeImageURL
123136
} else if let url = (input?.audiovisualAsset as? AVURLAsset)?.url {
124137
self.assetURL = url
125138
}
139+
140+
// Check file size.
141+
if let assetURL = assetURL, assetLoader.assetExceedsAllowedSize(url: assetURL) {
142+
compressing = true
143+
assetLoader.compressAsset(at: assetURL, type: assetType) { url in
144+
self.assetURL = url
145+
self.compressing = false
146+
}
147+
}
126148
}
127149
}
128150
}

StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Foundation
66
@testable import StreamChat
77

88
final class CDNClient_Mock: CDNClient {
9-
static var maxAttachmentSize: Int64 { .max }
9+
static var maxAttachmentSize: Int64 = .max
1010

1111
lazy var uploadAttachmentMockFunc = MockFunc.mock(for: uploadAttachment)
1212
func uploadAttachment(

StreamChatSwiftUITests/Infrastructure/TestTools/ChatClient_Mock.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import Foundation
77

88
public extension ChatClient {
99
/// Create a new instance of mock `ChatClient`
10-
static func mock(isLocalStorageEnabled: Bool = false) -> ChatClient {
10+
static func mock(
11+
isLocalStorageEnabled: Bool = false,
12+
customCDNClient: CDNClient? = nil
13+
) -> ChatClient {
1114
var config = ChatClientConfig(apiKey: .init("--== Mock ChatClient ==--"))
15+
config.customCDNClient = customCDNClient
1216
config.isLocalStorageEnabled = isLocalStorageEnabled
1317

1418
return .init(

0 commit comments

Comments
 (0)