Tusker/Tusker/Views/Poll/StatusPollView.swift

181 lines
6.4 KiB
Swift

//
// 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!
weak var toastableViewController: ToastableViewController?
private var statusID: String!
private(set) var poll: Poll?
private var optionsView: PollOptionsView!
private var voteButton: PollVoteButton!
private var infoLabel: UILabel!
private var canVote = true
private var animator: UIViewPropertyAnimator!
private var currentSelectedOptionIndex: Int!
var isTracking: Bool {
optionsView.isTracking
}
override init(frame: CGRect) {
super.init(frame: .zero)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
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 = PollVoteButton()
voteButton.translatesAutoresizingMaskIntoConstraints = false
voteButton.addTarget(self, action: #selector(votePressed))
voteButton.setFont(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),
])
accessibilityElements = [optionsView!, infoLabel!, voteButton!]
}
func updateUI(status: StatusMO, poll: Poll?) {
self.statusID = status.id
self.poll = poll
guard let poll = poll else { return }
// 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.disabledTitle = "Expired"
} else if poll.voted ?? false {
if status.account.id == mastodonController.account?.id {
voteButton.isHidden = true
} else {
voteButton.disabledTitle = "Voted"
}
} else if poll.multiple {
voteButton.disabledTitle = "Select multiple"
} else {
voteButton.disabledTitle = "Select one"
}
voteButton.isEnabled = false
}
private func checkedOptionsChanged() {
voteButton.isEnabled = optionsView.checkedOptionIndices.count > 0
}
@objc private func votePressed() {
optionsView.isEnabled = false
voteButton.isEnabled = false
voteButton.disabledTitle = "Voted"
UIImpactFeedbackGenerator(style: .light).impactOccurred()
let request = Poll.vote(poll!.id, choices: optionsView.checkedOptionIndices)
mastodonController.run(request) { (response) in
switch response {
case let .failure(error):
DispatchQueue.main.async {
self.updateUI(status: self.mastodonController.persistentContainer.status(for: self.statusID)!, poll: self.poll)
if let toastable = self.toastableViewController {
let config = ToastConfiguration(from: error, with: "Error Voting", in: toastable, retryAction: nil)
toastable.showToast(configuration: config, animated: true)
}
}
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
container.save(context: container.viewContext)
container.statusSubject.send(status.id)
}
}
}
}
}