feat: add post publish validate state binding

This commit is contained in:
CMK 2021-03-23 19:33:12 +08:00
parent b8e062c92e
commit 3eb2b916a7
3 changed files with 76 additions and 6 deletions

View File

@ -25,7 +25,7 @@ extension ComposeStatusItem: Hashable { }
extension ComposeStatusItem { extension ComposeStatusItem {
final class ComposeStatusAttribute: Equatable, Hashable { final class ComposeStatusAttribute: Equatable, Hashable {
private let id = UUID() private let id = UUID()
let avatarURL = CurrentValueSubject<URL?, Never>(nil) let avatarURL = CurrentValueSubject<URL?, Never>(nil)
let displayName = CurrentValueSubject<String?, Never>(nil) let displayName = CurrentValueSubject<String?, Never>(nil)
let username = CurrentValueSubject<String?, Never>(nil) let username = CurrentValueSubject<String?, Never>(nil)
@ -44,12 +44,32 @@ extension ComposeStatusItem {
} }
} }
protocol ComposeStatusItemDelegate: class {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollAttribute, pollOptionDidChange: String?)
}
extension ComposeStatusItem { extension ComposeStatusItem {
final class ComposePollAttribute: Equatable, Hashable { final class ComposePollAttribute: Equatable, Hashable {
private let id = UUID() private let id = UUID()
var disposeBag = Set<AnyCancellable>()
weak var delegate: ComposeStatusItemDelegate?
let option = CurrentValueSubject<String, Never>("") let option = CurrentValueSubject<String, Never>("")
init() {
option
.sink { [weak self] option in
guard let self = self else { return }
self.delegate?.composePollAttribute(self, pollOptionDidChange: option)
}
.store(in: &disposeBag)
}
deinit {
disposeBag.removeAll()
}
static func == (lhs: ComposePollAttribute, rhs: ComposePollAttribute) -> Bool { static func == (lhs: ComposePollAttribute, rhs: ComposePollAttribute) -> Bool {
return lhs.id == rhs.id && return lhs.id == rhs.id &&
lhs.option.value == rhs.option.value lhs.option.value == rhs.option.value

View File

@ -629,7 +629,6 @@ extension ComposeViewController: PHPickerViewControllerDelegate {
pickerResult: result, pickerResult: result,
initalAuthenticationBox: viewModel.activeAuthenticationBox.value initalAuthenticationBox: viewModel.activeAuthenticationBox.value
) )
service.delegate = viewModel
return service return service
} }
viewModel.attachmentServices.value = viewModel.attachmentServices.value + attachmentServices viewModel.attachmentServices.value = viewModel.attachmentServices.value + attachmentServices
@ -649,7 +648,6 @@ extension ComposeViewController: UIImagePickerControllerDelegate & UINavigationC
image: image, image: image,
initalAuthenticationBox: viewModel.activeAuthenticationBox.value initalAuthenticationBox: viewModel.activeAuthenticationBox.value
) )
attachmentService.delegate = viewModel
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService] viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
} }
@ -673,7 +671,6 @@ extension ComposeViewController: UIDocumentPickerDelegate {
imageData: imageData, imageData: imageData,
initalAuthenticationBox: viewModel.activeAuthenticationBox.value initalAuthenticationBox: viewModel.activeAuthenticationBox.value
) )
attachmentService.delegate = viewModel
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService] viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
} catch { } catch {
os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)

View File

@ -104,19 +104,48 @@ final class ComposeViewModel {
.map { services in .map { services in
services.allSatisfy { $0.uploadStateMachineSubject.value is MastodonAttachmentService.UploadState.Finish } services.allSatisfy { $0.uploadStateMachineSubject.value is MastodonAttachmentService.UploadState.Finish }
} }
Publishers.CombineLatest4( let isPollAttributeAllValid = pollAttributes
.map { pollAttributes in
pollAttributes.allSatisfy { attribute -> Bool in
!attribute.option.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}
let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4(
isComposeContentEmpty.eraseToAnyPublisher(), isComposeContentEmpty.eraseToAnyPublisher(),
isComposeContentValid.eraseToAnyPublisher(), isComposeContentValid.eraseToAnyPublisher(),
isMediaEmpty.eraseToAnyPublisher(), isMediaEmpty.eraseToAnyPublisher(),
isMediaUploadAllSuccess.eraseToAnyPublisher() isMediaUploadAllSuccess.eraseToAnyPublisher()
) )
.map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess in .map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess -> Bool in
if isMediaEmpty { if isMediaEmpty {
return isComposeContentValid && !isComposeContentEmpty return isComposeContentValid && !isComposeContentEmpty
} else { } else {
return isComposeContentValid && isMediaUploadAllSuccess return isComposeContentValid && isMediaUploadAllSuccess
} }
} }
.eraseToAnyPublisher()
let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest4(
isComposeContentEmpty.eraseToAnyPublisher(),
isComposeContentValid.eraseToAnyPublisher(),
isPollComposing.eraseToAnyPublisher(),
isPollAttributeAllValid.eraseToAnyPublisher()
)
.map { isComposeContentEmpty, isComposeContentValid, isPollComposing, isPollAttributeAllValid -> Bool in
if isPollComposing {
return isComposeContentValid && !isComposeContentEmpty && isPollAttributeAllValid
} else {
return isComposeContentValid && !isComposeContentEmpty
}
}
.eraseToAnyPublisher()
Publishers.CombineLatest(
isPublishBarButtonItemEnabledPrecondition1,
isPublishBarButtonItemEnabledPrecondition2
)
.map { $0 && $1 }
.assign(to: \.value, on: isPublishBarButtonItemEnabled) .assign(to: \.value, on: isPublishBarButtonItemEnabled)
.store(in: &disposeBag) .store(in: &disposeBag)
@ -201,6 +230,22 @@ final class ComposeViewModel {
} }
.store(in: &disposeBag) .store(in: &disposeBag)
// bind delegate
attachmentServices
.sink { [weak self] attachmentServices in
guard let self = self else { return }
attachmentServices.forEach { $0.delegate = self }
}
.store(in: &disposeBag)
pollAttributes
.sink { [weak self] pollAttributes in
guard let self = self else { return }
pollAttributes.forEach { $0.delegate = self }
}
.store(in: &disposeBag)
// bind compose toolbar UI state
Publishers.CombineLatest( Publishers.CombineLatest(
isPollComposing.eraseToAnyPublisher(), isPollComposing.eraseToAnyPublisher(),
attachmentServices.eraseToAnyPublisher() attachmentServices.eraseToAnyPublisher()
@ -235,3 +280,11 @@ extension ComposeViewModel: MastodonAttachmentServiceDelegate {
attachmentServices.value = attachmentServices.value attachmentServices.value = attachmentServices.value
} }
} }
// MARK: - ComposeStatusAttributeDelegate
extension ComposeViewModel: ComposeStatusItemDelegate {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollAttribute, pollOptionDidChange: String?) {
// trigger update
pollAttributes.value = pollAttributes.value
}
}