Skip to content

Commit 017e2e3

Browse files
authored
Merge pull request #840 from Adamant-im/trello.com/c/7YVA2NTe
[trello.com/c/7YVA2NTe] update cancel file uploading
2 parents c1b8065 + 88bc230 commit 017e2e3

File tree

9 files changed

+136
-46
lines changed

9 files changed

+136
-46
lines changed

Adamant/Modules/Chat/View/Helpers/FileMessageStatus.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ enum FileMessageStatus: Equatable {
1717

1818
var image: UIImage {
1919
switch self {
20-
case .busy: return .asset(named: "status_pending") ?? .init()
20+
case .busy: return .asset(named: "status_failed") ?? .init()
2121
case .success: return .asset(named: "status_success") ?? .init()
2222
case .failed: return .asset(named: "status_failed") ?? .init()
2323
case let .needToDownload(failed):

Adamant/Modules/Chat/View/Managers/ChatAction.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ enum ChatAction {
2121
case react(id: String, emoji: String)
2222
case presentMenu(arg: ChatContextMenuArguments)
2323
case openFile(messageId: String, file: ChatFile)
24-
case cancelUploading(messageId: String, file: ChatFile)
24+
case cancelUploading(messageId: String)
2525
case autoDownloadContentIfNeeded(messageId: String, files: [ChatFile])
2626
case forceDownloadAllFiles(messageId: String, files: [ChatFile])
2727
}

Adamant/Modules/Chat/View/Managers/ChatDataSourceManager.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ extension ChatDataSourceManager {
214214
viewModel.copyTextInPartAction(text)
215215
case let .openFile(messageId, file):
216216
viewModel.openFile(messageId: messageId, file: file)
217-
case let .cancelUploading(messageId, file):
218-
viewModel.cancelFileUploading(messageId: messageId, file: file)
217+
case let .cancelUploading(messageId):
218+
viewModel.cancelFileUploading(messageId: messageId)
219219
case let .autoDownloadContentIfNeeded(messageId, files):
220220
viewModel.autoDownloadContentIfNeeded(
221221
messageId: messageId,

Adamant/Modules/Chat/View/Subviews/ChatMedia/Container/ChatMediaContainerView.swift

+54-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import Combine
1010
import CommonKit
1111
import UIKit
12+
import SwiftUI
1213

1314
final class ChatMediaContainerView: UIView {
1415
private let spacingView: UIView = {
@@ -88,9 +89,28 @@ final class ChatMediaContainerView: UIView {
8889
button.addTarget(self, action: #selector(onStatusButtonTap), for: .touchUpInside)
8990
return button
9091
}()
92+
93+
private lazy var progressRingHostingView: UIView = {
94+
let progressBar = CircularProgressView { [weak self] in
95+
guard let self else { return .init(progress: .zero, hidden: true) }
96+
return self.statusProgressState
97+
}
98+
let hosting = UIHostingController(rootView: progressBar)
99+
hosting.view.backgroundColor = .clear
100+
hosting.view.isUserInteractionEnabled = false
101+
return hosting.view
102+
}()
91103

92104
private lazy var contentView = ChatMediaContentView()
93105
private lazy var chatMenuManager = ChatMenuManager(delegate: self)
106+
107+
private var statusProgressState = CircularProgressState(
108+
lineWidth: 2.0,
109+
backgroundColor: .clear,
110+
progressColor: .adamant.primary,
111+
progress: .zero,
112+
hidden: true
113+
)
94114

95115
// MARK: Dependencies
96116

@@ -123,6 +143,11 @@ final class ChatMediaContainerView: UIView {
123143
}
124144

125145
@objc func onStatusButtonTap() {
146+
if model.status == .busy {
147+
actionHandler(.cancelUploading(messageId: model.id))
148+
return
149+
}
150+
126151
if model.status == .failed,
127152
let file = model.content.fileModel.files.first
128153
{
@@ -153,9 +178,15 @@ extension ChatMediaContainerView {
153178
$0.verticalEdges.equalToSuperview()
154179
$0.horizontalEdges.equalToSuperview().inset(4)
155180
}
156-
181+
157182
reactionsStack.snp.makeConstraints { $0.width.equalTo(reactionsWidth) }
158183
chatMenuManager.setup(for: contentView)
184+
185+
reactionsStack.insertSubview(progressRingHostingView, aboveSubview: statusButton)
186+
progressRingHostingView.snp.makeConstraints { make in
187+
make.center.equalTo(statusButton)
188+
make.size.equalTo(31)
189+
}
159190
}
160191

161192
func update() {
@@ -166,13 +197,35 @@ extension ChatMediaContainerView {
166197
updateOwnReaction()
167198
updateOpponentReaction()
168199
updateStatus(model.status)
200+
updateProgressRing()
169201
}
170202

171203
func updateStatus(_ status: FileMessageStatus) {
172204
statusButton.setImage(status.image, for: .normal)
173205
statusButton.tintColor = status.imageTintColor
174206
statusButton.isHidden = status == .success
175207
}
208+
209+
func averageUploadProgress() -> Double {
210+
let files = model.content.fileModel.files
211+
212+
let progresses = files
213+
.compactMap { $0.progress }
214+
215+
guard !progresses.isEmpty else {
216+
return 0.0
217+
}
218+
219+
let totalProgress = progresses.reduce(0, +)
220+
return Double(totalProgress) / Double(progresses.count)
221+
}
222+
223+
func updateProgressRing() {
224+
let averageProgress = averageUploadProgress()
225+
226+
statusProgressState.progress = averageProgress / 100
227+
statusProgressState.hidden = averageProgress == 0 || averageProgress == 100
228+
}
176229

177230
func updateLayout() {
178231
var viewsList = [spacingView, reactionsStack, contentView]

Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/Views/ChatFileContainerView/FileListContainerView.swift

+5-11
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,12 @@ extension FileListContainerView {
8585
txStatus: model.txStatus
8686
)
8787
view?.buttonActionHandler = { [weak self, file, model] in
88-
if file.isBusy, file.isUploading {
89-
self?.actionHandler(
90-
.cancelUploading(messageId: model.messageId, file: file)
88+
self?.actionHandler(
89+
.openFile(
90+
messageId: model.messageId,
91+
file: file
9192
)
92-
} else {
93-
self?.actionHandler(
94-
.openFile(
95-
messageId: model.messageId,
96-
file: file
97-
)
98-
)
99-
}
93+
)
10094
}
10195
}
10296
}

Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/Views/MediaContainerView/MediaContainerView.swift

+6-13
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,12 @@ extension MediaContainerView {
133133
txStatus: model.txStatus
134134
)
135135
mediaView.buttonActionHandler = { [weak self, file, model] in
136-
let action: ChatAction =
137-
if file.isBusy, file.isUploading {
138-
.cancelUploading(
139-
messageId: model.messageId,
140-
file: file
141-
)
142-
} else {
143-
.openFile(
144-
messageId: model.messageId,
145-
file: file
146-
)
147-
}
148-
self?.actionHandler(action)
136+
self?.actionHandler(
137+
.openFile(
138+
messageId: model.messageId,
139+
file: file
140+
)
141+
)
149142
}
150143

151144
if let resolution = file.file.resolution,

Adamant/Modules/Chat/ViewModel/ChatFileService.swift

+64-14
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ final class ChatFileService: ChatFileProtocol, Sendable {
4949
private var fileDownloadAttemptsCount: [String: Int] = [:]
5050
private var uploadingFilesDictionary: [String: FileMessage] = [:]
5151
private var previewDownloadsAttemps: [String: Int] = [:]
52-
private var uploadTasks: [String: Task<UploadFileResult, Error>] = [:]
5352
private let synchronizer = AsyncStreamSender<@MainActor () -> Void>()
5453
private let _updateFileFields = ObservableSender<FileUpdateProperties>()
54+
55+
// [messageId: [fileID: Task]] we can cancel all tasks for one message by messageId, uploading is managing by fileID
56+
private var uploadTasks: [String: [String: Task<UploadFileResult, Error>]] = [:]
5557

5658
private var subscriptions = Set<AnyCancellable>()
5759
private let maxDownloadAttemptsCount = 3
@@ -204,17 +206,24 @@ final class ChatFileService: ChatFileProtocol, Sendable {
204206
return decodedData
205207
}
206208

207-
func cancelUpload(messageId: String, fileId: String) async {
208-
if let task = uploadTasks[fileId] {
209+
func cancelUpload(messageId: String) async {
210+
guard let tasks = uploadTasks[messageId] else { return }
211+
212+
var fileIdsToRemove: [String] = []
213+
214+
for (fileId, task) in tasks {
209215
task.cancel()
210-
uploadTasks[fileId] = nil
211216
uploadingFiles.removeAll { $0 == fileId }
212-
} else {
213-
await removeFromRichFile(
214-
oldId: fileId,
215-
txId: messageId
216-
)
217+
fileIdsToRemove.append(fileId)
217218
}
219+
220+
await removeFilesFromRichFile(
221+
oldIds: fileIdsToRemove,
222+
txId: messageId
223+
)
224+
225+
uploadTasks[messageId] = nil
226+
uploadingFilesDictionary[messageId] = nil
218227
}
219228

220229
func isDownloadPreviewLimitReached(for fileId: String) -> Bool {
@@ -973,10 +982,13 @@ extension ChatFileService {
973982
saveEncrypted: saveEncrypted
974983
)
975984

976-
uploadTasks[file.url.absoluteString] = uploadTask
985+
if uploadTasks[txId] == nil {
986+
uploadTasks[txId] = [:]
987+
}
988+
uploadTasks[txId]?[file.url.absoluteString] = uploadTask
977989

978990
defer {
979-
uploadTasks[file.url.absoluteString] = nil
991+
uploadTasks[txId]?[file.url.absoluteString] = nil
980992
}
981993

982994
do {
@@ -1004,6 +1016,12 @@ extension ChatFileService {
10041016
txId: txId
10051017
)
10061018
} catch is CancellationError {
1019+
guard let (fileMessage, _) = uploadingFilesDictionary[richMessageId: txId],
1020+
fileMessage.files.contains(where: { $0.file.url.absoluteString == file.url.absoluteString })
1021+
else {
1022+
return
1023+
}
1024+
10071025
await removeFromRichFile(
10081026
oldId: file.url.absoluteString,
10091027
txId: txId
@@ -1134,9 +1152,41 @@ extension ChatFileService {
11341152
uploadingFilesDictionary[txId] = fileMessage
11351153

11361154
if !updatedFiles.isEmpty {
1137-
// skip double update which causes bugs
1138-
// first update: here
1139-
// second update: if fileMessages is empty in sendFile method
1155+
// skip double update which causes bugs
1156+
// first update: here
1157+
// second update: if fileMessages is empty in sendFile method
1158+
try? await chatsProvider.updateTxMessageContent(
1159+
txId: txId,
1160+
richMessage: richMessage
1161+
)
1162+
}
1163+
}
1164+
1165+
fileprivate func removeFilesFromRichFile(
1166+
oldIds: [String],
1167+
txId: String
1168+
) async {
1169+
oldIds.forEach { oldId in
1170+
uploadingFiles.removeAll { $0 == oldId }
1171+
}
1172+
1173+
guard var (fileMessage, richMessage) = uploadingFilesDictionary[richMessageId: txId]
1174+
else { return }
1175+
1176+
richMessage.files = richMessage.files.filter { file in
1177+
!oldIds.contains(file.id)
1178+
}
1179+
fileMessage.adamantMessage = .richMessage(payload: richMessage)
1180+
1181+
let updatedFiles = fileMessage.files.filter { file in
1182+
let url = file.file.url
1183+
return !oldIds.contains(url.absoluteString)
1184+
}
1185+
1186+
fileMessage.files = updatedFiles
1187+
uploadingFilesDictionary[txId] = fileMessage
1188+
1189+
if !updatedFiles.isEmpty {
11401190
try? await chatsProvider.updateTxMessageContent(
11411191
txId: txId,
11421192
richMessage: richMessage

Adamant/Modules/Chat/ViewModel/ChatViewModel.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -741,9 +741,9 @@ final class ChatViewModel: NSObject {
741741
updateMessages(resetLoadingProperty: false)
742742
}
743743

744-
func cancelFileUploading(messageId: String, file: ChatFile) {
744+
func cancelFileUploading(messageId: String) {
745745
Task {
746-
await chatFileService.cancelUpload(messageId: messageId, fileId: file.file.id)
746+
await chatFileService.cancelUpload(messageId: messageId)
747747
}
748748
}
749749

Adamant/ServiceProtocols/ChatFileProtocol.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protocol ChatFileProtocol: Sendable {
7171

7272
func isDownloadPreviewLimitReached(for fileId: String) -> Bool
7373

74-
func cancelUpload(messageId: String, fileId: String) async
74+
func cancelUpload(messageId: String) async
7575

7676
func isPreviewAutoDownloadAllowedByPolicy(
7777
hasPartnerName: Bool,

0 commit comments

Comments
 (0)