forked from shadowfacts/Tusker
parent
442f57bfc4
commit
9763edef47
|
@ -255,7 +255,6 @@
|
|||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B81F432560390300F6E31D /* MenuController.swift */; };
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||
D6B93667281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */; };
|
||||
D6B9366B281EE77E00237D0E /* PollVoteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366A281EE77E00237D0E /* PollVoteButton.swift */; };
|
||||
D6B9366D2828445000237D0E /* SavedInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366C2828444F00237D0E /* SavedInstance.swift */; };
|
||||
D6B9366F2828452F00237D0E /* SavedHashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366E2828452F00237D0E /* SavedHashtag.swift */; };
|
||||
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */; };
|
||||
|
@ -658,7 +657,6 @@
|
|||
D6B81F432560390300F6E31D /* MenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuController.swift; sourceTree = "<group>"; };
|
||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||
D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarMyProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6B9366A281EE77E00237D0E /* PollVoteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVoteButton.swift; sourceTree = "<group>"; };
|
||||
D6B9366C2828444F00237D0E /* SavedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedInstance.swift; sourceTree = "<group>"; };
|
||||
D6B9366E2828452F00237D0E /* SavedHashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtag.swift; sourceTree = "<group>"; };
|
||||
D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Helpers.swift"; sourceTree = "<group>"; };
|
||||
|
@ -898,7 +896,6 @@
|
|||
D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */,
|
||||
D623A5402635FB3C0095BD04 /* PollOptionView.swift */,
|
||||
D623A542263634100095BD04 /* PollOptionCheckboxView.swift */,
|
||||
D6B9366A281EE77E00237D0E /* PollVoteButton.swift */,
|
||||
);
|
||||
path = Poll;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2201,7 +2198,6 @@
|
|||
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */,
|
||||
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */,
|
||||
D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */,
|
||||
D6B9366B281EE77E00237D0E /* PollVoteButton.swift in Sources */,
|
||||
D68A76DA29511CA6001DA1B3 /* AccountPreferences.swift in Sources */,
|
||||
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */,
|
||||
D6895DC428D65342006341DA /* ConfirmReblogStatusPreviewView.swift in Sources */,
|
||||
|
|
|
@ -75,7 +75,7 @@ class PollOptionView: UIView {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateUI(poll: Poll, option: Poll.Option, ownVoted: Bool, mastodonController: MastodonController) {
|
||||
func updateUI(poll: Poll, option: Poll.Option, ownVoted: Bool, showResults: Bool, mastodonController: MastodonController) {
|
||||
let showCheckbox = poll.ownVotes?.isEmpty == false || !poll.effectiveExpired
|
||||
if showCheckbox {
|
||||
checkbox.isChecked = ownVoted
|
||||
|
@ -100,7 +100,7 @@ class PollOptionView: UIView {
|
|||
|
||||
accessibilityLabel = option.title
|
||||
|
||||
if (poll.voted ?? false) || poll.effectiveExpired,
|
||||
if showResults,
|
||||
let optionVotes = option.votesCount {
|
||||
let frac: CGFloat
|
||||
if poll.multiple,
|
||||
|
|
|
@ -65,7 +65,7 @@ class PollOptionsView: UIControl {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateUI(poll: Poll) {
|
||||
func updateUI(poll: Poll, showResults: Bool) {
|
||||
self.poll = poll
|
||||
|
||||
if poll.options.count > options.count {
|
||||
|
@ -81,7 +81,7 @@ class PollOptionsView: UIControl {
|
|||
}
|
||||
|
||||
for (index, (view, opt)) in zip(options, poll.options).enumerated() {
|
||||
view.updateUI(poll: poll, option: opt, ownVoted: poll.ownVotes?.contains(index) ?? false, mastodonController: mastodonController)
|
||||
view.updateUI(poll: poll, option: opt, ownVoted: poll.ownVotes?.contains(index) ?? false, showResults: showResults, mastodonController: mastodonController)
|
||||
view.checkboxIfInitialized?.readOnly = !isEnabled
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
//
|
||||
// PollVoteButton.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 5/1/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Wraps a UILabel and UIButton to allow setting disabled titles on Catalyst, where `setTitle(_:for:)` only works for the normal state.
|
||||
class PollVoteButton: UIView {
|
||||
|
||||
var disabledTitle: String = "" {
|
||||
didSet {
|
||||
update()
|
||||
}
|
||||
}
|
||||
var isEnabled = true {
|
||||
didSet {
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
private var button = UIButton(type: .system)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
private var label = UILabel()
|
||||
#endif
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.setTitleColor(.secondaryLabel, for: .disabled)
|
||||
button.contentHorizontalAlignment = .trailing
|
||||
embedSubview(button)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
label.textColor = .secondaryLabel
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textAlignment = .right
|
||||
embedSubview(label)
|
||||
#endif
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func addTarget(_ target: Any, action: Selector) {
|
||||
button.addTarget(target, action: action, for: .touchUpInside)
|
||||
}
|
||||
|
||||
func setFont(_ font: UIFont) {
|
||||
button.titleLabel!.font = font
|
||||
#if targetEnvironment(macCatalyst)
|
||||
label.font = font
|
||||
#endif
|
||||
}
|
||||
|
||||
private func update() {
|
||||
button.isEnabled = isEnabled
|
||||
if isEnabled {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
label.isHidden = true
|
||||
button.isHidden = false
|
||||
#endif
|
||||
button.setTitle("Vote", for: .normal)
|
||||
} else {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
label.text = disabledTitle
|
||||
label.isHidden = false
|
||||
button.isHidden = true
|
||||
#else
|
||||
button.setTitle(disabledTitle, for: .disabled)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,7 @@ class StatusPollView: UIView, StatusContentView {
|
|||
let f = DateComponentsFormatter()
|
||||
f.includesTimeRemainingPhrase = true
|
||||
f.maximumUnitCount = 1
|
||||
f.unitsStyle = .full
|
||||
f.unitsStyle = .abbreviated
|
||||
f.allowedUnits = [.weekOfMonth, .day, .hour, .minute]
|
||||
return f
|
||||
}()
|
||||
|
@ -25,9 +25,10 @@ class StatusPollView: UIView, StatusContentView {
|
|||
|
||||
private var statusID: String!
|
||||
private(set) var poll: Poll?
|
||||
private var showingResults = false
|
||||
|
||||
private var optionsView: PollOptionsView!
|
||||
private var voteButton: PollVoteButton!
|
||||
private var voteButton: UIButton!
|
||||
private var infoLabel: UILabel!
|
||||
|
||||
private var canVote = true
|
||||
|
@ -63,11 +64,11 @@ class StatusPollView: UIView, StatusContentView {
|
|||
infoLabel.adjustsFontSizeToFitWidth = true
|
||||
addSubview(infoLabel)
|
||||
|
||||
voteButton = PollVoteButton()
|
||||
voteButton = UIButton(configuration: .plain())
|
||||
voteButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
voteButton.addTarget(self, action: #selector(votePressed))
|
||||
voteButton.setFont(infoLabel.font)
|
||||
voteButton.addTarget(self, action: #selector(votePressed), for: .touchUpInside)
|
||||
voteButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
voteButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
addSubview(voteButton)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
|
@ -76,20 +77,24 @@ class StatusPollView: UIView, StatusContentView {
|
|||
optionsView.topAnchor.constraint(equalTo: topAnchor),
|
||||
|
||||
infoLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
infoLabel.topAnchor.constraint(equalTo: optionsView.bottomAnchor),
|
||||
infoLabel.topAnchor.constraint(equalTo: optionsView.bottomAnchor, constant: 4),
|
||||
infoLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
infoLabel.trailingAnchor.constraint(equalTo: 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),
|
||||
voteButton.topAnchor.constraint(equalTo: infoLabel.topAnchor),
|
||||
voteButton.bottomAnchor.constraint(equalTo: infoLabel.bottomAnchor),
|
||||
])
|
||||
|
||||
accessibilityElements = [optionsView!, infoLabel!, voteButton!]
|
||||
}
|
||||
|
||||
func updateUI(status: StatusMO, poll: Poll?) {
|
||||
if statusID != status.id {
|
||||
showingResults = false
|
||||
}
|
||||
|
||||
self.statusID = status.id
|
||||
self.poll = poll
|
||||
|
||||
|
@ -104,19 +109,17 @@ class StatusPollView: UIView, StatusContentView {
|
|||
|
||||
optionsView.mastodonController = mastodonController
|
||||
optionsView.isEnabled = canVote
|
||||
optionsView.updateUI(poll: poll)
|
||||
optionsView.updateUI(poll: poll, showResults: showingResults || !canVote)
|
||||
|
||||
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"
|
||||
expiryText = "No expiry"
|
||||
}
|
||||
|
||||
let format = NSLocalizedString("poll votes count", comment: "poll total votes count")
|
||||
|
@ -125,20 +128,7 @@ class StatusPollView: UIView, StatusContentView {
|
|||
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
|
||||
updateVoteButton(status: status, poll: poll)
|
||||
}
|
||||
|
||||
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||
|
@ -146,11 +136,54 @@ class StatusPollView: UIView, StatusContentView {
|
|||
return optionsView.estimateHeight(effectiveWidth: effectiveWidth) + infoLabel.sizeThatFits(UIView.layoutFittingExpandedSize).height
|
||||
}
|
||||
|
||||
private func updateVoteButton(status: StatusMO, poll: Poll) {
|
||||
let buttonTitle: String
|
||||
let buttonEnabled: Bool
|
||||
if let expiresAt = poll.expiresAt,
|
||||
expiresAt <= Date() {
|
||||
buttonTitle = "Expired"
|
||||
buttonEnabled = false
|
||||
} else if poll.voted ?? false {
|
||||
if status.account.id == mastodonController.account?.id {
|
||||
voteButton.isHidden = true
|
||||
return
|
||||
} else {
|
||||
buttonTitle = "Voted"
|
||||
buttonEnabled = false
|
||||
}
|
||||
} else if optionsView.checkedOptionIndices.isEmpty {
|
||||
buttonTitle = "See Results"
|
||||
buttonEnabled = true
|
||||
} else {
|
||||
buttonTitle = "Vote"
|
||||
buttonEnabled = true
|
||||
}
|
||||
var config = UIButton.Configuration.plain()
|
||||
config.attributedTitle = AttributedString(buttonTitle)
|
||||
config.attributedTitle!.font = infoLabel.font
|
||||
config.contentInsets = .zero
|
||||
// Necessary on Catalyst for some reason.
|
||||
config.baseForegroundColor = .tintColor
|
||||
voteButton.configuration = config
|
||||
voteButton.isEnabled = buttonEnabled
|
||||
}
|
||||
|
||||
private func checkedOptionsChanged() {
|
||||
voteButton.isEnabled = optionsView.checkedOptionIndices.count > 0
|
||||
if let status = mastodonController.persistentContainer.status(for: statusID),
|
||||
let poll = status.poll {
|
||||
updateVoteButton(status: status, poll: poll)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func votePressed() {
|
||||
if !optionsView.checkedOptionIndices.isEmpty {
|
||||
doVote()
|
||||
} else {
|
||||
showResults()
|
||||
}
|
||||
}
|
||||
|
||||
private func doVote() {
|
||||
guard let statusID,
|
||||
let poll else {
|
||||
return
|
||||
|
@ -158,7 +191,6 @@ class StatusPollView: UIView, StatusContentView {
|
|||
|
||||
optionsView.isEnabled = false
|
||||
voteButton.isEnabled = false
|
||||
voteButton.disabledTitle = "Voted"
|
||||
|
||||
#if !os(visionOS)
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
|
@ -191,4 +223,11 @@ class StatusPollView: UIView, StatusContentView {
|
|||
}
|
||||
}
|
||||
|
||||
private func showResults() {
|
||||
showingResults = true
|
||||
if let status = mastodonController.persistentContainer.status(for: statusID) {
|
||||
updateUI(status: status, poll: status.poll)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue