diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 5a1a0a1..968e093 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -156,16 +156,20 @@ "share-extension-error.no-account-found" = "No account found"; "status.bookmark" = "Bookmark"; "status.content-warning-abbreviation" = "CW"; -"status.reblogged-by" = "%@ boosted"; +"status.mute" = "Mute conversation"; +"status.pin" = "Pin on profile"; "status.pinned-post" = "Pinned post"; -"status.show-more" = "Show More"; -"status.show-less" = "Show Less"; "status.poll.vote" = "Vote"; "status.poll.time-left" = "%@ left"; "status.poll.refresh" = "Refresh"; "status.poll.closed" = "Closed"; +"status.reblogged-by" = "%@ boosted"; +"status.show-more" = "Show More"; +"status.show-less" = "Show Less"; "status.spoiler-text-placeholder" = "Write your warning here"; "status.unbookmark" = "Unbookmark"; +"status.unmute" = "Unmute conversation"; +"status.unpin" = "Unpin from profile"; "status.visibility.public" = "Public"; "status.visibility.unlisted" = "Unlisted"; "status.visibility.private" = "Followers-only"; diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift index 04b3c67..0aba948 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift @@ -12,6 +12,10 @@ public enum StatusEndpoint { case unfavourite(id: Status.Id) case bookmark(id: Status.Id) case unbookmark(id: Status.Id) + case pin(id: Status.Id) + case unpin(id: Status.Id) + case mute(id: Status.Id) + case unmute(id: Status.Id) case post(Components) } @@ -110,6 +114,14 @@ extension StatusEndpoint: Endpoint { return [id, "bookmark"] case let .unbookmark(id): return [id, "unbookmark"] + case let .pin(id): + return [id, "pin"] + case let .unpin(id): + return [id, "unpin"] + case let .mute(id): + return [id, "mute"] + case let .unmute(id): + return [id, "unmute"] case .post: return [] } @@ -128,7 +140,7 @@ extension StatusEndpoint: Endpoint { switch self { case .status: return .get - case .reblog, .unreblog, .favourite, .unfavourite, .bookmark, .unbookmark, .post: + default: return .post } } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift index f651b13..6aefba2 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift @@ -56,6 +56,22 @@ public extension StatusService { .eraseToAnyPublisher() } + func togglePinned() -> AnyPublisher { + mastodonAPIClient.request(status.displayStatus.pinned ?? false + ? StatusEndpoint.unpin(id: status.displayStatus.id) + : StatusEndpoint.pin(id: status.displayStatus.id)) + .flatMap(contentDatabase.insert(status:)) + .eraseToAnyPublisher() + } + + func toggleMuted() -> AnyPublisher { + mastodonAPIClient.request(status.displayStatus.muted + ? StatusEndpoint.unmute(id: status.displayStatus.id) + : StatusEndpoint.mute(id: status.displayStatus.id)) + .flatMap(contentDatabase.insert(status:)) + .eraseToAnyPublisher() + } + func rebloggedByService() -> AccountListService { AccountListService( endpoint: .rebloggedBy(id: status.id), diff --git a/ViewModels/Sources/ViewModels/StatusViewModel.swift b/ViewModels/Sources/ViewModels/StatusViewModel.swift index 384cbf2..3786c0e 100644 --- a/ViewModels/Sources/ViewModels/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusViewModel.swift @@ -47,6 +47,8 @@ public final class StatusViewModel: CollectionItemViewModel, AttachmentsRenderin } public extension StatusViewModel { + var isMine: Bool { statusService.status.displayStatus.account.id == identification.identity.account?.id } + var shouldShowContent: Bool { guard spoilerText != "" else { return true } @@ -113,6 +115,10 @@ public extension StatusViewModel { var sensitive: Bool { statusService.status.displayStatus.sensitive } + var pinned: Bool? { statusService.status.displayStatus.pinned } + + var muted: Bool { statusService.status.displayStatus.muted } + var sharingURL: URL? { statusService.status.displayStatus.url } var isPollExpired: Bool { statusService.status.displayStatus.poll?.expired ?? true } @@ -231,6 +237,20 @@ public extension StatusViewModel { .eraseToAnyPublisher()) } + func togglePinned() { + eventsSubject.send( + statusService.togglePinned() + .map { _ in .ignorableOutput } + .eraseToAnyPublisher()) + } + + func toggleMuted() { + eventsSubject.send( + statusService.toggleMuted() + .map { _ in .ignorableOutput } + .eraseToAnyPublisher()) + } + func attachmentSelected(viewModel: AttachmentViewModel) { eventsSubject.send(Just(.attachment(viewModel, self)).setFailureType(to: Error.self).eraseToAnyPublisher()) } diff --git a/Views/Status/StatusView.swift b/Views/Status/StatusView.swift index 1f8050f..f133f9b 100644 --- a/Views/Status/StatusView.swift +++ b/Views/Status/StatusView.swift @@ -398,21 +398,44 @@ private extension StatusView { // swiftlint:enable function_body_length func menu(viewModel: StatusViewModel) -> UIMenu { - UIMenu(children: [ + var menuItems = [ UIAction( title: viewModel.bookmarked ? NSLocalizedString("status.unbookmark", comment: "") : NSLocalizedString("status.bookmark", comment: ""), image: UIImage(systemName: "bookmark")) { _ in viewModel.toggleBookmarked() - }, - UIAction( + } + ] + + if let pinned = viewModel.pinned { + menuItems.append(UIAction( + title: pinned + ? NSLocalizedString("status.unpin", comment: "") + : NSLocalizedString("status.pin", comment: ""), + image: UIImage(systemName: "pin")) { _ in + viewModel.togglePinned() + }) + } + + if viewModel.isMine { + menuItems.append(UIAction( + title: viewModel.muted + ? NSLocalizedString("status.unmute", comment: "") + : NSLocalizedString("status.mute", comment: ""), + image: UIImage(systemName: viewModel.muted ? "speaker" : "speaker.slash")) { _ in + viewModel.toggleMuted() + }) + } else { + menuItems.append(UIAction( title: NSLocalizedString("report", comment: ""), image: UIImage(systemName: "flag"), attributes: .destructive) { _ in viewModel.reportStatus() - } - ]) + }) + } + + return UIMenu(children: menuItems) } func setButtonImages(scale: UIImage.SymbolScale) {