forked from zelo72/mastodon-ios
feat: implement boost for toot
This commit is contained in:
parent
ce0fc56cd7
commit
441a6aee9e
|
@ -163,7 +163,7 @@ public extension Toot {
|
|||
func update(liked: Bool, mastodonUser: MastodonUser) {
|
||||
if liked {
|
||||
if !(self.favouritedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.favouritedBy)).addObjects(from: [mastodonUser])
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.favouritedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.favouritedBy ?? Set()).contains(mastodonUser) {
|
||||
|
@ -174,7 +174,7 @@ public extension Toot {
|
|||
func update(reblogged: Bool, mastodonUser: MastodonUser) {
|
||||
if reblogged {
|
||||
if !(self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.rebloggedBy)).addObjects(from: [mastodonUser])
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.rebloggedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
||||
|
@ -186,7 +186,7 @@ public extension Toot {
|
|||
func update(muted: Bool, mastodonUser: MastodonUser) {
|
||||
if muted {
|
||||
if !(self.mutedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.mutedBy)).addObjects(from: [mastodonUser])
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.mutedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.mutedBy ?? Set()).contains(mastodonUser) {
|
||||
|
@ -198,7 +198,7 @@ public extension Toot {
|
|||
func update(bookmarked: Bool, mastodonUser: MastodonUser) {
|
||||
if bookmarked {
|
||||
if !(self.bookmarkedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.bookmarkedBy)).addObjects(from: [mastodonUser])
|
||||
self.mutableSetValue(forKey: #keyPath(Toot.bookmarkedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.bookmarkedBy ?? Set()).contains(mastodonUser) {
|
||||
|
|
|
@ -183,6 +183,7 @@
|
|||
DB9D6C2E25E504AC0051B173 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C2D25E504AC0051B173 /* Attachment.swift */; };
|
||||
DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C3725E508BE0051B173 /* Attachment.swift */; };
|
||||
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; };
|
||||
DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */; };
|
||||
DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; };
|
||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; };
|
||||
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */; };
|
||||
|
@ -425,6 +426,7 @@
|
|||
DB9D6C2D25E504AC0051B173 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
|
||||
DB9D6C3725E508BE0051B173 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
|
||||
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
||||
DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Reblog.swift"; sourceTree = "<group>"; };
|
||||
DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = "<group>"; };
|
||||
DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewController.swift; sourceTree = "<group>"; };
|
||||
DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -906,6 +908,7 @@
|
|||
DB98337E25C9452D00AD9700 /* APIService+APIError.swift */,
|
||||
DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */,
|
||||
2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */,
|
||||
DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */,
|
||||
DB98336A25C9420100AD9700 /* APIService+App.swift */,
|
||||
DB98337025C9443200AD9700 /* APIService+Authentication.swift */,
|
||||
DB98339B25C96DE600AD9700 /* APIService+Account.swift */,
|
||||
|
@ -1663,6 +1666,7 @@
|
|||
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */,
|
||||
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
|
||||
DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */,
|
||||
DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -159,7 +159,7 @@ extension StatusSection {
|
|||
|
||||
// set poll
|
||||
let poll = (toot.reblog ?? toot).poll
|
||||
StatusSection.configure(
|
||||
StatusSection.configurePoll(
|
||||
cell: cell,
|
||||
poll: poll,
|
||||
requestUserID: requestUserID,
|
||||
|
@ -173,7 +173,7 @@ extension StatusSection {
|
|||
} receiveValue: { change in
|
||||
guard case let .update(object) = change.changeType,
|
||||
let newPoll = object as? Poll else { return }
|
||||
StatusSection.configure(
|
||||
StatusSection.configurePoll(
|
||||
cell: cell,
|
||||
poll: newPoll,
|
||||
requestUserID: requestUserID,
|
||||
|
@ -185,19 +185,7 @@ extension StatusSection {
|
|||
}
|
||||
|
||||
// toolbar
|
||||
let replyCountTitle: String = {
|
||||
let count = (toot.reblog ?? toot).repliesCount?.intValue ?? 0
|
||||
return StatusSection.formattedNumberTitleForActionButton(count)
|
||||
}()
|
||||
cell.statusView.actionToolbarContainer.replyButton.setTitle(replyCountTitle, for: .normal)
|
||||
|
||||
let isLike = (toot.reblog ?? toot).favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
||||
let favoriteCountTitle: String = {
|
||||
let count = (toot.reblog ?? toot).favouritesCount.intValue
|
||||
return StatusSection.formattedNumberTitleForActionButton(count)
|
||||
}()
|
||||
cell.statusView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal)
|
||||
cell.statusView.actionToolbarContainer.isStarButtonHighlight = isLike
|
||||
StatusSection.configureActionToolBar(cell: cell, toot: toot, requestUserID: requestUserID)
|
||||
|
||||
// set date
|
||||
let createdAt = (toot.reblog ?? toot).createdAt
|
||||
|
@ -215,20 +203,47 @@ extension StatusSection {
|
|||
// do nothing
|
||||
} receiveValue: { change in
|
||||
guard case .update(let object) = change.changeType,
|
||||
let newToot = object as? Toot else { return }
|
||||
let targetToot = newToot.reblog ?? newToot
|
||||
|
||||
let isLike = targetToot.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
||||
let favoriteCount = targetToot.favouritesCount.intValue
|
||||
let favoriteCountTitle = StatusSection.formattedNumberTitleForActionButton(favoriteCount)
|
||||
cell.statusView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal)
|
||||
cell.statusView.actionToolbarContainer.isStarButtonHighlight = isLike
|
||||
os_log("%{public}s[%{public}ld], %{public}s: like count label for toot %s did update: %ld", (#file as NSString).lastPathComponent, #line, #function, targetToot.id, favoriteCount)
|
||||
let toot = object as? Toot else { return }
|
||||
StatusSection.configureActionToolBar(cell: cell, toot: toot, requestUserID: requestUserID)
|
||||
|
||||
os_log("%{public}s[%{public}ld], %{public}s: boost count label for toot %s did update: %ld", (#file as NSString).lastPathComponent, #line, #function, toot.id, toot.reblogsCount.intValue)
|
||||
os_log("%{public}s[%{public}ld], %{public}s: like count label for toot %s did update: %ld", (#file as NSString).lastPathComponent, #line, #function, toot.id, toot.favouritesCount.intValue)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
static func configure(
|
||||
static func configureActionToolBar(
|
||||
cell: StatusTableViewCell,
|
||||
toot: Toot,
|
||||
requestUserID: String
|
||||
) {
|
||||
let toot = toot.reblog ?? toot
|
||||
|
||||
// set reply
|
||||
let replyCountTitle: String = {
|
||||
let count = toot.repliesCount?.intValue ?? 0
|
||||
return StatusSection.formattedNumberTitleForActionButton(count)
|
||||
}()
|
||||
cell.statusView.actionToolbarContainer.replyButton.setTitle(replyCountTitle, for: .normal)
|
||||
// set boost
|
||||
let isBoosted = toot.rebloggedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
||||
let boostCountTitle: String = {
|
||||
let count = toot.reblogsCount.intValue
|
||||
return StatusSection.formattedNumberTitleForActionButton(count)
|
||||
}()
|
||||
cell.statusView.actionToolbarContainer.boostButton.setTitle(boostCountTitle, for: .normal)
|
||||
cell.statusView.actionToolbarContainer.isBoostButtonHighlight = isBoosted
|
||||
// set like
|
||||
let isLike = toot.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
||||
let favoriteCountTitle: String = {
|
||||
let count = toot.favouritesCount.intValue
|
||||
return StatusSection.formattedNumberTitleForActionButton(count)
|
||||
}()
|
||||
cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal)
|
||||
cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike
|
||||
}
|
||||
|
||||
static func configurePoll(
|
||||
cell: StatusTableViewCell,
|
||||
poll: Poll?,
|
||||
requestUserID: String,
|
||||
|
|
|
@ -74,6 +74,7 @@ internal enum Asset {
|
|||
internal static let lightSuccessGreen = ColorAsset(name: "Colors/lightSuccessGreen")
|
||||
internal static let lightWhite = ColorAsset(name: "Colors/lightWhite")
|
||||
internal static let plusCircleFill = ImageAsset(name: "Colors/plus.circle.fill")
|
||||
internal static let systemGreen = ColorAsset(name: "Colors/system.green")
|
||||
internal static let systemOrange = ColorAsset(name: "Colors/system.orange")
|
||||
}
|
||||
internal enum Welcome {
|
||||
|
|
|
@ -16,6 +16,10 @@ import ActiveLabel
|
|||
// MARK: - ActionToolbarContainerDelegate
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, boostButtonDidPressed sender: UIButton) {
|
||||
StatusProviderFacade.responseToStatusBoostAction(provider: self, cell: cell)
|
||||
}
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) {
|
||||
StatusProviderFacade.responseToStatusLikeAction(provider: self, cell: cell)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
// }
|
||||
|
||||
func handleTableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
// update poll when toot appear
|
||||
let now = Date()
|
||||
var pollID: Mastodon.Entity.Poll.ID?
|
||||
toot(for: cell, indexPath: indexPath)
|
||||
|
|
|
@ -16,6 +16,7 @@ import ActiveLabel
|
|||
enum StatusProviderFacade {
|
||||
|
||||
}
|
||||
|
||||
extension StatusProviderFacade {
|
||||
|
||||
static func responseToStatusLikeAction(provider: StatusProvider) {
|
||||
|
@ -56,10 +57,9 @@ extension StatusProviderFacade {
|
|||
|
||||
toot
|
||||
.compactMap { toot -> (NSManagedObjectID, Mastodon.API.Favorites.FavoriteKind)? in
|
||||
guard let toot = toot else { return nil }
|
||||
guard let toot = toot?.reblog ?? toot else { return nil }
|
||||
let favoriteKind: Mastodon.API.Favorites.FavoriteKind = {
|
||||
let targetToot = (toot.reblog ?? toot)
|
||||
let isLiked = targetToot.favouritedBy.flatMap { $0.contains(where: { $0.id == mastodonUserID }) } ?? false
|
||||
let isLiked = toot.favouritedBy.flatMap { $0.contains(where: { $0.id == mastodonUserID }) } ?? false
|
||||
return isLiked ? .destroy : .create
|
||||
}()
|
||||
return (toot.objectID, favoriteKind)
|
||||
|
@ -120,6 +120,110 @@ extension StatusProviderFacade {
|
|||
|
||||
}
|
||||
|
||||
extension StatusProviderFacade {
|
||||
|
||||
|
||||
static func responseToStatusBoostAction(provider: StatusProvider) {
|
||||
_responseToStatusBoostAction(
|
||||
provider: provider,
|
||||
toot: provider.toot()
|
||||
)
|
||||
}
|
||||
|
||||
static func responseToStatusBoostAction(provider: StatusProvider, cell: UITableViewCell) {
|
||||
_responseToStatusBoostAction(
|
||||
provider: provider,
|
||||
toot: provider.toot(for: cell, indexPath: nil)
|
||||
)
|
||||
}
|
||||
|
||||
private static func _responseToStatusBoostAction(provider: StatusProvider, toot: Future<Toot?, Never>) {
|
||||
// prepare authentication
|
||||
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
// prepare current user infos
|
||||
guard let _currentMastodonUser = provider.context.authenticationService.activeMastodonAuthentication.value?.user else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
let mastodonUserID = activeMastodonAuthenticationBox.userID
|
||||
assert(_currentMastodonUser.id == mastodonUserID)
|
||||
let mastodonUserObjectID = _currentMastodonUser.objectID
|
||||
|
||||
guard let context = provider.context else { return }
|
||||
|
||||
// haptic feedback generator
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
let responseFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
|
||||
|
||||
toot
|
||||
.compactMap { toot -> (NSManagedObjectID, Mastodon.API.Status.Reblog.BoostKind)? in
|
||||
guard let toot = toot?.reblog ?? toot else { return nil }
|
||||
let boostKind: Mastodon.API.Status.Reblog.BoostKind = {
|
||||
let isBoosted = toot.rebloggedBy.flatMap { $0.contains(where: { $0.id == mastodonUserID }) } ?? false
|
||||
return isBoosted ? .undoBoost : .boost
|
||||
}()
|
||||
return (toot.objectID, boostKind)
|
||||
}
|
||||
.map { tootObjectID, boostKind -> AnyPublisher<(Toot.ID, Mastodon.API.Status.Reblog.BoostKind), Error> in
|
||||
return context.apiService.boost(
|
||||
tootObjectID: tootObjectID,
|
||||
mastodonUserObjectID: mastodonUserObjectID,
|
||||
boostKind: boostKind
|
||||
)
|
||||
.map { tootID in (tootID, boostKind) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
.switchToLatest()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.handleEvents { _ in
|
||||
generator.prepare()
|
||||
responseFeedbackGenerator.prepare()
|
||||
} receiveOutput: { _, boostKind in
|
||||
generator.impactOccurred()
|
||||
os_log("%{public}s[%{public}ld], %{public}s: [Boost] update local toot reblog status to: %s", ((#file as NSString).lastPathComponent), #line, #function, boostKind == .boost ? "boost" : "unboost")
|
||||
} receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .failure:
|
||||
// TODO: handle error
|
||||
break
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
.map { tootID, boostKind in
|
||||
return context.apiService.boost(
|
||||
statusID: tootID,
|
||||
boostKind: boostKind,
|
||||
mastodonAuthenticationBox: activeMastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
.switchToLatest()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak provider] completion in
|
||||
guard let provider = provider else { return }
|
||||
if provider.view.window != nil {
|
||||
responseFeedbackGenerator.impactOccurred()
|
||||
}
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log("%{public}s[%{public}ld], %{public}s: [Boost] remote boost request fail: %{public}s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
case .finished:
|
||||
os_log("%{public}s[%{public}ld], %{public}s: [Boost] remote boost request success", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
} receiveValue: { response in
|
||||
// do nothing
|
||||
}
|
||||
.store(in: &provider.disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StatusProviderFacade {
|
||||
enum Target {
|
||||
case toot
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.604",
|
||||
"green" : "0.741",
|
||||
"red" : "0.475"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ protocol StatusTableViewCellDelegate: class {
|
|||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, boostButtonDidPressed sender: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
||||
|
@ -207,8 +208,8 @@ extension StatusTableViewCell: ActionToolbarContainerDelegate {
|
|||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) {
|
||||
|
||||
}
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, retootButtonDidPressed sender: UIButton) {
|
||||
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, boostButtonDidPressed sender: UIButton) {
|
||||
delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, boostButtonDidPressed: sender)
|
||||
}
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) {
|
||||
delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, likeButtonDidPressed: sender)
|
||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
|
||||
protocol ActionToolbarContainerDelegate: class {
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton)
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, retootButtonDidPressed sender: UIButton)
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, boostButtonDidPressed sender: UIButton)
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton)
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton)
|
||||
}
|
||||
|
@ -19,12 +19,16 @@ protocol ActionToolbarContainerDelegate: class {
|
|||
final class ActionToolbarContainer: UIView {
|
||||
|
||||
let replyButton = HitTestExpandedButton()
|
||||
let retootButton = HitTestExpandedButton()
|
||||
let starButton = HitTestExpandedButton()
|
||||
let boostButton = HitTestExpandedButton()
|
||||
let favoriteButton = HitTestExpandedButton()
|
||||
let moreButton = HitTestExpandedButton()
|
||||
|
||||
var isStarButtonHighlight: Bool = false {
|
||||
didSet { isStarButtonHighlightStateDidChange(to: isStarButtonHighlight) }
|
||||
var isBoostButtonHighlight: Bool = false {
|
||||
didSet { isBoostButtonHighlightStateDidChange(to: isBoostButtonHighlight) }
|
||||
}
|
||||
|
||||
var isFavoriteButtonHighlight: Bool = false {
|
||||
didSet { isFavoriteButtonHighlightStateDidChange(to: isFavoriteButtonHighlight) }
|
||||
}
|
||||
|
||||
weak var delegate: ActionToolbarContainerDelegate?
|
||||
|
@ -57,8 +61,8 @@ extension ActionToolbarContainer {
|
|||
])
|
||||
|
||||
replyButton.addTarget(self, action: #selector(ActionToolbarContainer.replyButtonDidPressed(_:)), for: .touchUpInside)
|
||||
retootButton.addTarget(self, action: #selector(ActionToolbarContainer.retootButtonDidPressed(_:)), for: .touchUpInside)
|
||||
starButton.addTarget(self, action: #selector(ActionToolbarContainer.starButtonDidPressed(_:)), for: .touchUpInside)
|
||||
boostButton.addTarget(self, action: #selector(ActionToolbarContainer.boostButtonDidPressed(_:)), for: .touchUpInside)
|
||||
favoriteButton.addTarget(self, action: #selector(ActionToolbarContainer.favoriteButtonDidPressed(_:)), for: .touchUpInside)
|
||||
moreButton.addTarget(self, action: #selector(ActionToolbarContainer.moreButtonDidPressed(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
|
@ -89,7 +93,7 @@ extension ActionToolbarContainer {
|
|||
subview.removeFromSuperview()
|
||||
}
|
||||
|
||||
let buttons = [replyButton, retootButton, starButton, moreButton]
|
||||
let buttons = [replyButton, boostButton, favoriteButton, moreButton]
|
||||
buttons.forEach { button in
|
||||
button.tintColor = Asset.Colors.Button.actionToolbar.color
|
||||
button.titleLabel?.font = .monospacedDigitSystemFont(ofSize: 12, weight: .regular)
|
||||
|
@ -109,28 +113,28 @@ extension ActionToolbarContainer {
|
|||
button.contentHorizontalAlignment = .leading
|
||||
}
|
||||
replyButton.setImage(replyImage, for: .normal)
|
||||
retootButton.setImage(reblogImage, for: .normal)
|
||||
starButton.setImage(starImage, for: .normal)
|
||||
boostButton.setImage(reblogImage, for: .normal)
|
||||
favoriteButton.setImage(starImage, for: .normal)
|
||||
moreButton.setImage(moreImage, for: .normal)
|
||||
|
||||
container.axis = .horizontal
|
||||
container.distribution = .fill
|
||||
|
||||
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
retootButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
starButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
boostButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
favoriteButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
moreButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addArrangedSubview(replyButton)
|
||||
container.addArrangedSubview(retootButton)
|
||||
container.addArrangedSubview(starButton)
|
||||
container.addArrangedSubview(boostButton)
|
||||
container.addArrangedSubview(favoriteButton)
|
||||
container.addArrangedSubview(moreButton)
|
||||
NSLayoutConstraint.activate([
|
||||
replyButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: retootButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: starButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: boostButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: favoriteButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: moreButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.widthAnchor.constraint(equalTo: retootButton.widthAnchor).priority(.defaultHigh),
|
||||
replyButton.widthAnchor.constraint(equalTo: starButton.widthAnchor).priority(.defaultHigh),
|
||||
replyButton.widthAnchor.constraint(equalTo: boostButton.widthAnchor).priority(.defaultHigh),
|
||||
replyButton.widthAnchor.constraint(equalTo: favoriteButton.widthAnchor).priority(.defaultHigh),
|
||||
])
|
||||
moreButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
moreButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
|
@ -140,16 +144,16 @@ extension ActionToolbarContainer {
|
|||
button.contentHorizontalAlignment = .center
|
||||
}
|
||||
replyButton.setImage(replyImage, for: .normal)
|
||||
retootButton.setImage(reblogImage, for: .normal)
|
||||
starButton.setImage(starImage, for: .normal)
|
||||
boostButton.setImage(reblogImage, for: .normal)
|
||||
favoriteButton.setImage(starImage, for: .normal)
|
||||
|
||||
container.axis = .horizontal
|
||||
container.spacing = 8
|
||||
container.distribution = .fillEqually
|
||||
|
||||
container.addArrangedSubview(replyButton)
|
||||
container.addArrangedSubview(retootButton)
|
||||
container.addArrangedSubview(starButton)
|
||||
container.addArrangedSubview(boostButton)
|
||||
container.addArrangedSubview(favoriteButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,11 +162,18 @@ extension ActionToolbarContainer {
|
|||
return oldStyle != style
|
||||
}
|
||||
|
||||
private func isStarButtonHighlightStateDidChange(to isHighlight: Bool) {
|
||||
private func isBoostButtonHighlightStateDidChange(to isHighlight: Bool) {
|
||||
let tintColor = isHighlight ? Asset.Colors.systemGreen.color : Asset.Colors.Button.actionToolbar.color
|
||||
boostButton.tintColor = tintColor
|
||||
boostButton.setTitleColor(tintColor, for: .normal)
|
||||
boostButton.setTitleColor(tintColor, for: .highlighted)
|
||||
}
|
||||
|
||||
private func isFavoriteButtonHighlightStateDidChange(to isHighlight: Bool) {
|
||||
let tintColor = isHighlight ? Asset.Colors.systemOrange.color : Asset.Colors.Button.actionToolbar.color
|
||||
starButton.tintColor = tintColor
|
||||
starButton.setTitleColor(tintColor, for: .normal)
|
||||
starButton.setTitleColor(tintColor, for: .highlighted)
|
||||
favoriteButton.tintColor = tintColor
|
||||
favoriteButton.setTitleColor(tintColor, for: .normal)
|
||||
favoriteButton.setTitleColor(tintColor, for: .highlighted)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,12 +184,12 @@ extension ActionToolbarContainer {
|
|||
delegate?.actionToolbarContainer(self, replayButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
@objc private func retootButtonDidPressed(_ sender: UIButton) {
|
||||
@objc private func boostButtonDidPressed(_ sender: UIButton) {
|
||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.actionToolbarContainer(self, retootButtonDidPressed: sender)
|
||||
delegate?.actionToolbarContainer(self, boostButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
@objc private func starButtonDidPressed(_ sender: UIButton) {
|
||||
@objc private func favoriteButtonDidPressed(_ sender: UIButton) {
|
||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.actionToolbarContainer(self, starButtonDidPressed: sender)
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ extension APIService {
|
|||
}()
|
||||
let _oldToot: Toot? = {
|
||||
let request = Toot.sortedFetchRequest
|
||||
request.predicate = Toot.predicate(domain: mastodonAuthenticationBox.domain, id: entity.id)
|
||||
request.predicate = Toot.predicate(domain: mastodonAuthenticationBox.domain, id: statusID)
|
||||
request.returnsObjectsAsFaults = false
|
||||
request.relationshipKeyPathsForPrefetching = [#keyPath(Toot.reblog)]
|
||||
do {
|
||||
|
@ -112,7 +112,8 @@ extension APIService {
|
|||
.handleEvents(receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: error:", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
debugPrint(error)
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
//
|
||||
// APIService+Reblog.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import CommonOSLog
|
||||
|
||||
extension APIService {
|
||||
|
||||
// make local state change only
|
||||
func boost(
|
||||
tootObjectID: NSManagedObjectID,
|
||||
mastodonUserObjectID: NSManagedObjectID,
|
||||
boostKind: Mastodon.API.Status.Reblog.BoostKind
|
||||
) -> AnyPublisher<Toot.ID, Error> {
|
||||
var _targetTootID: Toot.ID?
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
return managedObjectContext.performChanges {
|
||||
let toot = managedObjectContext.object(with: tootObjectID) as! Toot
|
||||
let mastodonUser = managedObjectContext.object(with: mastodonUserObjectID) as! MastodonUser
|
||||
let targetToot = toot.reblog ?? toot
|
||||
let targetTootID = targetToot.id
|
||||
_targetTootID = targetTootID
|
||||
|
||||
targetToot.update(reblogged: boostKind == .boost, mastodonUser: mastodonUser)
|
||||
|
||||
}
|
||||
.tryMap { result in
|
||||
switch result {
|
||||
case .success:
|
||||
guard let targetTootID = _targetTootID else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
return targetTootID
|
||||
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// send boost request to remote
|
||||
func boost(
|
||||
statusID: Mastodon.Entity.Status.ID,
|
||||
boostKind: Mastodon.API.Status.Reblog.BoostKind,
|
||||
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||
let domain = mastodonAuthenticationBox.domain
|
||||
let authorization = mastodonAuthenticationBox.userAuthorization
|
||||
let requestMastodonUserID = mastodonAuthenticationBox.userID
|
||||
return Mastodon.API.Status.Reblog.boost(
|
||||
session: session,
|
||||
domain: domain,
|
||||
statusID: statusID,
|
||||
boostKind: boostKind,
|
||||
authorization: authorization
|
||||
)
|
||||
.map { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> in
|
||||
let log = OSLog.api
|
||||
let entity = response.value
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
|
||||
return managedObjectContext.performChanges {
|
||||
let _requestMastodonUser: MastodonUser? = {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: mastodonAuthenticationBox.domain, id: requestMastodonUserID)
|
||||
request.fetchLimit = 1
|
||||
request.returnsObjectsAsFaults = false
|
||||
do {
|
||||
return try managedObjectContext.fetch(request).first
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
let _oldToot: Toot? = {
|
||||
let request = Toot.sortedFetchRequest
|
||||
request.predicate = Toot.predicate(domain: domain, id: statusID)
|
||||
request.returnsObjectsAsFaults = false
|
||||
request.relationshipKeyPathsForPrefetching = [#keyPath(Toot.reblog)]
|
||||
do {
|
||||
return try managedObjectContext.fetch(request).first
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
guard let requestMastodonUser = _requestMastodonUser,
|
||||
let oldToot = _oldToot else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
APIService.CoreData.merge(toot: oldToot, entity: entity.reblog ?? entity, requestMastodonUser: requestMastodonUser, domain: mastodonAuthenticationBox.domain, networkDate: response.networkDate)
|
||||
os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: did update toot %{public}s reblog status to: %{public}s. now %ld boosts", ((#file as NSString).lastPathComponent), #line, #function, entity.id, entity.reblogged.flatMap { $0 ? "boost" : "unboost" } ?? "<nil>", entity.reblogsCount )
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Status> in
|
||||
switch result {
|
||||
case .success:
|
||||
return response
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.switchToLatest()
|
||||
.handleEvents(receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: error:", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
debugPrint(error)
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
})
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension APIService {
|
||||
// func likeList(
|
||||
// limit: Int = onceRequestTootMaxCount,
|
||||
// userID: String,
|
||||
// maxID: String? = nil,
|
||||
// mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
|
||||
// ) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Status]>, Error> {
|
||||
//
|
||||
// let requestMastodonUserID = mastodonAuthenticationBox.userID
|
||||
// let query = Mastodon.API.Favorites.ListQuery(limit: limit, minID: nil, maxID: maxID)
|
||||
// return Mastodon.API.Favorites.favoritedStatus(domain: mastodonAuthenticationBox.domain, session: session, authorization: mastodonAuthenticationBox.userAuthorization, query: query)
|
||||
// .map { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Status]>, Error> in
|
||||
// let log = OSLog.api
|
||||
//
|
||||
// return APIService.Persist.persistTimeline(
|
||||
// managedObjectContext: self.backgroundManagedObjectContext,
|
||||
// domain: mastodonAuthenticationBox.domain,
|
||||
// query: query,
|
||||
// response: response,
|
||||
// persistType: .likeList,
|
||||
// requestMastodonUserID: requestMastodonUserID,
|
||||
// log: log
|
||||
// )
|
||||
// .setFailureType(to: Error.self)
|
||||
// .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Status]> in
|
||||
// switch result {
|
||||
// case .success:
|
||||
// return response
|
||||
// case .failure(let error):
|
||||
// throw error
|
||||
// }
|
||||
// }
|
||||
// .eraseToAnyPublisher()
|
||||
// }
|
||||
// .switchToLatest()
|
||||
// .eraseToAnyPublisher()
|
||||
// }
|
||||
}
|
|
@ -114,14 +114,14 @@ extension Mastodon.API.Favorites {
|
|||
|
||||
}
|
||||
|
||||
public extension Mastodon.API.Favorites {
|
||||
extension Mastodon.API.Favorites {
|
||||
|
||||
enum FavoriteKind {
|
||||
public enum FavoriteKind {
|
||||
case create
|
||||
case destroy
|
||||
}
|
||||
|
||||
struct ListQuery: GetQuery,TimelineQueryType {
|
||||
public struct ListQuery: GetQuery,TimelineQueryType {
|
||||
|
||||
public var limit: Int?
|
||||
public var minID: String?
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
//
|
||||
// Mastodon+API+Status+Reblog.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
extension Mastodon.API.Status.Reblog {
|
||||
|
||||
static func boostedByEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||
let pathComponent = "statuses/" + statusID + "/reblogged_by"
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
/// Boosted by
|
||||
///
|
||||
/// View who boosted a given status.
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/9
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - statusID: id for status
|
||||
/// - authorization: User token. Could be nil if status is public
|
||||
/// - Returns: `AnyPublisher` contains `Status` nested in the response
|
||||
public static func poll(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
statusID: Mastodon.Entity.Status.ID,
|
||||
authorization: Mastodon.API.OAuth.Authorization?
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: boostedByEndpointURL(domain: domain, statusID: statusID),
|
||||
query: nil,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.Status.Reblog {
|
||||
|
||||
static func reblogEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||
let pathComponent = "statuses/" + statusID + "/reblog"
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
/// Boost
|
||||
///
|
||||
/// Reshare a status.
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/9
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - statusID: id for status
|
||||
/// - authorization: User token.
|
||||
/// - Returns: `AnyPublisher` contains `Status` nested in the response
|
||||
public static func boost(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
statusID: Mastodon.Entity.Status.ID,
|
||||
query: BoostQuery,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||
let request = Mastodon.API.post(
|
||||
url: reblogEndpointURL(domain: domain, statusID: statusID),
|
||||
query: nil,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public typealias Visibility = Mastodon.Entity.Source.Privacy
|
||||
|
||||
public struct BoostQuery: Codable, PostQuery {
|
||||
public let visibility: Visibility
|
||||
|
||||
public init(visibility: Visibility) {
|
||||
self.visibility = visibility
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.Status.Reblog {
|
||||
|
||||
static func unreblogEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||
let pathComponent = "statuses/" + statusID + "/unreblog"
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
/// Undo boost
|
||||
///
|
||||
/// Undo a reshare of a status.
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/9
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - statusID: id for status
|
||||
/// - authorization: User token.
|
||||
/// - Returns: `AnyPublisher` contains `Status` nested in the response
|
||||
public static func undoBoost(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
statusID: Mastodon.Entity.Status.ID,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||
let request = Mastodon.API.post(
|
||||
url: unreblogEndpointURL(domain: domain, statusID: statusID),
|
||||
query: nil,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.Status.Reblog {
|
||||
|
||||
public enum BoostKind {
|
||||
case boost
|
||||
case undoBoost
|
||||
}
|
||||
|
||||
public static func boost(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
statusID: Mastodon.Entity.Status.ID,
|
||||
boostKind: BoostKind,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||
let url: URL
|
||||
switch boostKind {
|
||||
case .boost: url = reblogEndpointURL(domain: domain, statusID: statusID)
|
||||
case .undoBoost: url = unreblogEndpointURL(domain: domain, statusID: statusID)
|
||||
}
|
||||
let request = Mastodon.API.post(
|
||||
url: url,
|
||||
query: nil,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// Mastodon+API+Status.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Mastodon.API.Status {
|
||||
public enum Reblog { }
|
||||
}
|
|
@ -95,6 +95,7 @@ extension Mastodon.API {
|
|||
public enum OAuth { }
|
||||
public enum Onboarding { }
|
||||
public enum Polls { }
|
||||
public enum Status { }
|
||||
public enum Timeline { }
|
||||
public enum Favorites { }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue