// // ProfileHeaderViewModel.swift // Mastodon // // Created by MainasuK Cirno on 2021-4-9. // import os.log import UIKit import Combine import Kanna import MastodonSDK import MastodonMeta final class ProfileHeaderViewModel { static let avatarImageMaxSizeInPixel = CGSize(width: 400, height: 400) static let maxProfileFieldCount = 4 var disposeBag = Set() // input let context: AppContext @Published var isEditing = false @Published var accountForEdit: Mastodon.Entity.Account? @Published var emojiMeta: MastodonContent.Emojis = [:] let viewDidAppear = CurrentValueSubject(false) let needsSetupBottomShadow = CurrentValueSubject(true) let needsFiledCollectionViewHidden = CurrentValueSubject(false) let isTitleViewContentOffsetSet = CurrentValueSubject(false) // output let isTitleViewDisplaying = CurrentValueSubject(false) let displayProfileInfo = ProfileInfo() let editProfileInfo = ProfileInfo() let editProfileInfoDidInitialized = CurrentValueSubject(Void()) // needs trigger initial event init(context: AppContext) { self.context = context Publishers.CombineLatest( $isEditing.removeDuplicates(), // only trigger when value toggle $accountForEdit ) .receive(on: DispatchQueue.main) .sink { [weak self] isEditing, account in guard let self = self else { return } guard isEditing else { return } // setup editing value when toggle to editing self.editProfileInfo.name = self.displayProfileInfo.name // set to name self.editProfileInfo.avatarImage = nil // set to empty self.editProfileInfo.note = ProfileHeaderViewModel.normalize(note: self.displayProfileInfo.note) self.editProfileInfoDidInitialized.send() } .store(in: &disposeBag) } } extension ProfileHeaderViewModel { class ProfileInfo { // input @Published var name: String? @Published var avatarImageURL: URL? @Published var avatarImage: UIImage? @Published var note: String? // output @Published var avatarImageResource = ImageResource(url: nil, image: nil) struct ImageResource { let url: URL? let image: UIImage? } init() { Publishers.CombineLatest( $avatarImageURL, $avatarImage ) .map { url, image in ImageResource(url: url, image: image) } .assign(to: &$avatarImageResource) } } } extension ProfileHeaderViewModel { static func normalize(note: String?) -> String? { let _note = note?.replacingOccurrences(of: "
|
", with: "\u{2028}", options: .regularExpression, range: nil) .replacingOccurrences(of: "

", with: "

\u{2029}", range: nil) .trimmingCharacters(in: .whitespacesAndNewlines) guard let note = _note, !note.isEmpty else { return nil } let html = try? HTML(html: note, encoding: .utf8) return html?.text } } // MARK: - ProfileViewModelEditable extension ProfileHeaderViewModel: ProfileViewModelEditable { func isEdited() -> Bool { guard isEditing else { return false } guard editProfileInfo.name == displayProfileInfo.name else { return true } guard editProfileInfo.avatarImage == nil else { return true } guard editProfileInfo.note == ProfileHeaderViewModel.normalize(note: displayProfileInfo.note) else { return true } return false } }