Cancel uploads

This commit is contained in:
Justin Mazzocchi 2021-01-03 15:57:40 -08:00
parent ebe624605b
commit a4c0589a07
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
5 changed files with 58 additions and 35 deletions

View file

@ -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 {

View file

@ -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()
} }
} }

View file

@ -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() {

View file

@ -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)

View file

@ -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),