Merge pull request #115 from tootsuite/feature/followrequest

Feature/followrequest
This commit is contained in:
sxiaojian88 2021-04-29 17:08:06 +08:00 committed by GitHub
commit e94432f615
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 329 additions and 28 deletions

View File

@ -349,7 +349,8 @@
"favourite": "favorited your post",
"reblog": "rebloged your post",
"poll": "Your poll has ended",
"mention": "mentioned you"
"mention": "mentioned you",
"follow_request": "request to follow you"
}
},
"thread": {

View File

@ -110,6 +110,7 @@
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */; };
2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift */; };
2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D84350425FF858100EECE90 /* UIScrollView.swift */; };
2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8FCA072637EABB00137F46 /* APIService+FollowRequest.swift */; };
2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0125C7E4F2004F19B8 /* Mention.swift */; };
2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0725C7E9A8004F19B8 /* Tag.swift */; };
2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; };
@ -645,6 +646,7 @@
2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarTitleViewModel.swift; sourceTree = "<group>"; };
2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarTitleView.swift; sourceTree = "<group>"; };
2D84350425FF858100EECE90 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = "<group>"; };
2D8FCA072637EABB00137F46 /* APIService+FollowRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+FollowRequest.swift"; sourceTree = "<group>"; };
2D927F0125C7E4F2004F19B8 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
2D927F0725C7E9A8004F19B8 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
2D927F0D25C7E9C9004F19B8 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
@ -1694,6 +1696,7 @@
DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */,
5B24BBE1262DB19100A9381B /* APIService+Report.swift */,
DBAE3F932616E28B004B8251 /* APIService+Follow.swift */,
2D8FCA072637EABB00137F46 /* APIService+FollowRequest.swift */,
DBAE3F8D2616E0B1004B8251 /* APIService+Block.swift */,
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */,
5B90C48426259BF10002E742 /* APIService+Subscriptions.swift */,
@ -2787,6 +2790,7 @@
DBA0A10925FB3C2B0079C110 /* RoundedEdgesButton.swift in Sources */,
DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */,
DB71FD4625F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift in Sources */,
2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */,
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
2DE0FAC82615F5F000CDF649 /* SearchRecommendAccountsCollectionViewCell.swift in Sources */,
2DFAD5272616F9D300F9EE7C /* SearchViewController+Searching.swift in Sources */,

View File

@ -69,7 +69,7 @@
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
"state": {
"branch": null,
"revision": "bbc4bc4def7eb05a7ba8e1219f80ee9be327334e",
"revision": "15d199e84677303a7004ed2c5ecaa1a90f3863f8",
"version": "6.2.1"
}
},

View File

@ -91,6 +91,18 @@ extension NotificationSection {
cell.actionLabel.text = actionText + " · " + timeText
}
.store(in: &cell.disposeBag)
cell.acceptButton.publisher(for: .touchUpInside)
.sink { [weak cell] _ in
guard let cell = cell else { return }
cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton)
}
.store(in: &cell.disposeBag)
cell.rejectButton.publisher(for: .touchUpInside)
.sink { [weak cell] _ in
guard let cell = cell else { return }
cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton)
}
.store(in: &cell.disposeBag)
cell.actionImageBackground.backgroundColor = color
cell.actionLabel.text = actionText + " · " + timeText
cell.nameLabel.text = notification.account.displayName.isEmpty ? notification.account.username : notification.account.displayName
@ -108,6 +120,7 @@ extension NotificationSection {
if let actionImage = UIImage(systemName: actionImageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold))?.withRenderingMode(.alwaysTemplate) {
cell.actionImageView.image = actionImage
}
cell.buttonStackView.isHidden = (type != .followRequest)
return cell
}
case .bottomLoader:

View File

@ -24,6 +24,8 @@ extension Mastodon.Entity.Notification.NotificationType {
color = Asset.Colors.Notification.mention.color
case .poll:
color = Asset.Colors.brandBlue.color
case .followRequest:
color = Asset.Colors.brandBlue.color
default:
color = .clear
}
@ -45,6 +47,8 @@ extension Mastodon.Entity.Notification.NotificationType {
actionText = L10n.Scene.Notification.Action.mention
case .poll:
actionText = L10n.Scene.Notification.Action.poll
case .followRequest:
actionText = L10n.Scene.Notification.Action.followRequest
default:
actionText = ""
}
@ -66,6 +70,8 @@ extension Mastodon.Entity.Notification.NotificationType {
actionImageName = "at"
case .poll:
actionImageName = "list.bullet"
case .followRequest:
actionImageName = "person.crop.circle"
default:
actionImageName = ""
}

View File

@ -377,6 +377,8 @@ internal enum L10n {
internal static let favourite = L10n.tr("Localizable", "Scene.Notification.Action.Favourite")
/// followed you
internal static let follow = L10n.tr("Localizable", "Scene.Notification.Action.Follow")
/// request to follow you
internal static let followRequest = L10n.tr("Localizable", "Scene.Notification.Action.FollowRequest")
/// mentioned you
internal static let mention = L10n.tr("Localizable", "Scene.Notification.Action.Mention")
/// Your poll has ended

View File

@ -44,8 +44,7 @@ extension UserProviderFacade {
return context.apiService.toggleFollow(
for: mastodonUser,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
needFeedback: true
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox
)
}
.switchToLatest()

View File

@ -127,6 +127,7 @@ tap the link to confirm your account.";
"Scene.HomeTimeline.Title" = "Home";
"Scene.Notification.Action.Favourite" = "favorited your post";
"Scene.Notification.Action.Follow" = "followed you";
"Scene.Notification.Action.FollowRequest" = "request to follow you";
"Scene.Notification.Action.Mention" = "mentioned you";
"Scene.Notification.Action.Poll" = "Your poll has ended";
"Scene.Notification.Action.Reblog" = "rebloged your post";

View File

@ -210,6 +210,14 @@ extension NotificationViewController: ContentOffsetAdjustableTimelineViewControl
// MARK: - NotificationTableViewCellDelegate
extension NotificationViewController: NotificationTableViewCellDelegate {
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) {
viewModel.acceptFollowRequest(notification: notification)
}
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) {
viewModel.rejectFollowRequest(notification: notification)
}
func userAvatarDidPressed(notification: MastodonNotification) {
let viewModel = ProfileViewModel(context: context, optionalMastodonUser: notification.account)
DispatchQueue.main.async {

View File

@ -53,7 +53,7 @@ extension NotificationViewModel.LoadLatestState {
sinceID: nil,
minID: nil,
limit: nil,
excludeTypes: [.followRequest],
excludeTypes: [],
accountID: nil
)
viewModel.context.apiService.allNotifications(

View File

@ -12,6 +12,7 @@ import Foundation
import GameplayKit
import MastodonSDK
import UIKit
import OSLog
final class NotificationViewModel: NSObject {
var disposeBag = Set<AnyCancellable>()
@ -120,6 +121,38 @@ final class NotificationViewModel: NSObject {
}
.store(in: &disposeBag)
}
func acceptFollowRequest(notification: MastodonNotification) {
guard let activeMastodonAuthenticationBox = self.activeMastodonAuthenticationBox.value else { return }
context.apiService.acceptFollowRequest(mastodonUserID: notification.account.id, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
.sink { [weak self] completion in
switch completion {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: accept FollowRequest fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
case .finished:
self?.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self)
}
} receiveValue: { _ in
}
.store(in: &disposeBag)
}
func rejectFollowRequest(notification: MastodonNotification) {
guard let activeMastodonAuthenticationBox = self.activeMastodonAuthenticationBox.value else { return }
context.apiService.rejectFollowRequest(mastodonUserID: notification.account.id, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
.sink { [weak self] completion in
switch completion {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: reject FollowRequest fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
case .finished:
self?.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self)
}
} receiveValue: { _ in
}
.store(in: &disposeBag)
}
}
extension NotificationViewModel {

View File

@ -21,6 +21,10 @@ protocol NotificationTableViewCellDelegate: AnyObject {
func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton)
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton)
}
final class NotificationTableViewCell: UITableViewCell {
@ -76,6 +80,24 @@ final class NotificationTableViewCell: UITableViewCell {
return label
}()
let acceptButton: UIButton = {
let button = UIButton(type: .custom)
let actionImage = UIImage(systemName: "checkmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
button.setImage(actionImage, for: .normal)
button.tintColor = Asset.Colors.Label.secondary.color
return button
}()
let rejectButton: UIButton = {
let button = UIButton(type: .custom)
let actionImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
button.setImage(actionImage, for: .normal)
button.tintColor = Asset.Colors.Label.secondary.color
return button
}()
let buttonStackView = UIStackView()
override func prepareForReuse() {
super.prepareForReuse()
avatatImageView.af.cancelImageRequest()
@ -97,9 +119,8 @@ extension NotificationTableViewCell {
func configure() {
let containerStackView = UIStackView()
containerStackView.axis = .horizontal
containerStackView.alignment = .center
containerStackView.spacing = 4
containerStackView.axis = .vertical
containerStackView.alignment = .fill
containerStackView.layoutMargins = UIEdgeInsets(top: 14, left: 0, bottom: 12, right: 0)
containerStackView.isLayoutMarginsRelativeArrangement = true
containerStackView.translatesAutoresizingMaskIntoConstraints = false
@ -110,8 +131,13 @@ extension NotificationTableViewCell {
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
])
let horizontalStackView = UIStackView()
horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.axis = .horizontal
horizontalStackView.spacing = 6
containerStackView.addArrangedSubview(avatarContainer)
horizontalStackView.addArrangedSubview(avatarContainer)
avatarContainer.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
avatarContainer.heightAnchor.constraint(equalToConstant: 47).priority(.required - 1),
@ -144,13 +170,23 @@ extension NotificationTableViewCell {
])
nameLabel.translatesAutoresizingMaskIntoConstraints = false
containerStackView.addArrangedSubview(nameLabel)
horizontalStackView.addArrangedSubview(nameLabel)
actionLabel.translatesAutoresizingMaskIntoConstraints = false
containerStackView.addArrangedSubview(actionLabel)
nameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
nameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
horizontalStackView.addArrangedSubview(actionLabel)
nameLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
nameLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
actionLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
containerStackView.addArrangedSubview(horizontalStackView)
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
acceptButton.translatesAutoresizingMaskIntoConstraints = false
rejectButton.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.addArrangedSubview(acceptButton)
buttonStackView.addArrangedSubview(rejectButton)
containerStackView.addArrangedSubview(buttonStackView)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

View File

@ -188,8 +188,7 @@ final class SuggestionAccountViewModel: NSObject {
let mastodonUser = context.managedObjectContext.object(with: objectID) as! MastodonUser
return context.apiService.toggleFollow(
for: mastodonUser,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
needFeedback: false
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox
)
}

View File

@ -24,15 +24,12 @@ extension APIService {
/// - Returns: publisher for `Relationship`
func toggleFollow(
for mastodonUser: MastodonUser,
activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox,
needFeedback: Bool
activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
var impactFeedbackGenerator: UIImpactFeedbackGenerator?
var notificationFeedbackGenerator: UINotificationFeedbackGenerator?
if needFeedback {
impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
notificationFeedbackGenerator = UINotificationFeedbackGenerator()
}
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
return followUpdateLocal(
mastodonUserObjectID: mastodonUser.objectID,
@ -40,9 +37,9 @@ extension APIService {
)
.receive(on: DispatchQueue.main)
.handleEvents { _ in
impactFeedbackGenerator?.prepare()
impactFeedbackGenerator.prepare()
} receiveOutput: { _ in
impactFeedbackGenerator?.impactOccurred()
impactFeedbackGenerator.impactOccurred()
} receiveCompletion: { completion in
switch completion {
case .failure(let error):
@ -79,13 +76,13 @@ extension APIService {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Friendship] rollback finish", ((#file as NSString).lastPathComponent), #line, #function)
} receiveValue: { _ in
// do nothing
notificationFeedbackGenerator?.prepare()
notificationFeedbackGenerator?.notificationOccurred(.error)
notificationFeedbackGenerator.prepare()
notificationFeedbackGenerator.notificationOccurred(.error)
}
.store(in: &self.disposeBag)
case .finished:
notificationFeedbackGenerator?.notificationOccurred(.success)
notificationFeedbackGenerator.notificationOccurred(.success)
os_log("%{public}s[%{public}ld], %{public}s: [Friendship] remote friendship update success", ((#file as NSString).lastPathComponent), #line, #function)
}
})

View File

@ -0,0 +1,105 @@
//
// APIService+FollowRequest.swift
// Mastodon
//
// Created by sxiaojian on 2021/4/27.
//
import Foundation
import UIKit
import Combine
import CoreData
import CoreDataStack
import CommonOSLog
import MastodonSDK
extension APIService {
func acceptFollowRequest(
mastodonUserID: MastodonUser.ID,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
let domain = mastodonAuthenticationBox.domain
let authorization = mastodonAuthenticationBox.userAuthorization
let requestMastodonUserID = mastodonAuthenticationBox.userID
return Mastodon.API.Account.acceptFollowRequest(
session: session,
domain: domain,
userID: mastodonUserID,
authorization: authorization)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> in
let managedObjectContext = self.backgroundManagedObjectContext
return managedObjectContext.performChanges {
let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
requestMastodonUserRequest.fetchLimit = 1
guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
let lookUpMastodonUserRequest = MastodonUser.sortedFetchRequest
lookUpMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: mastodonUserID)
lookUpMastodonUserRequest.fetchLimit = 1
let lookUpMastodonuser = managedObjectContext.safeFetch(lookUpMastodonUserRequest).first
if let lookUpMastodonuser = lookUpMastodonuser {
let entity = response.value
APIService.CoreData.update(user: lookUpMastodonuser, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
}
}
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Relationship> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
func rejectFollowRequest(
mastodonUserID: MastodonUser.ID,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
let domain = mastodonAuthenticationBox.domain
let authorization = mastodonAuthenticationBox.userAuthorization
let requestMastodonUserID = mastodonAuthenticationBox.userID
return Mastodon.API.Account.rejectFollowRequest(
session: session,
domain: domain,
userID: mastodonUserID,
authorization: authorization)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> in
let managedObjectContext = self.backgroundManagedObjectContext
return managedObjectContext.performChanges {
let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
requestMastodonUserRequest.fetchLimit = 1
guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
let lookUpMastodonUserRequest = MastodonUser.sortedFetchRequest
lookUpMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: mastodonUserID)
lookUpMastodonUserRequest.fetchLimit = 1
let lookUpMastodonuser = managedObjectContext.safeFetch(lookUpMastodonUserRequest).first
if let lookUpMastodonuser = lookUpMastodonuser {
let entity = response.value
APIService.CoreData.update(user: lookUpMastodonuser, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
}
}
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Relationship> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}

View File

@ -29,6 +29,14 @@ extension APIService {
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Notification]>, Error> in
let log = OSLog.api
return self.backgroundManagedObjectContext.performChanges {
if query.maxID == nil {
let requestMastodonNotificationRequest = MastodonNotification.sortedFetchRequest
requestMastodonNotificationRequest.predicate = MastodonNotification.predicate(domain: domain, userID: userID)
let oldNotifications = self.backgroundManagedObjectContext.safeFetch(requestMastodonNotificationRequest)
oldNotifications.forEach { notification in
self.backgroundManagedObjectContext.delete(notification)
}
}
response.value.forEach { notification in
let (mastodonUser, _) = APIService.CoreData.createOrMergeMastodonUser(into: self.backgroundManagedObjectContext, for: nil, in: domain, entity: notification.account, userCache: nil, networkDate: Date(), log: log)
var status: Status?

View File

@ -0,0 +1,89 @@
//
// Mastodon+API+Account+FollowRequest.swift
//
//
// Created by sxiaojian on 2021/4/27.
//
import Foundation
import Combine
// MARK: - Account credentials
extension Mastodon.API.Account {
static func acceptFollowRequestEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("follow_requests")
.appendingPathComponent(userID)
.appendingPathComponent("authorize")
}
static func rejectFollowRequestEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("follow_requests")
.appendingPathComponent(userID)
.appendingPathComponent("reject")
}
/// Accept Follow
///
///
/// - Since: 0.0.0
/// - Version: 3.3.0
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/follow_requests/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - userID: ID of the account in the database
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `Relationship` nested in the response
public static func acceptFollowRequest(
session: URLSession,
domain: String,
userID: Mastodon.Entity.Account.ID,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
let request = Mastodon.API.post(
url: acceptFollowRequestEndpointURL(domain: domain, userID: userID),
query: nil,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Relationship.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
/// Reject Follow
///
///
/// - Since: 0.0.0
/// - Version: 3.3.0
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/follow_requests/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - userID: ID of the account in the database
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `Relationship` nested in the response
public static func rejectFollowRequest(
session: URLSession,
domain: String,
userID: Mastodon.Entity.Account.ID,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
let request = Mastodon.API.post(
url: rejectFollowRequestEndpointURL(domain: domain, userID: userID),
query: nil,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Relationship.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
}