Compare commits
4 Commits
bf6dfab121
...
203c1852d4
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 203c1852d4 | |
Shadowfacts | 708112c486 | |
Shadowfacts | 5b321fcc78 | |
Shadowfacts | 59231e513f |
|
@ -147,7 +147,8 @@ class ConversationCollectionViewController: UIViewController, CollectionViewCont
|
||||||
Item.status(id: node.status.id, node: node, state: .unknown, prevLink: index > 0, nextLink: true)
|
Item.status(id: node.status.id, node: node, state: .unknown, prevLink: index > 0, nextLink: true)
|
||||||
}
|
}
|
||||||
snapshot.appendItems(parentItems, toSection: .ancestors)
|
snapshot.appendItems(parentItems, toSection: .ancestors)
|
||||||
snapshot.reconfigureItems([mainStatusItem])
|
// don't need to reconfigure main item, since when the refreshed copy was loaded
|
||||||
|
// it would have triggered a reconfigure via the status observer
|
||||||
|
|
||||||
// convert sub-threads into items for section and add to snapshot
|
// convert sub-threads into items for section and add to snapshot
|
||||||
self.addFlattenedChildThreadsToSnapshot(tree.descendants, mainStatus: mainStatus, snapshot: &snapshot)
|
self.addFlattenedChildThreadsToSnapshot(tree.descendants, mainStatus: mainStatus, snapshot: &snapshot)
|
||||||
|
|
|
@ -31,7 +31,7 @@ class StatusEditPollView: UIStackView, StatusContentPollView {
|
||||||
|
|
||||||
for option in poll?.options ?? [] {
|
for option in poll?.options ?? [] {
|
||||||
// the edit poll doesn't actually include the multiple value
|
// the edit poll doesn't actually include the multiple value
|
||||||
let icon = PollOptionCheckboxView(multiple: false)
|
let icon = PollOptionCheckboxView()
|
||||||
icon.readOnly = false // this is a lie, but it's only used for stylistic changes
|
icon.readOnly = false // this is a lie, but it's only used for stylistic changes
|
||||||
let label = EmojiLabel()
|
let label = EmojiLabel()
|
||||||
label.text = option.title
|
label.text = option.title
|
||||||
|
|
|
@ -63,19 +63,20 @@ class TimelineLikeController<Item: Sendable> {
|
||||||
}
|
}
|
||||||
let token = LoadAttemptToken()
|
let token = LoadAttemptToken()
|
||||||
state = .loadingInitial(token, hasAddedLoadingIndicator: false)
|
state = .loadingInitial(token, hasAddedLoadingIndicator: false)
|
||||||
let loadingIndicator = DeferredLoadingIndicator(owner: self, state: state, addedIndicatorState: .loadingInitial(token, hasAddedLoadingIndicator: true))
|
await emit(event: .addLoadingIndicator)
|
||||||
|
state = .loadingInitial(token, hasAddedLoadingIndicator: true)
|
||||||
do {
|
do {
|
||||||
let items = try await delegate.loadInitial()
|
let items = try await delegate.loadInitial()
|
||||||
guard case .loadingInitial(token, _) = state else {
|
guard case .loadingInitial(token, _) = state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await loadingIndicator.end()
|
|
||||||
await emit(event: .replaceAllItems(items, token))
|
await emit(event: .replaceAllItems(items, token))
|
||||||
|
await emit(event: .removeLoadingIndicator)
|
||||||
state = .idle
|
state = .idle
|
||||||
} catch is CancellationError {
|
} catch is CancellationError {
|
||||||
return
|
return
|
||||||
} catch {
|
} catch {
|
||||||
await loadingIndicator.end()
|
await emit(event: .removeLoadingIndicator)
|
||||||
await emit(event: .loadAllError(error, token))
|
await emit(event: .loadAllError(error, token))
|
||||||
state = .notLoadedInitial
|
state = .notLoadedInitial
|
||||||
}
|
}
|
||||||
|
@ -88,9 +89,10 @@ class TimelineLikeController<Item: Sendable> {
|
||||||
}
|
}
|
||||||
let token = LoadAttemptToken()
|
let token = LoadAttemptToken()
|
||||||
state = .restoringInitial(token, hasAddedLoadingIndicator: false)
|
state = .restoringInitial(token, hasAddedLoadingIndicator: false)
|
||||||
let loadingIndicator = DeferredLoadingIndicator(owner: self, state: state, addedIndicatorState: .restoringInitial(token, hasAddedLoadingIndicator: true))
|
await emit(event: .addLoadingIndicator)
|
||||||
|
state = .restoringInitial(token, hasAddedLoadingIndicator: true)
|
||||||
await doRestore()
|
await doRestore()
|
||||||
await loadingIndicator.end()
|
await emit(event: .removeLoadingIndicator)
|
||||||
state = .idle
|
state = .idle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,19 +130,20 @@ class TimelineLikeController<Item: Sendable> {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
state = .loadingOlder(token, hasAddedLoadingIndicator: false)
|
state = .loadingOlder(token, hasAddedLoadingIndicator: false)
|
||||||
let loadingIndicator = DeferredLoadingIndicator(owner: self, state: state, addedIndicatorState: .loadingOlder(token, hasAddedLoadingIndicator: true))
|
await emit(event: .addLoadingIndicator)
|
||||||
|
state = .loadingOlder(token, hasAddedLoadingIndicator: true)
|
||||||
do {
|
do {
|
||||||
let items = try await delegate.loadOlder()
|
let items = try await delegate.loadOlder()
|
||||||
guard case .loadingOlder(token, _) = state else {
|
guard case .loadingOlder(token, _) = state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await loadingIndicator.end()
|
|
||||||
await emit(event: .appendItems(items, token))
|
await emit(event: .appendItems(items, token))
|
||||||
|
await emit(event: .removeLoadingIndicator)
|
||||||
state = .idle
|
state = .idle
|
||||||
} catch is CancellationError {
|
} catch is CancellationError {
|
||||||
return
|
return
|
||||||
} catch {
|
} catch {
|
||||||
await loadingIndicator.end()
|
await emit(event: .removeLoadingIndicator)
|
||||||
await emit(event: .loadOlderError(error, token))
|
await emit(event: .loadOlderError(error, token))
|
||||||
state = .idle
|
state = .idle
|
||||||
}
|
}
|
||||||
|
@ -349,34 +352,6 @@ class TimelineLikeController<Item: Sendable> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
class DeferredLoadingIndicator {
|
|
||||||
private let owner: TimelineLikeController<Item>
|
|
||||||
private let addedIndicatorState: State
|
|
||||||
private let task: Task<Void, Error>
|
|
||||||
|
|
||||||
init(owner: TimelineLikeController<Item>, state: State, addedIndicatorState: State) {
|
|
||||||
self.owner = owner
|
|
||||||
self.addedIndicatorState = addedIndicatorState
|
|
||||||
self.task = Task { @MainActor in
|
|
||||||
try await Task.sleep(nanoseconds: 150 * NSEC_PER_MSEC)
|
|
||||||
guard state == owner.state else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await owner.emit(event: .addLoadingIndicator)
|
|
||||||
owner.transition(to: addedIndicatorState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func end() async {
|
|
||||||
if owner.state == addedIndicatorState {
|
|
||||||
await owner.emit(event: .removeLoadingIndicator)
|
|
||||||
} else {
|
|
||||||
task.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TimelineGapDirection {
|
enum TimelineGapDirection {
|
||||||
|
|
|
@ -10,6 +10,8 @@ import UIKit
|
||||||
|
|
||||||
class PollOptionCheckboxView: UIView {
|
class PollOptionCheckboxView: UIView {
|
||||||
|
|
||||||
|
private static let size: CGFloat = 20
|
||||||
|
|
||||||
var isChecked: Bool = false {
|
var isChecked: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateStyle()
|
updateStyle()
|
||||||
|
@ -25,16 +27,19 @@ class PollOptionCheckboxView: UIView {
|
||||||
updateStyle()
|
updateStyle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var multiple: Bool = false {
|
||||||
|
didSet {
|
||||||
|
updateStyle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let imageView: UIImageView
|
private let imageView: UIImageView
|
||||||
|
|
||||||
init(multiple: Bool) {
|
init() {
|
||||||
imageView = UIImageView(image: UIImage(systemName: "checkmark")!)
|
imageView = UIImageView(image: UIImage(systemName: "checkmark")!)
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
let size: CGFloat = 20
|
|
||||||
layer.cornerRadius = (multiple ? 0.1 : 0.5) * size
|
|
||||||
layer.cornerCurve = .continuous
|
layer.cornerCurve = .continuous
|
||||||
layer.borderWidth = 2
|
layer.borderWidth = 2
|
||||||
|
|
||||||
|
@ -46,7 +51,7 @@ class PollOptionCheckboxView: UIView {
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
widthAnchor.constraint(equalTo: heightAnchor),
|
widthAnchor.constraint(equalTo: heightAnchor),
|
||||||
widthAnchor.constraint(equalToConstant: size),
|
widthAnchor.constraint(equalToConstant: PollOptionCheckboxView.size),
|
||||||
|
|
||||||
imageView.widthAnchor.constraint(equalTo: widthAnchor, constant: -3),
|
imageView.widthAnchor.constraint(equalTo: widthAnchor, constant: -3),
|
||||||
imageView.heightAnchor.constraint(equalTo: heightAnchor, constant: -3),
|
imageView.heightAnchor.constraint(equalTo: heightAnchor, constant: -3),
|
||||||
|
@ -64,6 +69,8 @@ class PollOptionCheckboxView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateStyle() {
|
private func updateStyle() {
|
||||||
|
layer.cornerRadius = (multiple ? 0.1 : 0.5) * PollOptionCheckboxView.size
|
||||||
|
|
||||||
imageView.isHidden = !isChecked
|
imageView.isHidden = !isChecked
|
||||||
if voted || readOnly {
|
if voted || readOnly {
|
||||||
layer.borderColor = UIColor.clear.cgColor
|
layer.borderColor = UIColor.clear.cgColor
|
||||||
|
|
|
@ -11,35 +11,43 @@ import Pachyderm
|
||||||
|
|
||||||
class PollOptionView: UIView {
|
class PollOptionView: UIView {
|
||||||
|
|
||||||
|
private static let minHeight: CGFloat = 35
|
||||||
|
private static let cornerRadius = 0.1 * minHeight
|
||||||
private static let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
|
private static let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
|
||||||
|
|
||||||
private(set) var label: EmojiLabel!
|
private(set) var label: EmojiLabel!
|
||||||
private(set) var checkbox: PollOptionCheckboxView?
|
@Lazy private var checkbox: PollOptionCheckboxView = PollOptionCheckboxView().configure {
|
||||||
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
}
|
||||||
|
var checkboxIfInitialized: PollOptionCheckboxView? {
|
||||||
|
_checkbox.valueIfInitialized
|
||||||
|
}
|
||||||
|
private var percentLabel: UILabel!
|
||||||
|
@Lazy private var fillView: UIView = UIView().configure {
|
||||||
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
$0.backgroundColor = .tintColor.withAlphaComponent(0.6)
|
||||||
|
$0.layer.zPosition = -1
|
||||||
|
$0.layer.cornerRadius = PollOptionView.cornerRadius
|
||||||
|
$0.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
private var labelLeadingToSelfConstraint: NSLayoutConstraint!
|
||||||
|
private var fillViewWidthConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
init(poll: Poll, option: Poll.Option, mastodonController: MastodonController) {
|
init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
let minHeight: CGFloat = 35
|
layer.cornerRadius = PollOptionView.cornerRadius
|
||||||
layer.cornerRadius = 0.1 * minHeight
|
|
||||||
layer.cornerCurve = .continuous
|
layer.cornerCurve = .continuous
|
||||||
backgroundColor = PollOptionView.unselectedBackgroundColor
|
backgroundColor = PollOptionView.unselectedBackgroundColor
|
||||||
|
|
||||||
let showCheckbox = poll.ownVotes?.isEmpty == false || !poll.effectiveExpired
|
|
||||||
if showCheckbox {
|
|
||||||
checkbox = PollOptionCheckboxView(multiple: poll.multiple)
|
|
||||||
checkbox!.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(checkbox!)
|
|
||||||
}
|
|
||||||
|
|
||||||
label = EmojiLabel()
|
label = EmojiLabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
label.font = .preferredFont(forTextStyle: .callout)
|
label.font = .preferredFont(forTextStyle: .callout)
|
||||||
label.text = option.title
|
|
||||||
label.setEmojis(poll.emojis, identifier: poll.id)
|
|
||||||
addSubview(label)
|
addSubview(label)
|
||||||
|
labelLeadingToSelfConstraint = label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8)
|
||||||
|
|
||||||
let percentLabel = UILabel()
|
percentLabel = UILabel()
|
||||||
percentLabel.translatesAutoresizingMaskIntoConstraints = false
|
percentLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
percentLabel.text = "100%"
|
percentLabel.text = "100%"
|
||||||
percentLabel.font = label.font
|
percentLabel.font = label.font
|
||||||
|
@ -48,6 +56,53 @@ class PollOptionView: UIView {
|
||||||
percentLabel.setContentHuggingPriority(.required, for: .horizontal)
|
percentLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
addSubview(percentLabel)
|
addSubview(percentLabel)
|
||||||
|
|
||||||
|
let minHeightConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: PollOptionView.minHeight)
|
||||||
|
// on the first layout, something is weird and this becomes ambiguous even though it's fine on subsequent layouts
|
||||||
|
// this keeps autolayout from complaining
|
||||||
|
minHeightConstraint.priority = .required - 1
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
minHeightConstraint,
|
||||||
|
|
||||||
|
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
||||||
|
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
||||||
|
label.trailingAnchor.constraint(equalTo: percentLabel.leadingAnchor, constant: -4),
|
||||||
|
|
||||||
|
percentLabel.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
percentLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
|
||||||
|
])
|
||||||
|
|
||||||
|
isAccessibilityElement = true
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUI(poll: Poll, option: Poll.Option, ownVoted: Bool, mastodonController: MastodonController) {
|
||||||
|
let showCheckbox = poll.ownVotes?.isEmpty == false || !poll.effectiveExpired
|
||||||
|
if showCheckbox {
|
||||||
|
checkbox.isChecked = ownVoted
|
||||||
|
checkbox.voted = poll.voted ?? false
|
||||||
|
|
||||||
|
labelLeadingToSelfConstraint.isActive = false
|
||||||
|
if checkbox.superview != self {
|
||||||
|
addSubview(checkbox)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
checkbox.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||||
|
checkbox.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
||||||
|
label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 8),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} else if !showCheckbox {
|
||||||
|
labelLeadingToSelfConstraint.isActive = true
|
||||||
|
_checkbox.valueIfInitialized?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
label.text = option.title
|
||||||
|
label.setEmojis(poll.emojis, identifier: poll.id)
|
||||||
|
|
||||||
accessibilityLabel = option.title
|
accessibilityLabel = option.title
|
||||||
|
|
||||||
if (poll.voted ?? false) || poll.effectiveExpired,
|
if (poll.voted ?? false) || poll.effectiveExpired,
|
||||||
|
@ -65,56 +120,23 @@ class PollOptionView: UIView {
|
||||||
percentLabel.isHidden = false
|
percentLabel.isHidden = false
|
||||||
percentLabel.text = percent
|
percentLabel.text = percent
|
||||||
|
|
||||||
let fillView = UIView()
|
if fillView.superview != self {
|
||||||
fillView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
fillView.backgroundColor = .tintColor.withAlphaComponent(0.6)
|
|
||||||
fillView.layer.zPosition = -1
|
|
||||||
fillView.layer.cornerRadius = layer.cornerRadius
|
|
||||||
fillView.layer.cornerCurve = .continuous
|
|
||||||
addSubview(fillView)
|
addSubview(fillView)
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
fillView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
fillView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
fillView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: frac),
|
|
||||||
fillView.topAnchor.constraint(equalTo: topAnchor),
|
fillView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
fillView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
fillView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
])
|
||||||
|
}
|
||||||
|
fillViewWidthConstraint?.isActive = false
|
||||||
|
fillViewWidthConstraint = fillView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: frac)
|
||||||
|
fillViewWidthConstraint!.isActive = true
|
||||||
|
|
||||||
accessibilityLabel! += ", \(percent)"
|
accessibilityLabel! += ", \(percent)"
|
||||||
}
|
|
||||||
|
|
||||||
let minHeightConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
|
|
||||||
// on the first layout, something is weird and this becomes ambiguous even though it's fine on subsequent layouts
|
|
||||||
// this keeps autolayout from complaining
|
|
||||||
minHeightConstraint.priority = .required - 1
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
minHeightConstraint,
|
|
||||||
|
|
||||||
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
|
||||||
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
|
||||||
label.trailingAnchor.constraint(equalTo: percentLabel.leadingAnchor, constant: -4),
|
|
||||||
|
|
||||||
percentLabel.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
percentLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
|
|
||||||
])
|
|
||||||
|
|
||||||
if let checkbox {
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
checkbox.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
||||||
checkbox.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
|
||||||
label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 8),
|
|
||||||
])
|
|
||||||
} else {
|
} else {
|
||||||
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8).isActive = true
|
percentLabel.isHidden = true
|
||||||
|
_fillView.valueIfInitialized?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
isAccessibilityElement = true
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class PollOptionsView: UIControl {
|
||||||
var mastodonController: MastodonController!
|
var mastodonController: MastodonController!
|
||||||
|
|
||||||
var checkedOptionIndices: [Int] {
|
var checkedOptionIndices: [Int] {
|
||||||
options.enumerated().filter { $0.element.checkbox?.isChecked == true }.map(\.offset)
|
options.enumerated().filter { $0.element.checkboxIfInitialized?.isChecked == true }.map(\.offset)
|
||||||
}
|
}
|
||||||
var checkedOptionsChanged: (() -> Void)?
|
var checkedOptionsChanged: (() -> Void)?
|
||||||
|
|
||||||
|
@ -32,10 +32,15 @@ class PollOptionsView: UIControl {
|
||||||
|
|
||||||
override var isEnabled: Bool {
|
override var isEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
options.forEach { $0.checkbox?.readOnly = !isEnabled }
|
options.forEach { $0.checkboxIfInitialized?.readOnly = !isEnabled }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var accessibilityElements: [Any]? {
|
||||||
|
get { options }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
stack = UIStackView()
|
stack = UIStackView()
|
||||||
|
|
||||||
|
@ -61,20 +66,22 @@ class PollOptionsView: UIControl {
|
||||||
func updateUI(poll: Poll) {
|
func updateUI(poll: Poll) {
|
||||||
self.poll = poll
|
self.poll = poll
|
||||||
|
|
||||||
options.forEach { $0.removeFromSuperview() }
|
if poll.options.count > options.count {
|
||||||
|
for _ in 0..<(poll.options.count - options.count) {
|
||||||
options = poll.options.enumerated().map { (index, opt) in
|
let optView = PollOptionView()
|
||||||
let optionView = PollOptionView(poll: poll, option: opt, mastodonController: mastodonController)
|
options.append(optView)
|
||||||
if let checkbox = optionView.checkbox {
|
stack.addArrangedSubview(optView)
|
||||||
checkbox.readOnly = !isEnabled
|
}
|
||||||
checkbox.isChecked = poll.ownVotes?.contains(index) ?? false
|
} else if poll.options.count < options.count {
|
||||||
checkbox.voted = poll.voted ?? false
|
for _ in 0..<(options.count - poll.options.count) {
|
||||||
|
options.removeLast().removeFromSuperview()
|
||||||
}
|
}
|
||||||
stack.addArrangedSubview(optionView)
|
|
||||||
return optionView
|
|
||||||
}
|
}
|
||||||
|
|
||||||
accessibilityElements = options
|
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.checkboxIfInitialized?.readOnly = !isEnabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
@ -89,13 +96,13 @@ class PollOptionsView: UIControl {
|
||||||
|
|
||||||
private func selectOption(_ option: PollOptionView) {
|
private func selectOption(_ option: PollOptionView) {
|
||||||
if poll.multiple {
|
if poll.multiple {
|
||||||
option.checkbox?.isChecked.toggle()
|
option.checkboxIfInitialized?.isChecked.toggle()
|
||||||
} else {
|
} else {
|
||||||
for opt in options {
|
for opt in options {
|
||||||
if opt === option {
|
if opt === option {
|
||||||
opt.checkbox?.isChecked = true
|
opt.checkboxIfInitialized?.isChecked = true
|
||||||
} else {
|
} else {
|
||||||
opt.checkbox?.isChecked = false
|
opt.checkboxIfInitialized?.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,8 @@ extension StatusCollectionViewCell {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.filter { [unowned self] in $0 == self.statusID }
|
.filter { [unowned self] in $0 == self.statusID }
|
||||||
.sink { [unowned self] _ in
|
.sink { [unowned self] _ in
|
||||||
if let status = self.mastodonController.persistentContainer.status(for: self.statusID) {
|
if let mastodonController = self.mastodonController,
|
||||||
|
let status = mastodonController.persistentContainer.status(for: self.statusID) {
|
||||||
// update immediately w/o animation
|
// update immediately w/o animation
|
||||||
self.favoriteButton.active = status.favourited
|
self.favoriteButton.active = status.favourited
|
||||||
self.reblogButton.active = status.reblogged
|
self.reblogButton.active = status.reblogged
|
||||||
|
|
Loading…
Reference in New Issue