feat: [WIP] make the vote poll logic works
This commit is contained in:
parent
028f3a9404
commit
06aac878c8
|
@ -62,7 +62,7 @@ extension PollOption {
|
|||
self.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).add(by)
|
||||
}
|
||||
} else {
|
||||
if !(self.votedBy ?? Set()).contains(by) {
|
||||
if (self.votedBy ?? Set()).contains(by) {
|
||||
self.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).remove(by)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,20 @@
|
|||
"ERR_INCLUSION": "is not a supported value"
|
||||
},
|
||||
"alerts": {
|
||||
"common": {
|
||||
"please_try_again": "Please try again.",
|
||||
"please_try_again_later": "Please try again later."
|
||||
},
|
||||
"sign_up_failure": {
|
||||
"title": "Sign Up Failure"
|
||||
},
|
||||
"server_error": {
|
||||
"title": "Server Error"
|
||||
},
|
||||
"vote_failure": {
|
||||
"title": "Vote Failure",
|
||||
"poll_expired": "The poll has expired"
|
||||
}
|
||||
|
||||
},
|
||||
"controls": {
|
||||
"actions": {
|
||||
|
@ -125,7 +132,7 @@
|
|||
"prompt_eight_characters": "Eight characters"
|
||||
},
|
||||
"invite": {
|
||||
"registration_user_invite_request": "Why do you want to join?"
|
||||
"registration_user_invite_request": "Why do you want to join?"
|
||||
}
|
||||
},
|
||||
"success": "Success",
|
||||
|
@ -165,4 +172,4 @@
|
|||
"title": "Public"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -284,13 +284,13 @@ extension StatusSection {
|
|||
.map { option -> PollItem in
|
||||
let attribute: PollItem.Attribute = {
|
||||
let selectState: PollItem.Attribute.SelectState = {
|
||||
if isPollVoted {
|
||||
guard !votedOptions.isEmpty else {
|
||||
return .none
|
||||
}
|
||||
// make isPollVoted check later to make only local change possible
|
||||
if !votedOptions.isEmpty {
|
||||
return votedOptions.contains(option) ? .on : .off
|
||||
} else if poll.expired {
|
||||
return .none
|
||||
} else if isPollVoted, votedOptions.isEmpty {
|
||||
return .none
|
||||
} else {
|
||||
return .off
|
||||
}
|
||||
|
@ -302,6 +302,8 @@ extension StatusSection {
|
|||
return Double(option.votesCount?.intValue ?? 0) / Double(poll.votesCount.intValue)
|
||||
}()
|
||||
let voted = votedOptions.isEmpty ? true : votedOptions.contains(option)
|
||||
// let voted = true
|
||||
// let percentage: Double = Double.random(in: 0..<1)
|
||||
return .reveal(voted: voted, percentage: percentage)
|
||||
}()
|
||||
return PollItem.Attribute(selectState: selectState, voteState: voteState)
|
||||
|
|
|
@ -13,6 +13,12 @@ internal enum L10n {
|
|||
|
||||
internal enum Common {
|
||||
internal enum Alerts {
|
||||
internal enum Common {
|
||||
/// Please try again.
|
||||
internal static let pleaseTryAgain = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgain")
|
||||
/// Please try again later.
|
||||
internal static let pleaseTryAgainLater = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgainLater")
|
||||
}
|
||||
internal enum ServerError {
|
||||
/// Server Error
|
||||
internal static let title = L10n.tr("Localizable", "Common.Alerts.ServerError.Title")
|
||||
|
@ -21,6 +27,12 @@ internal enum L10n {
|
|||
/// Sign Up Failure
|
||||
internal static let title = L10n.tr("Localizable", "Common.Alerts.SignUpFailure.Title")
|
||||
}
|
||||
internal enum VoteFailure {
|
||||
/// The poll has expired
|
||||
internal static let pollExpired = L10n.tr("Localizable", "Common.Alerts.VoteFailure.PollExpired")
|
||||
/// Vote Failure
|
||||
internal static let title = L10n.tr("Localizable", "Common.Alerts.VoteFailure.Title")
|
||||
}
|
||||
}
|
||||
internal enum Controls {
|
||||
internal enum Actions {
|
||||
|
|
|
@ -39,6 +39,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - MosciaImageViewContainerDelegate
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) {
|
||||
|
@ -69,3 +70,37 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - PollTableView
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, pollTableView: PollTableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let activeMastodonAuthentication = context.authenticationService.activeMastodonAuthentication.value else { return }
|
||||
|
||||
guard let diffableDataSource = cell.statusView.pollTableViewDataSource else { return }
|
||||
let item = diffableDataSource.itemIdentifier(for: indexPath)
|
||||
guard case let .opion(objectID, attribute) = item else { return }
|
||||
guard let option = managedObjectContext.object(with: objectID) as? PollOption else { return }
|
||||
|
||||
|
||||
if option.poll.multiple {
|
||||
var choices: [Int] = []
|
||||
|
||||
} else {
|
||||
context.apiService.vote(
|
||||
pollObjectID: option.poll.objectID,
|
||||
mastodonUserObjectID: activeMastodonAuthentication.user.objectID,
|
||||
choices: [option.index.intValue]
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { completion in
|
||||
|
||||
} receiveValue: { pollID in
|
||||
|
||||
}
|
||||
.store(in: &context.disposeBag)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,7 +33,12 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
return nil
|
||||
}
|
||||
let timeIntervalSinceUpdate = now.timeIntervalSince(poll.updatedAt)
|
||||
guard timeIntervalSinceUpdate > 60 else {
|
||||
#if DEBUG
|
||||
let autoRefreshTimeInterval: TimeInterval = 3 // speedup testing
|
||||
#else
|
||||
let autoRefreshTimeInterval: TimeInterval = 60
|
||||
#endif
|
||||
guard timeIntervalSinceUpdate > autoRefreshTimeInterval else {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: poll %s updated in the %.2fs. Skip for update", ((#file as NSString).lastPathComponent), #line, #function, poll.id, timeIntervalSinceUpdate)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
"Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
|
||||
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
|
||||
"Common.Alerts.ServerError.Title" = "Server Error";
|
||||
"Common.Alerts.SignUpFailure.Title" = "Sign Up Failure";
|
||||
"Common.Alerts.VoteFailure.PollExpired" = "The poll has expired";
|
||||
"Common.Alerts.VoteFailure.Title" = "Vote Failure";
|
||||
"Common.Controls.Actions.Add" = "Add";
|
||||
"Common.Controls.Actions.Back" = "Back";
|
||||
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||
|
|
|
@ -51,7 +51,9 @@ extension VoteProgressStripView {
|
|||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] progress in
|
||||
guard let self = self else { return }
|
||||
self.updateLayerPath()
|
||||
UIView.animate(withDuration: 0.33) {
|
||||
self.updateLayerPath()
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
|
|
@ -13,11 +13,15 @@ import CoreData
|
|||
import CoreDataStack
|
||||
|
||||
protocol StatusTableViewCellDelegate: class {
|
||||
var context: AppContext! { get}
|
||||
var managedObjectContext: NSManagedObjectContext { get }
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int)
|
||||
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, likeButtonDidPressed sender: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, pollTableView: PollTableView, didSelectRowAt indexPath: IndexPath)
|
||||
|
||||
}
|
||||
|
||||
final class StatusTableViewCell: UITableViewCell {
|
||||
|
@ -110,6 +114,44 @@ extension StatusTableViewCell: UITableViewDelegate {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if tableView === statusView.pollTableView, let diffableDataSource = statusView.pollTableViewDataSource {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||
|
||||
guard let context = delegate?.context else { return nil }
|
||||
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return nil }
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath),
|
||||
case let .opion(objectID, _) = item,
|
||||
let option = delegate?.managedObjectContext.object(with: objectID) as? PollOption else {
|
||||
return nil
|
||||
}
|
||||
let poll = option.poll
|
||||
|
||||
// disallow select when: poll expired OR user voted remote OR user voted local
|
||||
let userID = activeMastodonAuthenticationBox.userID
|
||||
let didVotedRemote = (option.poll.votedBy ?? Set()).contains(where: { $0.id == userID })
|
||||
let votedOptions = poll.options.filter { option in
|
||||
(option.votedBy ?? Set()).map { $0.id }.contains(userID)
|
||||
}
|
||||
let didVotedLocal = !votedOptions.isEmpty
|
||||
guard !option.poll.expired, !didVotedRemote, !didVotedLocal else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return indexPath
|
||||
} else {
|
||||
return indexPath
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if tableView === statusView.pollTableView {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||
delegate?.statusTableViewCell(self, pollTableView: statusView.pollTableView, didSelectRowAt: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - StatusViewDelegate
|
||||
|
|
|
@ -21,6 +21,8 @@ extension APIService {
|
|||
case badResponse
|
||||
case requestThrottle
|
||||
|
||||
case voteExpiredPoll
|
||||
|
||||
// Server API error
|
||||
case mastodonAPIError(Mastodon.API.Error)
|
||||
}
|
||||
|
@ -44,6 +46,7 @@ extension APIService.APIError: LocalizedError {
|
|||
case .badRequest: return "Bad Request"
|
||||
case .badResponse: return "Bad Response"
|
||||
case .requestThrottle: return "Request Throttled"
|
||||
case .voteExpiredPoll: return L10n.Common.Alerts.VoteFailure.title
|
||||
case .mastodonAPIError(let error):
|
||||
guard let responseError = error.mastodonError else {
|
||||
guard error.httpResponseStatus != .ok else {
|
||||
|
@ -62,6 +65,7 @@ extension APIService.APIError: LocalizedError {
|
|||
case .badRequest: return "Request invalid."
|
||||
case .badResponse: return "Response invalid."
|
||||
case .requestThrottle: return "Request too frequency."
|
||||
case .voteExpiredPoll: return L10n.Common.Alerts.VoteFailure.pollExpired
|
||||
case .mastodonAPIError(let error):
|
||||
guard let responseError = error.mastodonError else {
|
||||
return nil
|
||||
|
@ -73,9 +77,10 @@ extension APIService.APIError: LocalizedError {
|
|||
var helpAnchor: String? {
|
||||
switch errorReason {
|
||||
case .authenticationMissing: return "Please request after authenticated."
|
||||
case .badRequest: return "Please try again."
|
||||
case .badResponse: return "Please try again."
|
||||
case .requestThrottle: return "Please try again later."
|
||||
case .badRequest: return L10n.Common.Alerts.Common.pleaseTryAgain
|
||||
case .badResponse: return L10n.Common.Alerts.Common.pleaseTryAgain
|
||||
case .requestThrottle: return L10n.Common.Alerts.Common.pleaseTryAgainLater
|
||||
case .voteExpiredPoll: return nil
|
||||
case .mastodonAPIError(let error):
|
||||
guard let responseError = error.mastodonError else {
|
||||
return nil
|
||||
|
|
|
@ -69,3 +69,127 @@ extension APIService {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
extension APIService {
|
||||
|
||||
/// vote local
|
||||
/// # Note
|
||||
/// Not mark the poll voted so that view model could know when to reveal the results
|
||||
func vote(
|
||||
pollObjectID: NSManagedObjectID,
|
||||
mastodonUserObjectID: NSManagedObjectID,
|
||||
choices: [Int]
|
||||
) -> AnyPublisher<Mastodon.Entity.Poll.ID, Error> {
|
||||
var _targetPollID: Mastodon.Entity.Poll.ID?
|
||||
var isPollExpired = false
|
||||
var didVotedLocal = false
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
return managedObjectContext.performChanges {
|
||||
let poll = managedObjectContext.object(with: pollObjectID) as! Poll
|
||||
let mastodonUser = managedObjectContext.object(with: mastodonUserObjectID) as! MastodonUser
|
||||
|
||||
_targetPollID = poll.id
|
||||
|
||||
if let expiresAt = poll.expiresAt, Date().timeIntervalSince(expiresAt) > 0 {
|
||||
isPollExpired = true
|
||||
poll.update(expired: true)
|
||||
return
|
||||
}
|
||||
|
||||
let options = poll.options.sorted(by: { $0.index.intValue < $1.index.intValue })
|
||||
let votedOptions = poll.options.filter { option in
|
||||
(option.votedBy ?? Set()).map { $0.id }.contains(mastodonUser.id)
|
||||
}
|
||||
guard votedOptions.isEmpty else {
|
||||
// if did voted. Do not allow vote again
|
||||
didVotedLocal = true
|
||||
return
|
||||
}
|
||||
for option in options {
|
||||
let voted = choices.contains(option.index.intValue)
|
||||
option.update(voted: voted, by: mastodonUser)
|
||||
option.didUpdate(at: option.updatedAt) // trigger update without change anything
|
||||
}
|
||||
poll.didUpdate(at: poll.updatedAt) // trigger update without change anything
|
||||
}
|
||||
.tryMap { result in
|
||||
guard !isPollExpired else {
|
||||
throw APIError.explicit(APIError.ErrorReason.voteExpiredPoll)
|
||||
}
|
||||
guard !didVotedLocal else {
|
||||
throw APIError.implicit(APIError.ErrorReason.badRequest)
|
||||
}
|
||||
switch result {
|
||||
case .success:
|
||||
guard let targetPollID = _targetPollID else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
return targetPollID
|
||||
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// send vote request to remote
|
||||
func vote(
|
||||
domain: String,
|
||||
pollID: Mastodon.Entity.Poll.ID,
|
||||
pollObjectID: NSManagedObjectID,
|
||||
choices: [Int],
|
||||
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> {
|
||||
let authorization = mastodonAuthenticationBox.userAuthorization
|
||||
let requestMastodonUserID = mastodonAuthenticationBox.userID
|
||||
|
||||
let query = Mastodon.API.Polls.VoteQuery(choices: choices)
|
||||
return Mastodon.API.Polls.vote(
|
||||
session: session,
|
||||
domain: domain,
|
||||
pollID: pollID,
|
||||
query: query,
|
||||
authorization: authorization
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> in
|
||||
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
|
||||
}
|
||||
}()
|
||||
guard let requestMastodonUser = _requestMastodonUser else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard let poll = managedObjectContext.object(with: pollObjectID) as? Poll else { return }
|
||||
APIService.CoreData.merge(poll: poll, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Poll> in
|
||||
switch result {
|
||||
case .success:
|
||||
return response
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import Combine
|
|||
import Foundation
|
||||
|
||||
extension Mastodon.API.Favorites {
|
||||
|
||||
static func favoritesStatusesEndpointURL(domain: String) -> URL {
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("favourites")
|
||||
}
|
||||
|
@ -34,6 +35,8 @@ extension Mastodon.API.Favorites {
|
|||
///
|
||||
/// Add a status to your favourites list / Remove a status from your favourites list
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
|
@ -60,6 +63,8 @@ extension Mastodon.API.Favorites {
|
|||
///
|
||||
/// View who favourited a given status.
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
|
@ -85,6 +90,8 @@ extension Mastodon.API.Favorites {
|
|||
///
|
||||
/// Using this endpoint to view the favourited list for user
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
|
@ -104,9 +111,11 @@ extension Mastodon.API.Favorites {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension Mastodon.API.Favorites {
|
||||
|
||||
enum FavoriteKind {
|
||||
case create
|
||||
case destroy
|
||||
|
@ -144,4 +153,5 @@ public extension Mastodon.API.Favorites {
|
|||
return items
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,11 +14,18 @@ extension Mastodon.API.Polls {
|
|||
let pathComponent = "polls/" + pollID
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
static func votePollEndpointURL(domain: String, pollID: Mastodon.Entity.Poll.ID) -> URL {
|
||||
let pathComponent = "polls/" + pollID + "/votes"
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
/// View a poll
|
||||
///
|
||||
/// Using this endpoint to view the poll of status
|
||||
///
|
||||
/// - Since: 2.8.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
|
@ -28,7 +35,7 @@ extension Mastodon.API.Polls {
|
|||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - pollID: id for poll
|
||||
/// - authorization: User token. Could be nil if status is public
|
||||
/// - Returns: `AnyPublisher` contains `Server` nested in the response
|
||||
/// - Returns: `AnyPublisher` contains `Poll` nested in the response
|
||||
public static func poll(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
|
@ -48,4 +55,51 @@ extension Mastodon.API.Polls {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
/// Vote on a poll
|
||||
///
|
||||
/// Using this endpoint to vote an option of poll
|
||||
///
|
||||
/// - Since: 2.8.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/3/4
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/statuses/polls/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - pollID: id for poll
|
||||
/// - query: `VoteQuery`
|
||||
/// - authorization: User token
|
||||
/// - Returns: `AnyPublisher` contains `Poll` nested in the response
|
||||
public static func vote(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
pollID: Mastodon.Entity.Poll.ID,
|
||||
query: VoteQuery,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> {
|
||||
let request = Mastodon.API.post(
|
||||
url: votePollEndpointURL(domain: domain, pollID: pollID),
|
||||
query: query,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Poll.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.Polls {
|
||||
public struct VoteQuery: Codable, PostQuery {
|
||||
public let choices: [Int]
|
||||
|
||||
public init(choices: [Int]) {
|
||||
self.choices = choices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue