Cancel uploads
This commit is contained in:
parent
ebe624605b
commit
a4c0589a07
|
@ -7,6 +7,8 @@ extension URLSession {
|
||||||
func dataTaskPublisher(for request: URLRequest, progress: Progress?)
|
func dataTaskPublisher(for request: URLRequest, progress: Progress?)
|
||||||
-> AnyPublisher<DataTaskPublisher.Output, Error> {
|
-> AnyPublisher<DataTaskPublisher.Output, Error> {
|
||||||
if let progress = progress {
|
if let progress = progress {
|
||||||
|
var dataTaskReference: URLSessionDataTask?
|
||||||
|
|
||||||
return Deferred {
|
return Deferred {
|
||||||
Future<DataTaskPublisher.Output, Error> { promise in
|
Future<DataTaskPublisher.Output, Error> { promise in
|
||||||
let dataTask = self.dataTask(with: request) { data, response, error in
|
let dataTask = self.dataTask(with: request) { data, response, error in
|
||||||
|
@ -19,7 +21,11 @@ extension URLSession {
|
||||||
|
|
||||||
progress.addChild(dataTask.progress, withPendingUnitCount: 1)
|
progress.addChild(dataTask.progress, withPendingUnitCount: 1)
|
||||||
dataTask.resume()
|
dataTask.resume()
|
||||||
|
dataTaskReference = dataTask
|
||||||
}
|
}
|
||||||
|
.handleEvents(receiveCancel: {
|
||||||
|
dataTaskReference?.cancel()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,7 +17,7 @@ public final class CompositionViewModel: ObservableObject, Identifiable {
|
||||||
@Published public private(set) var canAddAttachment = true
|
@Published public private(set) var canAddAttachment = true
|
||||||
@Published public private(set) var canAddNonImageAttachment = true
|
@Published public private(set) var canAddNonImageAttachment = true
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var attachmentUploadCancellable: AnyCancellable?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
$text.map { !$0.isEmpty }
|
$text.map { !$0.isEmpty }
|
||||||
|
@ -56,11 +56,15 @@ public extension CompositionViewModel {
|
||||||
func remove(attachmentViewModel: CompositionAttachmentViewModel) {
|
func remove(attachmentViewModel: CompositionAttachmentViewModel) {
|
||||||
attachmentViewModels.removeAll { $0 === attachmentViewModel }
|
attachmentViewModels.removeAll { $0 === attachmentViewModel }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cancelUpload() {
|
||||||
|
attachmentUploadCancellable?.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CompositionViewModel {
|
extension CompositionViewModel {
|
||||||
func attach(itemProvider: NSItemProvider, service: IdentityService) -> AnyPublisher<Never, Error> {
|
func attach(itemProvider: NSItemProvider, parentViewModel: NewStatusViewModel) {
|
||||||
MediaProcessingService.dataAndMimeType(itemProvider: itemProvider)
|
attachmentUploadCancellable = MediaProcessingService.dataAndMimeType(itemProvider: itemProvider)
|
||||||
.flatMap { [weak self] data, mimeType -> AnyPublisher<Attachment, Error> in
|
.flatMap { [weak self] data, mimeType -> AnyPublisher<Attachment, Error> in
|
||||||
guard let self = self else { return Empty().eraseToAnyPublisher() }
|
guard let self = self else { return Empty().eraseToAnyPublisher() }
|
||||||
|
|
||||||
|
@ -70,16 +74,19 @@ extension CompositionViewModel {
|
||||||
self.attachmentUpload = AttachmentUpload(progress: progress, data: data, mimeType: mimeType)
|
self.attachmentUpload = AttachmentUpload(progress: progress, data: data, mimeType: mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return service.uploadAttachment(data: data, mimeType: mimeType, progress: progress)
|
return parentViewModel.identification.service.uploadAttachment(
|
||||||
|
data: data,
|
||||||
|
mimeType: mimeType,
|
||||||
|
progress: progress)
|
||||||
}
|
}
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.handleEvents(
|
.assignErrorsToAlertItem(to: \.alertItem, on: parentViewModel)
|
||||||
receiveOutput: { [weak self] in
|
.handleEvents(receiveCancel: { [weak self] in self?.attachmentUpload = nil })
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.attachmentUpload = nil
|
||||||
|
} receiveValue: { [weak self] in
|
||||||
self?.attachmentViewModels.append(CompositionAttachmentViewModel(attachment: $0))
|
self?.attachmentViewModels.append(CompositionAttachmentViewModel(attachment: $0))
|
||||||
},
|
}
|
||||||
receiveCompletion: { [weak self] _ in self?.attachmentUpload = nil })
|
|
||||||
.ignoreOutput()
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,11 +98,7 @@ public extension NewStatusViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func attach(itemProvider: NSItemProvider, to compositionViewModel: CompositionViewModel) {
|
func attach(itemProvider: NSItemProvider, to compositionViewModel: CompositionViewModel) {
|
||||||
compositionViewModel.attach(itemProvider: itemProvider, service: identification.service)
|
compositionViewModel.attach(itemProvider: itemProvider, parentViewModel: self)
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
|
||||||
.sink { _ in }
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func post() {
|
func post() {
|
||||||
|
|
|
@ -6,23 +6,16 @@ import ViewModels
|
||||||
|
|
||||||
final class AttachmentUploadView: UIView {
|
final class AttachmentUploadView: UIView {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
let cancelButton = UIButton(type: .system)
|
||||||
let progressView = UIProgressView(progressViewStyle: .default)
|
let progressView = UIProgressView(progressViewStyle: .default)
|
||||||
|
|
||||||
|
private let viewModel: CompositionViewModel
|
||||||
private var progressCancellable: AnyCancellable?
|
private var progressCancellable: AnyCancellable?
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
var attachmentUpload: AttachmentUpload? {
|
init(viewModel: CompositionViewModel) {
|
||||||
didSet {
|
self.viewModel = viewModel
|
||||||
if let attachmentUpload = attachmentUpload {
|
|
||||||
progressCancellable = attachmentUpload.progress.publisher(for: \.fractionCompleted)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] in self?.progressView.progress = Float($0) }
|
|
||||||
isHidden = false
|
|
||||||
} else {
|
|
||||||
isHidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
addSubview(label)
|
addSubview(label)
|
||||||
|
@ -34,18 +27,42 @@ final class AttachmentUploadView: UIView {
|
||||||
label.textColor = .secondaryLabel
|
label.textColor = .secondaryLabel
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
|
|
||||||
|
addSubview(cancelButton)
|
||||||
|
cancelButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
cancelButton.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||||
|
cancelButton.titleLabel?.font = .preferredFont(forTextStyle: .callout)
|
||||||
|
cancelButton.setTitle(NSLocalizedString("cancel", comment: ""), for: .normal)
|
||||||
|
cancelButton.addAction(UIAction { _ in viewModel.cancelUpload() }, for: .touchUpInside)
|
||||||
|
|
||||||
addSubview(progressView)
|
addSubview(progressView)
|
||||||
progressView.translatesAutoresizingMaskIntoConstraints = false
|
progressView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
||||||
label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
||||||
label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
label.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: .defaultSpacing),
|
||||||
|
cancelButton.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
||||||
|
cancelButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
||||||
|
cancelButton.bottomAnchor.constraint(equalTo: label.bottomAnchor),
|
||||||
progressView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: .defaultSpacing),
|
progressView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: .defaultSpacing),
|
||||||
progressView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
progressView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
||||||
progressView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
progressView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
||||||
progressView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
|
progressView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
viewModel.$attachmentUpload.sink { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
if let attachmentUpload = $0 {
|
||||||
|
self.progressCancellable = attachmentUpload.progress.publisher(for: \.fractionCompleted)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { self.progressView.progress = Float($0) }
|
||||||
|
self.isHidden = false
|
||||||
|
} else {
|
||||||
|
self.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
|
|
|
@ -10,8 +10,8 @@ final class CompositionView: UIView {
|
||||||
let spoilerTextField = UITextField()
|
let spoilerTextField = UITextField()
|
||||||
let textView = UITextView()
|
let textView = UITextView()
|
||||||
let textViewPlaceholder = UILabel()
|
let textViewPlaceholder = UILabel()
|
||||||
let attachmentUploadView = AttachmentUploadView()
|
|
||||||
let attachmentsCollectionView: UICollectionView
|
let attachmentsCollectionView: UICollectionView
|
||||||
|
let attachmentUploadView: AttachmentUploadView
|
||||||
|
|
||||||
private let viewModel: CompositionViewModel
|
private let viewModel: CompositionViewModel
|
||||||
private let parentViewModel: NewStatusViewModel
|
private let parentViewModel: NewStatusViewModel
|
||||||
|
@ -50,6 +50,7 @@ final class CompositionView: UIView {
|
||||||
let attachmentsLayout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)
|
let attachmentsLayout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)
|
||||||
|
|
||||||
attachmentsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: attachmentsLayout)
|
attachmentsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: attachmentsLayout)
|
||||||
|
attachmentUploadView = AttachmentUploadView(viewModel: viewModel)
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
@ -168,10 +169,6 @@ private extension CompositionView {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
viewModel.$attachmentUpload
|
|
||||||
.sink { [weak self] in self?.attachmentUploadView.attachmentUpload = $0 }
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
let guide = UIDevice.current.userInterfaceIdiom == .pad ? readableContentGuide : layoutMarginsGuide
|
let guide = UIDevice.current.userInterfaceIdiom == .pad ? readableContentGuide : layoutMarginsGuide
|
||||||
let constraints = [
|
let constraints = [
|
||||||
avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
||||||
|
|
Loading…
Reference in New Issue