metatext/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift

392 lines
14 KiB
Swift
Raw Normal View History

2020-08-21 02:29:01 +00:00
// Copyright © 2020 Metabolist. All rights reserved.
import Combine
2020-09-05 02:31:43 +00:00
import Foundation
2020-08-30 23:33:11 +00:00
import Mastodon
2020-08-31 18:57:02 +00:00
import ServiceLayer
2020-08-21 02:29:01 +00:00
2021-02-04 22:24:27 +00:00
public final class StatusViewModel: AttachmentsRenderingViewModel, ObservableObject {
2021-02-06 22:27:57 +00:00
public let accountViewModel: AccountViewModel
2020-09-01 07:33:49 +00:00
public let content: NSAttributedString
2021-01-12 07:33:35 +00:00
public let contentEmojis: [Emoji]
2020-09-01 07:33:49 +00:00
public let spoilerText: String
public let isReblog: Bool
public let rebloggedByDisplayName: String
2021-01-12 07:33:35 +00:00
public let rebloggedByDisplayNameEmojis: [Emoji]
2020-09-01 07:33:49 +00:00
public let attachmentViewModels: [AttachmentViewModel]
2021-01-12 07:33:35 +00:00
public let pollEmojis: [Emoji]
2020-10-25 02:31:44 +00:00
@Published public var pollOptionSelections = Set<Int>()
2020-10-06 00:33:58 +00:00
public var configuration = CollectionItem.StatusConfiguration.default
2021-03-03 05:02:07 +00:00
public var showReportSelectionToggle = false
public var selectedForReport = false
public let identityContext: IdentityContext
2020-08-21 02:29:01 +00:00
private let statusService: StatusService
2021-02-04 22:24:27 +00:00
private let eventsSubject: PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, Never>
2020-08-21 02:29:01 +00:00
2021-02-04 22:24:27 +00:00
init(statusService: StatusService,
identityContext: IdentityContext,
eventsSubject: PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, Never>) {
2020-08-21 02:29:01 +00:00
self.statusService = statusService
2021-01-26 00:06:35 +00:00
self.identityContext = identityContext
2021-02-04 22:24:27 +00:00
self.eventsSubject = eventsSubject
2021-02-06 22:27:57 +00:00
accountViewModel = AccountViewModel(
accountService: statusService.navigationService
.accountService(account: statusService.status.displayStatus.account),
identityContext: identityContext,
eventsSubject: eventsSubject)
2020-08-21 02:29:01 +00:00
content = statusService.status.displayStatus.content.attributed
2021-01-12 07:33:35 +00:00
contentEmojis = statusService.status.displayStatus.emojis
2020-08-21 02:29:01 +00:00
spoilerText = statusService.status.displayStatus.spoilerText
isReblog = statusService.status.reblog != nil
2020-12-03 22:32:15 +00:00
rebloggedByDisplayName = statusService.status.account.displayName.isEmpty
2020-08-21 02:29:01 +00:00
? statusService.status.account.username
: statusService.status.account.displayName
2021-01-12 07:33:35 +00:00
rebloggedByDisplayNameEmojis = statusService.status.account.emojis
2020-08-28 19:56:28 +00:00
attachmentViewModels = statusService.status.displayStatus.mediaAttachments
2021-01-26 00:06:35 +00:00
.map { AttachmentViewModel(attachment: $0, identityContext: identityContext, status: statusService.status) }
2021-01-12 07:33:35 +00:00
pollEmojis = statusService.status.displayStatus.poll?.emojis ?? []
2020-09-14 23:32:34 +00:00
}
}
2020-09-01 07:33:49 +00:00
public extension StatusViewModel {
2021-01-26 00:06:35 +00:00
var isMine: Bool { statusService.status.displayStatus.account.id == identityContext.identity.account?.id }
2021-01-11 03:12:06 +00:00
2020-10-14 00:03:01 +00:00
var shouldShowContent: Bool {
2020-10-11 00:02:02 +00:00
guard spoilerText != "" else { return true }
2020-10-07 21:06:26 +00:00
2021-01-26 00:06:35 +00:00
if identityContext.identity.preferences.readingExpandSpoilers {
2020-10-14 00:03:01 +00:00
return !configuration.showContentToggled
2020-08-21 02:29:01 +00:00
} else {
2020-10-14 00:03:01 +00:00
return configuration.showContentToggled
2020-08-21 02:29:01 +00:00
}
}
2020-10-14 00:03:01 +00:00
var shouldShowAttachments: Bool {
2021-01-26 00:06:35 +00:00
switch identityContext.identity.preferences.readingExpandMedia {
2020-10-14 00:03:01 +00:00
case .default, .unknown:
return !sensitive || configuration.showAttachmentsToggled
case .showAll:
return !configuration.showAttachmentsToggled
case .hideAll:
return configuration.showAttachmentsToggled
}
}
var shouldShowHideAttachmentsButton: Bool {
2021-01-26 00:06:35 +00:00
sensitive || identityContext.identity.preferences.readingExpandMedia == .hideAll
2020-10-14 00:03:01 +00:00
}
2021-01-10 05:56:15 +00:00
var id: Status.Id { statusService.status.displayStatus.id }
2020-12-03 22:40:33 +00:00
var accountName: String { "@".appending(statusService.status.displayStatus.account.acct) }
2020-08-21 02:29:01 +00:00
2020-10-22 22:16:06 +00:00
var avatarURL: URL {
2021-03-06 02:25:18 +00:00
if identityContext.appPreferences.animateAvatars == .everywhere {
2020-10-22 22:16:06 +00:00
return statusService.status.displayStatus.account.avatar
} else {
return statusService.status.displayStatus.account.avatarStatic
}
}
2020-10-15 07:44:01 +00:00
2021-02-07 00:54:11 +00:00
var rebloggerAvatarURL: URL {
2021-03-06 02:25:18 +00:00
if identityContext.appPreferences.animateAvatars == .everywhere {
2021-02-07 00:54:11 +00:00
return statusService.status.account.avatar
} else {
return statusService.status.account.avatarStatic
}
}
2020-08-21 02:29:01 +00:00
var time: String? { statusService.status.displayStatus.createdAt.timeAgo }
2021-02-02 21:04:11 +00:00
var accessibilityTime: String? { statusService.status.displayStatus.createdAt.accessibilityTimeAgo }
2021-02-02 20:18:15 +00:00
2020-08-21 02:29:01 +00:00
var contextParentTime: String {
Self.contextParentDateFormatter.string(from: statusService.status.displayStatus.createdAt)
}
2021-02-02 20:18:15 +00:00
var accessibilityContextParentTime: String {
Self.contextParentAccessibilityDateFormatter.string(from: statusService.status.displayStatus.createdAt)
}
2020-08-21 02:29:01 +00:00
var applicationName: String? { statusService.status.displayStatus.application?.name }
var applicationURL: URL? {
guard let website = statusService.status.displayStatus.application?.website else { return nil }
return URL(string: website)
}
2021-02-06 21:26:42 +00:00
var mentions: [Mention] { statusService.status.displayStatus.mentions }
2021-01-27 01:12:03 +00:00
var visibility: Status.Visibility { statusService.status.displayStatus.visibility }
2020-08-21 02:29:01 +00:00
var repliesCount: Int { statusService.status.displayStatus.repliesCount }
var reblogsCount: Int { statusService.status.displayStatus.reblogsCount }
var favoritesCount: Int { statusService.status.displayStatus.favouritesCount }
2020-08-24 02:50:54 +00:00
var reblogged: Bool { statusService.status.displayStatus.reblogged }
2020-08-21 02:29:01 +00:00
2020-08-24 02:50:54 +00:00
var favorited: Bool { statusService.status.displayStatus.favourited }
2020-08-21 02:29:01 +00:00
2020-12-01 23:53:14 +00:00
var bookmarked: Bool { statusService.status.displayStatus.bookmarked }
2020-08-21 02:29:01 +00:00
var sensitive: Bool { statusService.status.displayStatus.sensitive }
2021-01-11 03:12:06 +00:00
var pinned: Bool? { statusService.status.displayStatus.pinned }
var muted: Bool { statusService.status.displayStatus.muted }
2021-01-31 21:59:26 +00:00
var sharingURL: URL? {
guard let urlString = statusService.status.displayStatus.url else { return nil }
return URL(string: urlString)
}
2020-08-21 02:29:01 +00:00
2020-10-25 02:31:44 +00:00
var isPollExpired: Bool { statusService.status.displayStatus.poll?.expired ?? true }
var hasVotedInPoll: Bool { statusService.status.displayStatus.poll?.voted ?? false }
var isPollMultipleSelection: Bool { statusService.status.displayStatus.poll?.multiple ?? false }
var pollOptions: [Poll.Option] { statusService.status.displayStatus.poll?.options ?? [] }
var pollVotersCount: Int {
guard let poll = statusService.status.displayStatus.poll else { return 0 }
return poll.votersCount ?? poll.votesCount
}
var pollOwnVotes: Set<Int> { Set(statusService.status.displayStatus.poll?.ownVotes ?? []) }
var pollTimeLeft: String? {
guard let expiresAt = statusService.status.displayStatus.poll?.expiresAt,
expiresAt > Date()
else { return nil }
return expiresAt.fullUnitTimeUntil
}
2020-09-29 01:14:43 +00:00
var cardViewModel: CardViewModel? {
if let card = statusService.status.displayStatus.card {
return CardViewModel(card: card)
} else {
return nil
}
}
2020-08-21 02:29:01 +00:00
var canBeReblogged: Bool {
switch statusService.status.displayStatus.visibility {
case .direct, .private:
return false
default:
return true
}
}
2020-08-24 02:50:54 +00:00
2020-10-14 00:03:01 +00:00
func toggleShowContent() {
eventsSubject.send(
statusService.toggleShowContent()
2020-10-20 06:41:10 +00:00
.map { _ in .ignorableOutput }
2020-10-14 00:03:01 +00:00
.eraseToAnyPublisher())
}
func toggleShowAttachments() {
2020-10-07 21:06:26 +00:00
eventsSubject.send(
2020-10-14 00:03:01 +00:00
statusService.toggleShowAttachments()
2020-10-20 06:41:10 +00:00
.map { _ in .ignorableOutput }
2020-10-07 21:06:26 +00:00
.eraseToAnyPublisher())
}
2020-09-15 01:39:35 +00:00
func urlSelected(_ url: URL) {
eventsSubject.send(
2020-09-25 05:39:06 +00:00
statusService.navigationService.item(url: url)
2020-10-20 06:41:10 +00:00
.map { .navigation($0) }
2020-09-15 01:39:35 +00:00
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2020-09-22 06:53:11 +00:00
func accountSelected() {
eventsSubject.send(
2020-10-20 06:41:10 +00:00
Just(.navigation(
2020-09-27 02:03:53 +00:00
.profile(
statusService.navigationService.profileService(
account: statusService.status.displayStatus.account))))
2020-09-22 06:53:11 +00:00
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2021-02-07 00:54:11 +00:00
func rebloggerAccountSelected() {
eventsSubject.send(
Just(.navigation(
.profile(
statusService.navigationService.profileService(
account: statusService.status.account))))
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2020-09-28 23:23:41 +00:00
func rebloggedBySelected() {
eventsSubject.send(
2020-10-20 06:41:10 +00:00
Just(.navigation(.collection(statusService.rebloggedByService())))
2020-09-28 23:23:41 +00:00
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2020-09-23 01:00:56 +00:00
func favoritedBySelected() {
2020-09-25 05:39:06 +00:00
eventsSubject.send(
2020-10-20 06:41:10 +00:00
Just(.navigation(.collection(statusService.favoritedByService())))
2020-09-25 05:39:06 +00:00
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
2020-09-23 01:00:56 +00:00
}
2021-01-10 05:56:15 +00:00
func reply() {
2021-02-04 22:24:27 +00:00
let replyViewModel = Self(statusService: statusService,
identityContext: identityContext,
eventsSubject: .init())
2021-01-10 05:56:15 +00:00
replyViewModel.configuration = configuration.reply()
2021-01-11 22:45:30 +00:00
eventsSubject.send(
2021-03-02 00:53:36 +00:00
Just(.compose(inReplyTo: replyViewModel))
2021-01-11 22:45:30 +00:00
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
2021-01-10 05:56:15 +00:00
}
2021-01-04 01:51:52 +00:00
func toggleReblogged() {
eventsSubject.send(
statusService.toggleReblogged()
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
2020-08-24 02:50:54 +00:00
func toggleFavorited() {
2020-09-25 05:39:06 +00:00
eventsSubject.send(
statusService.toggleFavorited()
2020-10-20 06:41:10 +00:00
.map { _ in .ignorableOutput }
2020-09-25 05:39:06 +00:00
.eraseToAnyPublisher())
2020-09-14 23:32:34 +00:00
}
2020-12-01 23:53:14 +00:00
func toggleBookmarked() {
eventsSubject.send(
statusService.toggleBookmarked()
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
2021-01-11 03:12:06 +00:00
func togglePinned() {
eventsSubject.send(
statusService.togglePinned()
2021-02-03 21:51:45 +00:00
.collect()
.map { _ in .refresh }
2021-01-11 03:12:06 +00:00
.eraseToAnyPublisher())
}
func toggleMuted() {
eventsSubject.send(
statusService.toggleMuted()
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
2021-01-11 23:40:46 +00:00
func confirmDelete(redraft: Bool) {
eventsSubject.send(
Just(.confirmDelete(self, redraft: redraft))
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2021-01-11 22:45:30 +00:00
func delete() {
eventsSubject.send(
statusService.delete()
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
func deleteAndRedraft() {
2021-01-26 00:06:35 +00:00
let identityContext = self.identityContext
2021-01-11 22:45:30 +00:00
eventsSubject.send(
statusService.deleteAndRedraft()
.map { redraft, inReplyToStatusService in
let inReplyToViewModel: StatusViewModel?
if let inReplyToStatusService = inReplyToStatusService {
inReplyToViewModel = Self(
statusService: inReplyToStatusService,
2021-02-04 22:24:27 +00:00
identityContext: identityContext,
eventsSubject: .init())
2021-01-11 22:45:30 +00:00
inReplyToViewModel?.configuration = CollectionItem.StatusConfiguration.default.reply()
} else {
inReplyToViewModel = nil
}
return .compose(inReplyTo: inReplyToViewModel, redraft: redraft)
}
.eraseToAnyPublisher())
}
2020-10-20 06:41:10 +00:00
func attachmentSelected(viewModel: AttachmentViewModel) {
2021-02-07 01:03:15 +00:00
if viewModel.attachment.type == .unknown, let remoteUrl = viewModel.attachment.remoteUrl {
urlSelected(remoteUrl)
} else {
eventsSubject.send(Just(.attachment(viewModel, self)).setFailureType(to: Error.self).eraseToAnyPublisher())
}
2020-10-20 06:41:10 +00:00
}
2020-09-14 23:32:34 +00:00
func shareStatus() {
2021-01-31 21:59:26 +00:00
guard let urlString = statusService.status.displayStatus.url,
let url = URL(string: urlString)
else { return }
2020-09-14 23:32:34 +00:00
2020-10-20 06:41:10 +00:00
eventsSubject.send(Just(.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher())
2020-08-24 02:50:54 +00:00
}
2020-10-25 02:31:44 +00:00
2020-11-30 02:54:11 +00:00
func reportStatus() {
eventsSubject.send(
Just(.report(ReportViewModel(
accountService: statusService.navigationService.accountService(
account: statusService.status.displayStatus.account),
2021-03-03 05:02:07 +00:00
statusId: statusService.status.displayStatus.id,
2021-01-26 00:06:35 +00:00
identityContext: identityContext)))
2020-11-30 02:54:11 +00:00
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2020-10-25 02:31:44 +00:00
func vote() {
eventsSubject.send(
statusService.vote(selectedOptions: pollOptionSelections)
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
func refreshPoll() {
eventsSubject.send(
statusService.refreshPoll()
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
2020-08-21 02:29:01 +00:00
}
private extension StatusViewModel {
private static let contextParentDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short
return dateFormatter
}()
2021-02-02 20:18:15 +00:00
private static let contextParentAccessibilityDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
return dateFormatter
}()
2020-08-21 02:29:01 +00:00
}