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