mastodon-ios/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift

139 lines
4.3 KiB
Swift

//
// ProfileAboutViewModel.swift
// Mastodon
//
// Created by MainasuK on 2022-1-22.
//
import os.log
import UIKit
import Combine
import CoreDataStack
import MastodonSDK
import MastodonMeta
import MastodonCore
import Kanna
final class ProfileAboutViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
@Published var user: MastodonUser?
@Published var isEditing = false
@Published var accountForEdit: Mastodon.Entity.Account?
// output
var diffableDataSource: UICollectionViewDiffableDataSource<ProfileFieldSection, ProfileFieldItem>?
let profileInfo = ProfileInfo()
let profileInfoEditing = ProfileInfo()
@Published var fields: [MastodonField] = []
@Published var emojiMeta: MastodonContent.Emojis = [:]
@Published var createdAt: Date = Date()
init(context: AppContext) {
self.context = context
// end init
$user
.compactMap { $0 }
.flatMap { $0.publisher(for: \.emojis) }
.map { $0.asDictionary }
.assign(to: &$emojiMeta)
$user
.compactMap { $0 }
.flatMap { $0.publisher(for: \.fields) }
.assign(to: &$fields)
$user
.compactMap { $0 }
.flatMap { $0.publisher(for: \.createdAt) }
.assign(to: &$createdAt)
Publishers.CombineLatest(
$fields,
$emojiMeta
)
.map { fields, emojiMeta in
fields.map { ProfileFieldItem.FieldValue(name: $0.name, value: $0.value, verifiedAt: $0.verifiedAt, emojiMeta: emojiMeta) }
}
.assign(to: &profileInfo.$fields)
Publishers.CombineLatest(
$accountForEdit,
$emojiMeta
)
.receive(on: DispatchQueue.main)
.sink { [weak self] account, emojiMeta in
guard let self = self else { return }
guard let account = account else { return }
// update profileInfo will occurs race condition issue
// bind user.fields to profileInfo to avoid it
self.profileInfoEditing.fields = account.source?.fields?.compactMap { field in
ProfileFieldItem.FieldValue(
name: field.name,
value: field.value,
verifiedAt: field.verifiedAt,
emojiMeta: [:] // no use for editing
)
} ?? []
}
.store(in: &disposeBag)
}
}
extension ProfileAboutViewModel {
class ProfileInfo {
@Published var fields: [ProfileFieldItem.FieldValue] = []
}
}
extension ProfileAboutViewModel {
func appendFieldItem() {
var fields = profileInfoEditing.fields
guard fields.count < ProfileHeaderViewModel.maxProfileFieldCount else { return }
fields.append(ProfileFieldItem.FieldValue(name: "", value: "", verifiedAt: nil, emojiMeta: [:]))
profileInfoEditing.fields = fields
}
func removeFieldItem(item: ProfileFieldItem) {
var fields = profileInfoEditing.fields
guard case let .editField(field) = item else { return }
guard let removeIndex = fields.firstIndex(of: field) else { return }
fields.remove(at: removeIndex)
profileInfoEditing.fields = fields
}
}
// MARK: - ProfileViewModelEditable
extension ProfileAboutViewModel: ProfileViewModelEditable {
var isEdited: Bool {
guard isEditing else { return false }
let isFieldsEqual: Bool = {
let originalFields = self.accountForEdit?.source?.fields?.compactMap { field in
ProfileFieldItem.FieldValue(name: field.name, value: field.value, verifiedAt: nil, emojiMeta: [:])
} ?? []
let editFields = profileInfoEditing.fields
guard editFields.count == originalFields.count else { return false }
for (editField, originalField) in zip(editFields, originalFields) {
guard editField.name.value == originalField.name.value,
editField.value.value == originalField.value.value else {
return false
}
}
return true
}()
guard isFieldsEqual else { return true }
return false
}
}