mastodon-ios/Mastodon/Scene/Profile/ProfileViewModel.swift

247 lines
9.5 KiB
Swift
Raw Normal View History

2021-04-01 08:39:15 +02:00
//
// ProfileViewModel.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-3-29.
//
import UIKit
import Combine
import CoreDataStack
import MastodonSDK
2021-07-23 13:10:27 +02:00
import MastodonMeta
import MastodonAsset
2022-10-08 07:43:06 +02:00
import MastodonCore
import MastodonLocalization
import MastodonUI
2021-04-01 08:39:15 +02:00
// please override this base class
class ProfileViewModel: NSObject {
2021-04-01 08:39:15 +02:00
typealias UserID = String
var disposeBag = Set<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
private var mastodonUserObserver: AnyCancellable?
private var currentMastodonUserObserver: AnyCancellable?
2022-05-13 11:23:35 +02:00
let postsUserTimelineViewModel: UserTimelineViewModel
let repliesUserTimelineViewModel: UserTimelineViewModel
let mediaUserTimelineViewModel: UserTimelineViewModel
let profileAboutViewModel: ProfileAboutViewModel
2021-04-01 08:39:15 +02:00
// input
let context: AppContext
let authContext: AuthContext
@Published var me: Mastodon.Entity.Account
@Published var account: Mastodon.Entity.Account
@Published var relationship: Mastodon.Entity.Relationship?
2021-04-01 08:39:15 +02:00
let viewDidAppear = PassthroughSubject<Void, Never>()
2022-05-13 11:23:35 +02:00
@Published var isEditing = false
@Published var isUpdating = false
@Published var accountForEdit: Mastodon.Entity.Account?
2021-04-01 08:39:15 +02:00
2022-05-13 11:23:35 +02:00
@Published var userIdentifier: UserIdentifier? = nil
@Published var isRelationshipActionButtonHidden: Bool = true
@Published var isReplyBarButtonItemHidden: Bool = true
@Published var isMoreMenuBarButtonItemHidden: Bool = true
@Published var isMeBarButtonItemsHidden: Bool = true
@Published var isPagingEnabled = true
// @Published var protected: Bool? = nil
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
2023-11-22 12:32:04 +01:00
@MainActor
init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?, me: Mastodon.Entity.Account) {
2021-04-01 08:39:15 +02:00
self.context = context
self.authContext = authContext
self.account = account
self.relationship = relationship
self.me = me
2022-05-13 11:23:35 +02:00
self.postsUserTimelineViewModel = UserTimelineViewModel(
context: context,
authContext: authContext,
title: L10n.Scene.Profile.SegmentedControl.posts,
2022-05-13 11:23:35 +02:00
queryFilter: .init(excludeReplies: true)
)
self.repliesUserTimelineViewModel = UserTimelineViewModel(
context: context,
authContext: authContext,
title: L10n.Scene.Profile.SegmentedControl.postsAndReplies,
queryFilter: .init(excludeReplies: false)
2022-05-13 11:23:35 +02:00
)
self.mediaUserTimelineViewModel = UserTimelineViewModel(
context: context,
authContext: authContext,
title: L10n.Scene.Profile.SegmentedControl.media,
2022-05-13 11:23:35 +02:00
queryFilter: .init(onlyMedia: true)
)
2023-12-08 15:26:46 +01:00
self.profileAboutViewModel = ProfileAboutViewModel(context: context, account: account)
2021-04-01 08:39:15 +02:00
super.init()
2022-05-13 11:23:35 +02:00
// bind user
$account
2022-05-13 11:23:35 +02:00
.map { user -> UserIdentifier? in
guard let domain = account.domain else { return nil }
return MastodonUserIdentifier(domain: domain, userID: account.id)
2022-05-13 11:23:35 +02:00
}
.assign(to: &$userIdentifier)
// bind userIdentifier
2022-05-13 11:23:35 +02:00
$userIdentifier.assign(to: &postsUserTimelineViewModel.$userIdentifier)
$userIdentifier.assign(to: &repliesUserTimelineViewModel.$userIdentifier)
$userIdentifier.assign(to: &mediaUserTimelineViewModel.$userIdentifier)
// bind bar button items
Publishers.CombineLatest3($account, $me, $relationship)
.sink(receiveValue: { [weak self] account, me, relationship in
guard let self else {
self?.isReplyBarButtonItemHidden = true
self?.isMoreMenuBarButtonItemHidden = true
self?.isMeBarButtonItemsHidden = true
return
}
let isMyself = (account == me)
self.isReplyBarButtonItemHidden = isMyself
self.isMoreMenuBarButtonItemHidden = isMyself
self.isMeBarButtonItemsHidden = (isMyself == false)
})
.store(in: &disposeBag)
viewDidAppear
.sink { [weak self] _ in
guard let self else { return }
self.isReplyBarButtonItemHidden = self.isReplyBarButtonItemHidden
self.isMoreMenuBarButtonItemHidden = self.isMoreMenuBarButtonItemHidden
self.isMeBarButtonItemsHidden = self.isMeBarButtonItemsHidden
}
.store(in: &disposeBag)
// query relationship
#warning("TODO: Implement")
// let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
2021-04-01 08:39:15 +02:00
// // observe friendship
// Publishers.CombineLatest(
// account,
// pendingRetryPublisher
// )
// .sink { [weak self] account, _ in
// guard let self, let account else { return }
//
// Task {
// do {
// let response = try await self.updateRelationship(
// account: account,
// authenticationBox: self.authContext.mastodonAuthenticationBox
// )
// // there are seconds delay after request follow before requested -> following. Query again when needs
// guard let relationship = response.value.first else { return }
// if relationship.requested == true {
// let delay = pendingRetryPublisher.value
// DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
// guard let _ = self else { return }
// pendingRetryPublisher.value = min(2 * delay, 60)
// }
// }
// } catch {
// }
// } // end Task
// }
// .store(in: &disposeBag)
let isBlockingOrBlocked = Publishers.CombineLatest3(
(relationship?.blocking ?? false).publisher,
(relationship?.blockedBy ?? false).publisher,
(relationship?.domainBlocking ?? false).publisher
)
.map { $0 || $1 || $2 }
.share()
2023-06-01 16:34:45 +02:00
Publishers.CombineLatest(
isBlockingOrBlocked,
$isEditing
)
.map { !$0 && !$1 }
.assign(to: &$isPagingEnabled)
2023-06-01 16:34:45 +02:00
}
// fetch profile info before edit
func fetchEditProfileInfo() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
guard let domain = me.domain else {
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
}
2023-11-13 12:53:47 +01:00
let mastodonAuthentication = authContext.mastodonAuthenticationBox.authentication
let authorization = Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken)
return context.apiService.accountVerifyCredentials(domain: domain, authorization: authorization)
}
private func updateRelationship(
account: Mastodon.Entity.Account,
authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Relationship]> {
let response = try await context.apiService.relationship(
forAccounts: [account],
authenticationBox: authenticationBox
)
return response
}
}
extension ProfileViewModel {
func updateProfileInfo(
headerProfileInfo: ProfileHeaderViewModel.ProfileInfo,
aboutProfileInfo: ProfileAboutViewModel.ProfileInfo
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
let authenticationBox = authContext.mastodonAuthenticationBox
let domain = authenticationBox.domain
let authorization = authenticationBox.userAuthorization
2022-11-17 04:49:49 +01:00
// TODO: constrain size?
2022-11-17 04:52:44 +01:00
let _header: UIImage? = {
guard let image = headerProfileInfo.header else { return nil }
guard image.size.width <= ProfileHeaderViewModel.bannerImageMaxSizeInPixel.width else {
return image.af.imageScaled(to: ProfileHeaderViewModel.bannerImageMaxSizeInPixel)
}
return image
}()
2022-11-17 04:49:49 +01:00
let _avatar: UIImage? = {
guard let image = headerProfileInfo.avatar else { return nil }
guard image.size.width <= ProfileHeaderViewModel.avatarImageMaxSizeInPixel.width else {
return image.af.imageScaled(to: ProfileHeaderViewModel.avatarImageMaxSizeInPixel)
}
return image
}()
let fieldsAttributes = aboutProfileInfo.fields.map { field in
Mastodon.Entity.Field(name: field.name.value, value: field.value.value)
}
let query = Mastodon.API.Account.UpdateCredentialQuery(
discoverable: nil,
bot: nil,
displayName: headerProfileInfo.name,
note: headerProfileInfo.note,
2022-11-17 04:49:49 +01:00
avatar: _avatar.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) },
header: _header.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) },
locked: nil,
source: nil,
fieldsAttributes: fieldsAttributes
)
return try await context.apiService.accountUpdateCredentials(
domain: domain,
query: query,
authorization: authorization
)
}
}