diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 95ade39..ea64c80 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -124,6 +124,7 @@ "report.target-%@" = "Reporting %@"; "report.forward.hint" = "The account is from another server. Send an anonymized copy of the report there as well?"; "report.forward-%@" = "Forward report to %@"; +"status.bookmark" = "Bookmark"; "status.reblogged-by" = "%@ boosted"; "status.pinned-post" = "Pinned post"; "status.show-more" = "Show More"; @@ -132,6 +133,7 @@ "status.poll.time-left" = "%@ left"; "status.poll.refresh" = "Refresh"; "status.poll.closed" = "Closed"; +"status.unbookmark" = "Unbookmark"; "status.visibility.public" = "Public"; "status.visibility.unlisted" = "Unlisted"; "status.visibility.private" = "Private"; diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift index 2426b4d..7d3e61a 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift @@ -8,6 +8,8 @@ public enum StatusEndpoint { case status(id: Status.Id) case favourite(id: Status.Id) case unfavourite(id: Status.Id) + case bookmark(id: Status.Id) + case unbookmark(id: Status.Id) } extension StatusEndpoint: Endpoint { @@ -25,6 +27,10 @@ extension StatusEndpoint: Endpoint { return [id, "favourite"] case let .unfavourite(id): return [id, "unfavourite"] + case let .bookmark(id): + return [id, "bookmark"] + case let .unbookmark(id): + return [id, "unbookmark"] } } @@ -32,7 +38,7 @@ extension StatusEndpoint: Endpoint { switch self { case .status: return .get - case .favourite, .unfavourite: + case .favourite, .unfavourite, .bookmark, .unbookmark: return .post } } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift index 730b5fb..6455baf 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift @@ -40,6 +40,14 @@ public extension StatusService { .eraseToAnyPublisher() } + func toggleBookmarked() -> AnyPublisher { + mastodonAPIClient.request(status.displayStatus.bookmarked + ? StatusEndpoint.unbookmark(id: status.displayStatus.id) + : StatusEndpoint.bookmark(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 665c0df..a70dbac 100644 --- a/ViewModels/Sources/ViewModels/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusViewModel.swift @@ -107,6 +107,8 @@ public extension StatusViewModel { var favorited: Bool { statusService.status.displayStatus.favourited } + var bookmarked: Bool { statusService.status.displayStatus.bookmarked } + var sensitive: Bool { statusService.status.displayStatus.sensitive } var sharingURL: URL? { statusService.status.displayStatus.url } @@ -205,6 +207,13 @@ public extension StatusViewModel { .eraseToAnyPublisher()) } + func toggleBookmarked() { + eventsSubject.send( + statusService.toggleBookmarked() + .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 d71ae35..31c0032 100644 --- a/Views/Status/StatusView.swift +++ b/Views/Status/StatusView.swift @@ -3,6 +3,7 @@ // swiftlint:disable file_length import Kingfisher import UIKit +import ViewModels final class StatusView: UIView { let avatarImageView = AnimatedImageView() @@ -206,14 +207,6 @@ private extension StatusView { for: .touchUpInside) menuButton.showsMenuAsPrimaryAction = true - menuButton.menu = UIMenu(children: [ - UIAction( - title: NSLocalizedString("report", comment: ""), - image: UIImage(systemName: "flag"), - attributes: .destructive) { [weak self] _ in - self?.statusConfiguration.viewModel.reportStatus() - } - ]) for button in actionButtons { button.titleLabel?.font = .preferredFont(forTextStyle: .footnote) @@ -272,6 +265,8 @@ private extension StatusView { let isContextParent = viewModel.configuration.isContextParent let mutableDisplayName = NSMutableAttributedString(string: viewModel.displayName) + menuButton.menu = menu(viewModel: viewModel) + avatarImageView.kf.setImage(with: viewModel.avatarURL) sideStackView.isHidden = isContextParent @@ -396,6 +391,24 @@ private extension StatusView { } // swiftlint:enable function_body_length + func menu(viewModel: StatusViewModel) -> UIMenu { + UIMenu(children: [ + UIAction( + title: viewModel.bookmarked + ? NSLocalizedString("status.unbookmark", comment: "") + : NSLocalizedString("status.bookmark", comment: ""), + image: UIImage(systemName: "bookmark")) { _ in + viewModel.toggleBookmarked() + }, + UIAction( + title: NSLocalizedString("report", comment: ""), + image: UIImage(systemName: "flag"), + attributes: .destructive) { _ in + viewModel.reportStatus() + } + ]) + } + func setButtonImages(scale: UIImage.SymbolScale) { replyButton.setImage(UIImage(systemName: "bubble.right", withConfiguration: UIImage.SymbolConfiguration(scale: scale)), for: .normal)