2021-04-28 19:00:17 -04:00
|
|
|
//
|
|
|
|
// StatusPollView.swift
|
|
|
|
// Tusker
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 4/25/21.
|
|
|
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
import Pachyderm
|
|
|
|
|
|
|
|
class StatusPollView: UIView {
|
|
|
|
|
|
|
|
private static let formatter: DateComponentsFormatter = {
|
|
|
|
let f = DateComponentsFormatter()
|
|
|
|
f.includesTimeRemainingPhrase = true
|
|
|
|
f.maximumUnitCount = 1
|
|
|
|
f.unitsStyle = .full
|
|
|
|
f.allowedUnits = [.weekOfMonth, .day, .hour, .minute]
|
|
|
|
return f
|
|
|
|
}()
|
|
|
|
|
|
|
|
weak var mastodonController: MastodonController!
|
|
|
|
|
|
|
|
private var statusID: String!
|
2021-05-22 11:30:40 -04:00
|
|
|
private(set) var poll: Poll?
|
2021-04-28 19:00:17 -04:00
|
|
|
|
|
|
|
private var optionsView: PollOptionsView!
|
|
|
|
private var voteButton: UIButton!
|
|
|
|
private var infoLabel: UILabel!
|
|
|
|
|
|
|
|
private var canVote = true
|
|
|
|
private var animator: UIViewPropertyAnimator!
|
|
|
|
private var currentSelectedOptionIndex: Int!
|
|
|
|
|
2021-06-12 19:22:51 -04:00
|
|
|
var isTracking: Bool {
|
|
|
|
optionsView.isTracking
|
|
|
|
}
|
|
|
|
|
2021-04-28 19:00:17 -04:00
|
|
|
override func awakeFromNib() {
|
|
|
|
super.awakeFromNib()
|
|
|
|
|
|
|
|
backgroundColor = .clear
|
|
|
|
|
|
|
|
optionsView = PollOptionsView(frame: .zero)
|
|
|
|
optionsView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
optionsView.checkedOptionsChanged = self.checkedOptionsChanged
|
|
|
|
addSubview(optionsView)
|
|
|
|
|
|
|
|
infoLabel = UILabel()
|
|
|
|
infoLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
infoLabel.textColor = .secondaryLabel
|
|
|
|
infoLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .callout), size: 0)
|
|
|
|
infoLabel.adjustsFontSizeToFitWidth = true
|
|
|
|
addSubview(infoLabel)
|
|
|
|
|
|
|
|
voteButton = UIButton(type: .system)
|
|
|
|
voteButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
voteButton.addTarget(self, action: #selector(votePressed), for: .touchUpInside)
|
|
|
|
voteButton.setTitle("Vote", for: .normal)
|
|
|
|
voteButton.setTitleColor(.secondaryLabel, for: .disabled)
|
|
|
|
voteButton.titleLabel!.font = infoLabel.font
|
|
|
|
addSubview(voteButton)
|
|
|
|
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
optionsView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
|
|
optionsView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
|
|
optionsView.topAnchor.constraint(equalTo: topAnchor),
|
|
|
|
|
|
|
|
infoLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
|
|
infoLabel.topAnchor.constraint(equalTo: optionsView.bottomAnchor),
|
|
|
|
infoLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
|
|
infoLabel.trailingAnchor.constraint(lessThanOrEqualTo: voteButton.leadingAnchor, constant: -8),
|
|
|
|
|
|
|
|
voteButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 44),
|
|
|
|
voteButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
|
|
voteButton.topAnchor.constraint(equalTo: optionsView.bottomAnchor),
|
|
|
|
voteButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
|
|
])
|
2021-06-06 21:50:45 -04:00
|
|
|
|
|
|
|
accessibilityElements = [optionsView!, infoLabel!, voteButton!]
|
2021-04-28 19:00:17 -04:00
|
|
|
}
|
|
|
|
|
2021-05-22 11:30:40 -04:00
|
|
|
func updateUI(status: StatusMO, poll: Poll?) {
|
2021-04-28 19:00:17 -04:00
|
|
|
self.statusID = status.id
|
|
|
|
self.poll = poll
|
|
|
|
|
2021-05-22 11:30:40 -04:00
|
|
|
guard let poll = poll else { return }
|
|
|
|
|
2021-04-28 19:00:17 -04:00
|
|
|
// poll.voted is nil if there is no user (e.g., public timeline), in which case the poll also cannot be voted upon
|
|
|
|
if (poll.voted ?? true) || poll.expired || status.account.id == mastodonController.account.id {
|
|
|
|
canVote = false
|
|
|
|
} else {
|
|
|
|
canVote = true
|
|
|
|
}
|
|
|
|
|
|
|
|
optionsView.isEnabled = canVote
|
|
|
|
optionsView.updateUI(poll: poll)
|
|
|
|
|
|
|
|
var expired = false
|
|
|
|
let expiryText: String?
|
|
|
|
if let expiresAt = poll.expiresAt {
|
|
|
|
if expiresAt > Date() {
|
|
|
|
expiryText = StatusPollView.formatter.string(from: Date(), to: expiresAt)
|
|
|
|
} else {
|
|
|
|
expired = true
|
|
|
|
expiryText = nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
expiryText = "Does not expire"
|
|
|
|
}
|
|
|
|
|
|
|
|
let format = NSLocalizedString("poll votes count", comment: "poll total votes count")
|
|
|
|
infoLabel.text = String.localizedStringWithFormat(format, poll.votesCount)
|
|
|
|
if let expiryText = expiryText {
|
|
|
|
infoLabel.text! += ", \(expiryText)"
|
|
|
|
}
|
|
|
|
|
|
|
|
if expired {
|
|
|
|
voteButton.setTitle("Expired", for: .disabled)
|
|
|
|
} else if poll.voted ?? false {
|
2021-05-03 23:18:15 -04:00
|
|
|
if status.account.id == mastodonController.account.id {
|
|
|
|
voteButton.setTitle("", for: .disabled)
|
|
|
|
} else {
|
|
|
|
voteButton.setTitle("Voted", for: .disabled)
|
|
|
|
}
|
2021-04-28 19:00:17 -04:00
|
|
|
} else if poll.multiple {
|
|
|
|
voteButton.setTitle("Select multiple", for: .disabled)
|
|
|
|
} else {
|
|
|
|
voteButton.setTitle("Select one", for: .disabled)
|
|
|
|
}
|
|
|
|
voteButton.isEnabled = false
|
|
|
|
}
|
|
|
|
|
|
|
|
private func checkedOptionsChanged() {
|
|
|
|
voteButton.isEnabled = optionsView.checkedOptionIndices.count > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc private func votePressed() {
|
|
|
|
optionsView.isEnabled = false
|
|
|
|
voteButton.isEnabled = false
|
|
|
|
voteButton.setTitle("Voted", for: .disabled)
|
|
|
|
|
2021-05-05 17:46:41 -04:00
|
|
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
|
|
|
2021-05-22 11:30:40 -04:00
|
|
|
let request = Poll.vote(poll!.id, choices: optionsView.checkedOptionIndices)
|
2021-04-28 19:00:17 -04:00
|
|
|
mastodonController.run(request) { (response) in
|
|
|
|
switch response {
|
|
|
|
case let .failure(error):
|
|
|
|
fatalError("error voting in poll: \(error)")
|
|
|
|
|
|
|
|
case let .success(poll, _):
|
|
|
|
let container = self.mastodonController.persistentContainer
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
guard let status = container.status(for: self.statusID) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
status.poll = poll
|
|
|
|
if container.viewContext.hasChanges {
|
|
|
|
try! container.viewContext.save()
|
|
|
|
}
|
|
|
|
container.statusSubject.send(status.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|