2021-03-11 08:41:27 +01:00
|
|
|
//
|
|
|
|
// ComposeViewModel.swift
|
|
|
|
// Mastodon
|
|
|
|
//
|
|
|
|
// Created by MainasuK Cirno on 2021-3-11.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
import Combine
|
|
|
|
import CoreData
|
|
|
|
import CoreDataStack
|
2021-03-18 10:33:07 +01:00
|
|
|
import GameplayKit
|
2021-03-11 08:41:27 +01:00
|
|
|
|
|
|
|
final class ComposeViewModel {
|
|
|
|
|
2021-03-12 07:18:07 +01:00
|
|
|
var disposeBag = Set<AnyCancellable>()
|
|
|
|
|
2021-03-11 08:41:27 +01:00
|
|
|
// input
|
|
|
|
let context: AppContext
|
2021-03-12 07:18:07 +01:00
|
|
|
let composeKind: ComposeStatusSection.ComposeKind
|
2021-03-16 04:23:19 +01:00
|
|
|
let composeStatusAttribute = ComposeStatusItem.ComposeStatusAttribute()
|
2021-03-12 07:18:07 +01:00
|
|
|
let activeAuthentication: CurrentValueSubject<MastodonAuthentication?, Never>
|
2021-03-18 10:33:07 +01:00
|
|
|
let activeAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never>
|
2021-03-11 08:41:27 +01:00
|
|
|
|
|
|
|
// output
|
|
|
|
var diffableDataSource: UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>!
|
2021-03-18 10:33:07 +01:00
|
|
|
private(set) lazy var publishStateMachine: GKStateMachine = {
|
|
|
|
// exclude timeline middle fetcher state
|
|
|
|
let stateMachine = GKStateMachine(states: [
|
|
|
|
PublishState.Initial(viewModel: self),
|
|
|
|
PublishState.Publishing(viewModel: self),
|
|
|
|
PublishState.Fail(viewModel: self),
|
|
|
|
PublishState.Finish(viewModel: self),
|
|
|
|
])
|
|
|
|
stateMachine.enter(PublishState.Initial.self)
|
|
|
|
return stateMachine
|
|
|
|
}()
|
2021-03-12 07:18:07 +01:00
|
|
|
|
|
|
|
// UI & UX
|
2021-03-11 08:41:27 +01:00
|
|
|
let title: CurrentValueSubject<String, Never>
|
|
|
|
let shouldDismiss = CurrentValueSubject<Bool, Never>(true)
|
2021-03-18 12:42:26 +01:00
|
|
|
let isPublishBarButtonItemEnabled = CurrentValueSubject<Bool, Never>(false)
|
2021-03-11 08:41:27 +01:00
|
|
|
|
2021-03-16 04:23:34 +01:00
|
|
|
// custom emojis
|
|
|
|
let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
|
|
|
|
|
2021-03-17 11:09:38 +01:00
|
|
|
// attachment
|
|
|
|
let attachmentServices = CurrentValueSubject<[MastodonAttachmentService], Never>([])
|
|
|
|
|
2021-03-11 08:41:27 +01:00
|
|
|
init(
|
|
|
|
context: AppContext,
|
2021-03-12 07:18:07 +01:00
|
|
|
composeKind: ComposeStatusSection.ComposeKind
|
2021-03-11 08:41:27 +01:00
|
|
|
) {
|
|
|
|
self.context = context
|
|
|
|
self.composeKind = composeKind
|
|
|
|
switch composeKind {
|
2021-03-15 06:42:46 +01:00
|
|
|
case .post: self.title = CurrentValueSubject(L10n.Scene.Compose.Title.newPost)
|
|
|
|
case .reply: self.title = CurrentValueSubject(L10n.Scene.Compose.Title.newReply)
|
2021-03-11 08:41:27 +01:00
|
|
|
}
|
2021-03-12 07:18:07 +01:00
|
|
|
self.activeAuthentication = CurrentValueSubject(context.authenticationService.activeMastodonAuthentication.value)
|
2021-03-18 10:33:07 +01:00
|
|
|
self.activeAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
|
2021-03-11 08:41:27 +01:00
|
|
|
// end init
|
2021-03-12 07:18:07 +01:00
|
|
|
|
|
|
|
// bind active authentication
|
|
|
|
context.authenticationService.activeMastodonAuthentication
|
|
|
|
.assign(to: \.value, on: activeAuthentication)
|
|
|
|
.store(in: &disposeBag)
|
2021-03-18 10:33:07 +01:00
|
|
|
context.authenticationService.activeMastodonAuthenticationBox
|
|
|
|
.assign(to: \.value, on: activeAuthenticationBox)
|
|
|
|
.store(in: &disposeBag)
|
2021-03-12 07:18:07 +01:00
|
|
|
|
2021-03-16 04:23:34 +01:00
|
|
|
// bind avatar and names
|
2021-03-12 07:18:07 +01:00
|
|
|
activeAuthentication
|
|
|
|
.sink { [weak self] mastodonAuthentication in
|
|
|
|
guard let self = self else { return }
|
|
|
|
let mastodonUser = mastodonAuthentication?.user
|
|
|
|
let username = mastodonUser?.username ?? " "
|
2021-03-11 08:41:27 +01:00
|
|
|
|
2021-03-16 04:23:34 +01:00
|
|
|
self.composeStatusAttribute.avatarURL.value = mastodonUser?.avatarImageURL()
|
|
|
|
self.composeStatusAttribute.displayName.value = {
|
2021-03-12 07:18:07 +01:00
|
|
|
guard let displayName = mastodonUser?.displayName, !displayName.isEmpty else {
|
|
|
|
return username
|
|
|
|
}
|
|
|
|
return displayName
|
|
|
|
}()
|
2021-03-16 04:23:34 +01:00
|
|
|
self.composeStatusAttribute.username.value = username
|
2021-03-12 07:18:07 +01:00
|
|
|
}
|
|
|
|
.store(in: &disposeBag)
|
2021-03-12 08:57:58 +01:00
|
|
|
|
2021-03-16 04:23:34 +01:00
|
|
|
// bind compose bar button item UI state
|
2021-03-18 12:42:26 +01:00
|
|
|
let isComposeContentEmpty = composeStatusAttribute.composeContent
|
|
|
|
.map { ($0 ?? "").isEmpty }
|
|
|
|
let isComposeContentValid = Just(true).eraseToAnyPublisher()
|
|
|
|
let isMediaEmpty = attachmentServices
|
|
|
|
.map { $0.isEmpty }
|
|
|
|
let isMediaUploadAllSuccess = attachmentServices
|
|
|
|
.map { services in
|
|
|
|
services.allSatisfy { $0.uploadStateMachineSubject.value is MastodonAttachmentService.UploadState.Finish }
|
2021-03-12 08:57:58 +01:00
|
|
|
}
|
2021-03-18 12:42:26 +01:00
|
|
|
Publishers.CombineLatest4(
|
|
|
|
isComposeContentEmpty.eraseToAnyPublisher(),
|
|
|
|
isComposeContentValid.eraseToAnyPublisher(),
|
|
|
|
isMediaEmpty.eraseToAnyPublisher(),
|
|
|
|
isMediaUploadAllSuccess.eraseToAnyPublisher()
|
|
|
|
)
|
|
|
|
.map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess in
|
|
|
|
if isMediaEmpty {
|
|
|
|
return isComposeContentValid && !isComposeContentEmpty
|
|
|
|
} else {
|
|
|
|
return isComposeContentValid && isMediaUploadAllSuccess
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.assign(to: \.value, on: isPublishBarButtonItemEnabled)
|
|
|
|
.store(in: &disposeBag)
|
2021-03-12 08:57:58 +01:00
|
|
|
|
2021-03-16 04:23:34 +01:00
|
|
|
// bind modal dismiss state
|
|
|
|
composeStatusAttribute.composeContent
|
2021-03-12 08:57:58 +01:00
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.map { content in
|
|
|
|
let content = content ?? ""
|
|
|
|
return content.isEmpty
|
|
|
|
}
|
|
|
|
.assign(to: \.value, on: shouldDismiss)
|
|
|
|
.store(in: &disposeBag)
|
2021-03-16 04:23:34 +01:00
|
|
|
|
|
|
|
// bind custom emojis
|
|
|
|
context.authenticationService.activeMastodonAuthenticationBox
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] activeMastodonAuthenticationBox in
|
|
|
|
guard let self = self else { return }
|
|
|
|
guard let activeMastodonAuthenticationBox = activeMastodonAuthenticationBox else { return }
|
|
|
|
let domain = activeMastodonAuthenticationBox.domain
|
|
|
|
|
|
|
|
// trigger dequeue to preload emojis
|
2021-03-16 07:19:12 +01:00
|
|
|
self.customEmojiViewModel.value = self.context.emojiService.dequeueCustomEmojiViewModel(for: domain)
|
2021-03-16 04:23:34 +01:00
|
|
|
}
|
|
|
|
.store(in: &disposeBag)
|
2021-03-17 11:09:38 +01:00
|
|
|
|
|
|
|
// bind snapshot
|
|
|
|
attachmentServices
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] attachmentServices in
|
|
|
|
guard let self = self else { return }
|
|
|
|
guard let diffableDataSource = self.diffableDataSource else { return }
|
|
|
|
var snapshot = diffableDataSource.snapshot()
|
|
|
|
|
|
|
|
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .attachment))
|
|
|
|
var items: [ComposeStatusItem] = []
|
|
|
|
for attachmentService in attachmentServices {
|
|
|
|
let item = ComposeStatusItem.attachment(attachmentService: attachmentService)
|
|
|
|
items.append(item)
|
|
|
|
}
|
|
|
|
snapshot.appendItems(items, toSection: .attachment)
|
|
|
|
|
|
|
|
diffableDataSource.apply(snapshot)
|
|
|
|
}
|
|
|
|
.store(in: &disposeBag)
|
2021-03-11 08:41:27 +01:00
|
|
|
}
|
2021-03-12 07:18:07 +01:00
|
|
|
|
2021-03-11 08:41:27 +01:00
|
|
|
}
|
2021-03-18 12:42:26 +01:00
|
|
|
|
|
|
|
// MARK: - MastodonAttachmentServiceDelegate
|
|
|
|
extension ComposeViewModel: MastodonAttachmentServiceDelegate {
|
|
|
|
func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) {
|
|
|
|
// trigger new output event
|
|
|
|
attachmentServices.value = attachmentServices.value
|
|
|
|
}
|
|
|
|
}
|