mastodon-ios/Mastodon/Scene/Compose/ComposeViewModel.swift

169 lines
6.9 KiB
Swift
Raw Normal View History

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 {
var disposeBag = Set<AnyCancellable>()
2021-03-11 08:41:27 +01:00
// input
let context: AppContext
let composeKind: ComposeStatusSection.ComposeKind
2021-03-16 04:23:19 +01:00
let composeStatusAttribute = ComposeStatusItem.ComposeStatusAttribute()
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
}()
// 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
// custom emojis
let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
// attachment
let attachmentServices = CurrentValueSubject<[MastodonAttachmentService], Never>([])
2021-03-11 08:41:27 +01:00
init(
context: AppContext,
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
}
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
// 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)
// bind avatar and names
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
self.composeStatusAttribute.avatarURL.value = mastodonUser?.avatarImageURL()
self.composeStatusAttribute.displayName.value = {
guard let displayName = mastodonUser?.displayName, !displayName.isEmpty else {
return username
}
return displayName
}()
self.composeStatusAttribute.username.value = username
}
.store(in: &disposeBag)
// 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-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)
// bind modal dismiss state
composeStatusAttribute.composeContent
.receive(on: DispatchQueue.main)
.map { content in
let content = content ?? ""
return content.isEmpty
}
.assign(to: \.value, on: shouldDismiss)
.store(in: &disposeBag)
// 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
self.customEmojiViewModel.value = self.context.emojiService.dequeueCustomEmojiViewModel(for: domain)
}
.store(in: &disposeBag)
// 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-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
}
}