metatext-app-ios-iphone-ipad/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift

413 lines
15 KiB
Swift
Raw Normal View History

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 {
2021-02-06 23:27:57 +01:00
public let accountViewModel: AccountViewModel
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 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-03-03 06:02:07 +01:00
public var showReportSelectionToggle = false
public var selectedForReport = false
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
2021-02-06 23:27:57 +01:00
accountViewModel = AccountViewModel(
accountService: statusService.navigationService
.accountService(account: statusService.status.displayStatus.account),
identityContext: identityContext,
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-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-03-06 03:25:18 +01:00
if identityContext.appPreferences.animateAvatars == .everywhere {
2021-03-29 08:04:14 +02:00
return statusService.status.displayStatus.account.avatar.url
2020-10-23 00:16:06 +02:00
} else {
2021-03-29 08:04:14 +02:00
return statusService.status.displayStatus.account.avatarStatic.url
2020-10-23 00:16:06 +02:00
}
}
2020-10-15 09:44:01 +02:00
2021-02-07 01:54:11 +01:00
var rebloggerAvatarURL: URL {
2021-03-06 03:25:18 +01:00
if identityContext.appPreferences.animateAvatars == .everywhere {
2021-03-29 08:04:14 +02:00
return statusService.status.account.avatar.url
2021-02-07 01:54:11 +01:00
} else {
2021-03-29 08:04:14 +02:00
return statusService.status.account.avatarStatic.url
2021-02-07 01:54:11 +01: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(
account: statusService.status.displayStatus.account))))
2020-09-22 08:53:11 +02:00
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2021-02-07 01:54:11 +01:00
func rebloggerAccountSelected() {
eventsSubject.send(
Just(.navigation(
.profile(
statusService.navigationService.profileService(
account: statusService.status.account))))
.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-03-22 00:23:41 +01:00
func reply(identity: Identity? = nil) {
if let identity = identity {
let identityContext = self.identityContext
let configuration = self.configuration.reply()
2021-01-10 06:56:15 +01:00
2021-03-22 00:23:41 +01:00
eventsSubject.send(statusService.asIdentity(id: identity.id).map {
let replyViewModel = Self(statusService: $0,
identityContext: identityContext,
eventsSubject: .init())
2021-01-10 06:56:15 +01:00
2021-03-22 00:23:41 +01:00
replyViewModel.configuration = configuration
return CollectionItemEvent.compose(identity: identity, inReplyTo: replyViewModel)
}
.eraseToAnyPublisher())
} else {
let replyViewModel = Self(statusService: statusService,
identityContext: identityContext,
eventsSubject: .init())
replyViewModel.configuration = configuration.reply()
eventsSubject.send(
Just(.compose(inReplyTo: replyViewModel))
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
2021-01-10 06:56:15 +01:00
}
func toggleReblogged(identityId: Identity.Id? = nil) {
2021-01-04 02:51:52 +01:00
eventsSubject.send(
statusService.toggleReblogged(identityId: identityId)
2021-01-04 02:51:52 +01:00
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
func toggleFavorited(identityId: Identity.Id? = nil) {
2020-09-25 07:39:06 +02:00
eventsSubject.send(
statusService.toggleFavorited(identityId: identityId)
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() {
let isContextParent = configuration.isContextParent
2021-01-11 23:45:30 +01:00
eventsSubject.send(
statusService.delete()
.map { _ in isContextParent ? .contextParentDeleted : .ignorableOutput }
2021-01-11 23:45:30 +01:00
.eraseToAnyPublisher())
}
func deleteAndRedraft() {
2021-01-26 01:06:35 +01:00
let identityContext = self.identityContext
let isContextParent = configuration.isContextParent
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,
redraftWasContextParent: isContextParent)
2021-01-11 23:45:30 +01:00
}
.eraseToAnyPublisher())
}
2020-10-20 08:41:10 +02:00
func attachmentSelected(viewModel: AttachmentViewModel) {
2021-02-07 02:03:15 +01:00
if viewModel.attachment.type == .unknown, let remoteUrl = viewModel.attachment.remoteUrl {
2021-03-29 08:04:14 +02:00
urlSelected(remoteUrl.url)
2021-02-07 02:03:15 +01:00
} else {
eventsSubject.send(Just(.attachment(viewModel, self)).setFailureType(to: Error.self).eraseToAnyPublisher())
}
2020-10-20 08:41:10 +02:00
}
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),
2021-03-03 06:02:07 +01:00
statusId: statusService.status.displayStatus.id,
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
}