feat: implement single vote poll
This commit is contained in:
parent
58c8eaabe8
commit
11cee6df35
|
@ -13,10 +13,10 @@ import MastodonSDK
|
||||||
/// Note: update Equatable when change case
|
/// Note: update Equatable when change case
|
||||||
enum Item {
|
enum Item {
|
||||||
// timeline
|
// timeline
|
||||||
case homeTimelineIndex(objectID: NSManagedObjectID, attribute: StatusTimelineAttribute)
|
case homeTimelineIndex(objectID: NSManagedObjectID, attribute: StatusAttribute)
|
||||||
|
|
||||||
// normal list
|
// normal list
|
||||||
case toot(objectID: NSManagedObjectID, attribute: StatusTimelineAttribute)
|
case toot(objectID: NSManagedObjectID, attribute: StatusAttribute)
|
||||||
|
|
||||||
// loader
|
// loader
|
||||||
case homeMiddleLoader(upperTimelineIndexAnchorObjectID: NSManagedObjectID)
|
case homeMiddleLoader(upperTimelineIndexAnchorObjectID: NSManagedObjectID)
|
||||||
|
@ -30,7 +30,7 @@ protocol StatusContentWarningAttribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Item {
|
extension Item {
|
||||||
class StatusTimelineAttribute: Hashable, StatusContentWarningAttribute {
|
class StatusAttribute: Hashable, StatusContentWarningAttribute {
|
||||||
var isStatusTextSensitive: Bool
|
var isStatusTextSensitive: Bool
|
||||||
var isStatusSensitive: Bool
|
var isStatusSensitive: Bool
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ extension Item {
|
||||||
self.isStatusSensitive = isStatusSensitive
|
self.isStatusSensitive = isStatusSensitive
|
||||||
}
|
}
|
||||||
|
|
||||||
static func == (lhs: Item.StatusTimelineAttribute, rhs: Item.StatusTimelineAttribute) -> Bool {
|
static func == (lhs: Item.StatusAttribute, rhs: Item.StatusAttribute) -> Bool {
|
||||||
return lhs.isStatusTextSensitive == rhs.isStatusTextSensitive &&
|
return lhs.isStatusTextSensitive == rhs.isStatusTextSensitive &&
|
||||||
lhs.isStatusSensitive == rhs.isStatusSensitive
|
lhs.isStatusSensitive == rhs.isStatusSensitive
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ extension PollItem {
|
||||||
|
|
||||||
enum VoteState: Equatable, Hashable {
|
enum VoteState: Equatable, Hashable {
|
||||||
case hidden
|
case hidden
|
||||||
case reveal(voted: Bool, percentage: Double)
|
case reveal(voted: Bool, percentage: Double, animated: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectState: SelectState
|
var selectState: SelectState
|
||||||
|
|
|
@ -24,7 +24,7 @@ extension PollSection {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PollOptionTableViewCell.self), for: indexPath) as! PollOptionTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PollOptionTableViewCell.self), for: indexPath) as! PollOptionTableViewCell
|
||||||
managedObjectContext.performAndWait {
|
managedObjectContext.performAndWait {
|
||||||
let option = managedObjectContext.object(with: objectID) as! PollOption
|
let option = managedObjectContext.object(with: objectID) as! PollOption
|
||||||
PollSection.configure(cell: cell, pollOption: option, itemAttribute: attribute)
|
PollSection.configure(cell: cell, pollOption: option, pollItemAttribute: attribute)
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
@ -35,12 +35,15 @@ extension PollSection {
|
||||||
extension PollSection {
|
extension PollSection {
|
||||||
static func configure(
|
static func configure(
|
||||||
cell: PollOptionTableViewCell,
|
cell: PollOptionTableViewCell,
|
||||||
pollOption: PollOption,
|
pollOption option: PollOption,
|
||||||
itemAttribute: PollItem.Attribute
|
pollItemAttribute attribute: PollItem.Attribute
|
||||||
) {
|
) {
|
||||||
cell.optionLabel.text = pollOption.title
|
cell.optionLabel.text = option.title
|
||||||
configure(cell: cell, selectState: itemAttribute.selectState)
|
configure(cell: cell, selectState: attribute.selectState)
|
||||||
configure(cell: cell, voteState: itemAttribute.voteState)
|
configure(cell: cell, voteState: attribute.voteState)
|
||||||
|
cell.attribute = attribute
|
||||||
|
cell.layoutIfNeeded()
|
||||||
|
cell.updateTextAppearance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,24 +67,18 @@ extension PollSection {
|
||||||
cell.checkmarkBackgroundView.isHidden = false
|
cell.checkmarkBackgroundView.isHidden = false
|
||||||
cell.checkmarkImageView.isHidden = false
|
cell.checkmarkImageView.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.selectState = state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func configure(cell: PollOptionTableViewCell, voteState state: PollItem.Attribute.VoteState) {
|
static func configure(cell: PollOptionTableViewCell, voteState state: PollItem.Attribute.VoteState) {
|
||||||
switch state {
|
switch state {
|
||||||
case .hidden:
|
case .hidden:
|
||||||
cell.optionPercentageLabel.isHidden = true
|
cell.optionPercentageLabel.isHidden = true
|
||||||
case .reveal(let voted, let percentage):
|
case .reveal(let voted, let percentage, let animated):
|
||||||
cell.optionPercentageLabel.isHidden = false
|
cell.optionPercentageLabel.isHidden = false
|
||||||
cell.optionPercentageLabel.text = String(Int(100 * percentage)) + "%"
|
cell.optionPercentageLabel.text = String(Int(100 * percentage)) + "%"
|
||||||
cell.voteProgressStripView.tintColor = voted ? Asset.Colors.Background.Poll.highlight.color : Asset.Colors.Background.Poll.disabled.color
|
cell.voteProgressStripView.tintColor = voted ? Asset.Colors.Background.Poll.highlight.color : Asset.Colors.Background.Poll.disabled.color
|
||||||
cell.voteProgressStripView.setProgress(CGFloat(percentage), animated: true)
|
cell.voteProgressStripView.setProgress(CGFloat(percentage), animated: animated)
|
||||||
}
|
}
|
||||||
cell.voteState = state
|
|
||||||
|
|
||||||
cell.layoutIfNeeded()
|
|
||||||
cell.updateTextAppearance()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ extension StatusSection {
|
||||||
// configure cell
|
// configure cell
|
||||||
managedObjectContext.performAndWait {
|
managedObjectContext.performAndWait {
|
||||||
let timelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex
|
let timelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex
|
||||||
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: timelineIndex.toot, requestUserID: timelineIndex.userID, statusContentWarningAttribute: attribute)
|
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: timelineIndex.toot, requestUserID: timelineIndex.userID, statusItemAttribute: attribute)
|
||||||
}
|
}
|
||||||
cell.delegate = statusTableViewCellDelegate
|
cell.delegate = statusTableViewCellDelegate
|
||||||
return cell
|
return cell
|
||||||
|
@ -45,7 +45,7 @@ extension StatusSection {
|
||||||
// configure cell
|
// configure cell
|
||||||
managedObjectContext.performAndWait {
|
managedObjectContext.performAndWait {
|
||||||
let toot = managedObjectContext.object(with: objectID) as! Toot
|
let toot = managedObjectContext.object(with: objectID) as! Toot
|
||||||
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: toot, requestUserID: requestUserID, statusContentWarningAttribute: attribute)
|
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: toot, requestUserID: requestUserID, statusItemAttribute: attribute)
|
||||||
}
|
}
|
||||||
cell.delegate = statusTableViewCellDelegate
|
cell.delegate = statusTableViewCellDelegate
|
||||||
return cell
|
return cell
|
||||||
|
@ -76,7 +76,7 @@ extension StatusSection {
|
||||||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
||||||
toot: Toot,
|
toot: Toot,
|
||||||
requestUserID: String,
|
requestUserID: String,
|
||||||
statusContentWarningAttribute: StatusContentWarningAttribute?
|
statusItemAttribute: Item.StatusAttribute
|
||||||
) {
|
) {
|
||||||
// set header
|
// set header
|
||||||
cell.statusView.headerContainerStackView.isHidden = toot.reblog == nil
|
cell.statusView.headerContainerStackView.isHidden = toot.reblog == nil
|
||||||
|
@ -99,7 +99,7 @@ extension StatusSection {
|
||||||
|
|
||||||
// set status text content warning
|
// set status text content warning
|
||||||
let spoilerText = (toot.reblog ?? toot).spoilerText ?? ""
|
let spoilerText = (toot.reblog ?? toot).spoilerText ?? ""
|
||||||
let isStatusTextSensitive = statusContentWarningAttribute?.isStatusTextSensitive ?? !spoilerText.isEmpty
|
let isStatusTextSensitive = statusItemAttribute.isStatusTextSensitive
|
||||||
cell.statusView.isStatusTextSensitive = isStatusTextSensitive
|
cell.statusView.isStatusTextSensitive = isStatusTextSensitive
|
||||||
cell.statusView.updateContentWarningDisplay(isHidden: !isStatusTextSensitive)
|
cell.statusView.updateContentWarningDisplay(isHidden: !isStatusTextSensitive)
|
||||||
cell.statusView.contentWarningTitle.text = {
|
cell.statusView.contentWarningTitle.text = {
|
||||||
|
@ -153,13 +153,19 @@ extension StatusSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cell.statusView.statusMosaicImageViewContainer.isHidden = mosiacImageViewModel.metas.isEmpty
|
cell.statusView.statusMosaicImageViewContainer.isHidden = mosiacImageViewModel.metas.isEmpty
|
||||||
let isStatusSensitive = statusContentWarningAttribute?.isStatusSensitive ?? (toot.reblog ?? toot).sensitive
|
let isStatusSensitive = statusItemAttribute.isStatusSensitive
|
||||||
cell.statusView.statusMosaicImageViewContainer.blurVisualEffectView.effect = isStatusSensitive ? MosaicImageViewContainer.blurVisualEffect : nil
|
cell.statusView.statusMosaicImageViewContainer.blurVisualEffectView.effect = isStatusSensitive ? MosaicImageViewContainer.blurVisualEffect : nil
|
||||||
cell.statusView.statusMosaicImageViewContainer.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0
|
cell.statusView.statusMosaicImageViewContainer.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0
|
||||||
|
|
||||||
// set poll
|
// set poll
|
||||||
let poll = (toot.reblog ?? toot).poll
|
let poll = (toot.reblog ?? toot).poll
|
||||||
configure(cell: cell, timestampUpdatePublisher: timestampUpdatePublisher, poll: poll, requestUserID: requestUserID)
|
StatusSection.configure(
|
||||||
|
cell: cell,
|
||||||
|
poll: poll,
|
||||||
|
requestUserID: requestUserID,
|
||||||
|
updateProgressAnimated: false,
|
||||||
|
timestampUpdatePublisher: timestampUpdatePublisher
|
||||||
|
)
|
||||||
if let poll = poll {
|
if let poll = poll {
|
||||||
ManagedObjectObserver.observe(object: poll)
|
ManagedObjectObserver.observe(object: poll)
|
||||||
.sink { _ in
|
.sink { _ in
|
||||||
|
@ -167,7 +173,13 @@ extension StatusSection {
|
||||||
} receiveValue: { change in
|
} receiveValue: { change in
|
||||||
guard case let .update(object) = change.changeType,
|
guard case let .update(object) = change.changeType,
|
||||||
let newPoll = object as? Poll else { return }
|
let newPoll = object as? Poll else { return }
|
||||||
StatusSection.configure(cell: cell, timestampUpdatePublisher: timestampUpdatePublisher, poll: newPoll, requestUserID: requestUserID)
|
StatusSection.configure(
|
||||||
|
cell: cell,
|
||||||
|
poll: newPoll,
|
||||||
|
requestUserID: requestUserID,
|
||||||
|
updateProgressAnimated: true,
|
||||||
|
timestampUpdatePublisher: timestampUpdatePublisher
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
}
|
}
|
||||||
|
@ -218,9 +230,10 @@ extension StatusSection {
|
||||||
|
|
||||||
static func configure(
|
static func configure(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusTableViewCell,
|
||||||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
|
||||||
poll: Poll?,
|
poll: Poll?,
|
||||||
requestUserID: String
|
requestUserID: String,
|
||||||
|
updateProgressAnimated: Bool,
|
||||||
|
timestampUpdatePublisher: AnyPublisher<Date, Never>
|
||||||
) {
|
) {
|
||||||
guard let poll = poll,
|
guard let poll = poll,
|
||||||
let managedObjectContext = poll.managedObjectContext else {
|
let managedObjectContext = poll.managedObjectContext else {
|
||||||
|
@ -302,7 +315,7 @@ extension StatusSection {
|
||||||
return Double(option.votesCount?.intValue ?? 0) / Double(poll.votesCount.intValue)
|
return Double(option.votesCount?.intValue ?? 0) / Double(poll.votesCount.intValue)
|
||||||
}()
|
}()
|
||||||
let voted = votedOptions.isEmpty ? true : votedOptions.contains(option)
|
let voted = votedOptions.isEmpty ? true : votedOptions.contains(option)
|
||||||
return .reveal(voted: voted, percentage: percentage)
|
return .reveal(voted: voted, percentage: percentage, animated: updateProgressAnimated)
|
||||||
}()
|
}()
|
||||||
return PollItem.Attribute(selectState: selectState, voteState: voteState)
|
return PollItem.Attribute(selectState: selectState, voteState: voteState)
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -75,6 +75,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
|
|
||||||
func statusTableViewCell(_ cell: StatusTableViewCell, pollTableView: PollTableView, didSelectRowAt indexPath: IndexPath) {
|
func statusTableViewCell(_ cell: StatusTableViewCell, pollTableView: PollTableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||||
guard let activeMastodonAuthentication = context.authenticationService.activeMastodonAuthentication.value else { return }
|
guard let activeMastodonAuthentication = context.authenticationService.activeMastodonAuthentication.value else { return }
|
||||||
|
|
||||||
guard let diffableDataSource = cell.statusView.pollTableViewDataSource else { return }
|
guard let diffableDataSource = cell.statusView.pollTableViewDataSource else { return }
|
||||||
|
@ -82,24 +83,38 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
guard case let .opion(objectID, attribute) = item else { return }
|
guard case let .opion(objectID, attribute) = item else { return }
|
||||||
guard let option = managedObjectContext.object(with: objectID) as? PollOption else { return }
|
guard let option = managedObjectContext.object(with: objectID) as? PollOption else { return }
|
||||||
|
|
||||||
|
let domain = option.poll.toot.domain
|
||||||
|
let pollObjectID = option.poll.objectID
|
||||||
|
|
||||||
if option.poll.multiple {
|
if option.poll.multiple {
|
||||||
var choices: [Int] = []
|
var choices: [Int] = []
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
let choices = [option.index.intValue]
|
||||||
context.apiService.vote(
|
context.apiService.vote(
|
||||||
pollObjectID: option.poll.objectID,
|
pollObjectID: option.poll.objectID,
|
||||||
mastodonUserObjectID: activeMastodonAuthentication.user.objectID,
|
mastodonUserObjectID: activeMastodonAuthentication.user.objectID,
|
||||||
choices: [option.index.intValue]
|
choices: [option.index.intValue]
|
||||||
)
|
)
|
||||||
|
.handleEvents(receiveOutput: { _ in
|
||||||
|
// TODO: add haptic
|
||||||
|
})
|
||||||
|
.flatMap { pollID -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> in
|
||||||
|
return self.context.apiService.vote(
|
||||||
|
domain: domain,
|
||||||
|
pollID: pollID,
|
||||||
|
pollObjectID: pollObjectID,
|
||||||
|
choices: choices,
|
||||||
|
mastodonAuthenticationBox: activeMastodonAuthenticationBox
|
||||||
|
)
|
||||||
|
}
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
|
|
||||||
} receiveValue: { pollID in
|
} receiveValue: { response in
|
||||||
|
print(response.value)
|
||||||
}
|
}
|
||||||
.store(in: &context.disposeBag)
|
.store(in: &context.disposeBag)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
||||||
|
|
||||||
// that's will be the most fastest fetch because of upstream just update and no modify needs consider
|
// that's will be the most fastest fetch because of upstream just update and no modify needs consider
|
||||||
|
|
||||||
var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusTimelineAttribute] = [:]
|
var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:]
|
||||||
|
|
||||||
for item in oldSnapshot.itemIdentifiers {
|
for item in oldSnapshot.itemIdentifiers {
|
||||||
guard case let .homeTimelineIndex(objectID, attribute) = item else { continue }
|
guard case let .homeTimelineIndex(objectID, attribute) = item else { continue }
|
||||||
|
@ -88,7 +88,7 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
||||||
guard let spoilerText = toot.spoilerText, !spoilerText.isEmpty else { return false }
|
guard let spoilerText = toot.spoilerText, !spoilerText.isEmpty else { return false }
|
||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
let attribute = oldSnapshotAttributeDict[timelineIndex.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: isStatusTextSensitive, isStatusSensitive: toot.sensitive)
|
let attribute = oldSnapshotAttributeDict[timelineIndex.objectID] ?? Item.StatusAttribute(isStatusTextSensitive: isStatusTextSensitive, isStatusSensitive: toot.sensitive)
|
||||||
|
|
||||||
// append new item into snapshot
|
// append new item into snapshot
|
||||||
newTimelineItems.append(.homeTimelineIndex(objectID: timelineIndex.objectID, attribute: attribute))
|
newTimelineItems.append(.homeTimelineIndex(objectID: timelineIndex.objectID, attribute: attribute))
|
||||||
|
|
|
@ -50,7 +50,7 @@ extension PublicTimelineViewModel: NSFetchedResultsControllerDelegate {
|
||||||
return indexes.firstIndex(of: toot.id).map { index in (index, toot) }
|
return indexes.firstIndex(of: toot.id).map { index in (index, toot) }
|
||||||
}
|
}
|
||||||
.sorted { $0.0 < $1.0 }
|
.sorted { $0.0 < $1.0 }
|
||||||
var oldSnapshotAttributeDict: [NSManagedObjectID: Item.StatusTimelineAttribute] = [:]
|
var oldSnapshotAttributeDict: [NSManagedObjectID: Item.StatusAttribute] = [:]
|
||||||
for item in self.items.value {
|
for item in self.items.value {
|
||||||
guard case let .toot(objectID, attribute) = item else { continue }
|
guard case let .toot(objectID, attribute) = item else { continue }
|
||||||
oldSnapshotAttributeDict[objectID] = attribute
|
oldSnapshotAttributeDict[objectID] = attribute
|
||||||
|
@ -63,7 +63,7 @@ extension PublicTimelineViewModel: NSFetchedResultsControllerDelegate {
|
||||||
guard let spoilerText = targetToot.spoilerText, !spoilerText.isEmpty else { return false }
|
guard let spoilerText = targetToot.spoilerText, !spoilerText.isEmpty else { return false }
|
||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
let attribute = oldSnapshotAttributeDict[toot.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: isStatusTextSensitive, isStatusSensitive: targetToot.sensitive)
|
let attribute = oldSnapshotAttributeDict[toot.objectID] ?? Item.StatusAttribute(isStatusTextSensitive: isStatusTextSensitive, isStatusSensitive: targetToot.sensitive)
|
||||||
items.append(Item.toot(objectID: toot.objectID, attribute: attribute))
|
items.append(Item.toot(objectID: toot.objectID, attribute: attribute))
|
||||||
if tootIDsWhichHasGap.contains(toot.id) {
|
if tootIDsWhichHasGap.contains(toot.id) {
|
||||||
items.append(Item.publicMiddleLoader(tootID: toot.id))
|
items.append(Item.publicMiddleLoader(tootID: toot.id))
|
||||||
|
|
|
@ -11,12 +11,15 @@ import Combine
|
||||||
|
|
||||||
private final class StripProgressLayer: CALayer {
|
private final class StripProgressLayer: CALayer {
|
||||||
|
|
||||||
|
static let progressAnimationKey = "progressAnimationKey"
|
||||||
|
static let progressKey = "progress"
|
||||||
|
|
||||||
var tintColor: UIColor = .black
|
var tintColor: UIColor = .black
|
||||||
@NSManaged var progress: CGFloat
|
@NSManaged var progress: CGFloat
|
||||||
|
|
||||||
override class func needsDisplay(forKey key: String) -> Bool {
|
override class func needsDisplay(forKey key: String) -> Bool {
|
||||||
switch key {
|
switch key {
|
||||||
case "progress":
|
case StripProgressLayer.progressKey:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return super.needsDisplay(forKey: key)
|
return super.needsDisplay(forKey: key)
|
||||||
|
@ -24,7 +27,13 @@ private final class StripProgressLayer: CALayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func display() {
|
override func display() {
|
||||||
let progress = presentation()?.progress ?? self.progress
|
let progress: CGFloat = {
|
||||||
|
guard animation(forKey: StripProgressLayer.progressAnimationKey) != nil else {
|
||||||
|
return self.progress
|
||||||
|
}
|
||||||
|
|
||||||
|
return presentation()?.progress ?? self.progress
|
||||||
|
}()
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: progress: %.2f", ((#file as NSString).lastPathComponent), #line, #function, progress)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: progress: %.2f", ((#file as NSString).lastPathComponent), #line, #function, progress)
|
||||||
|
|
||||||
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
|
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
|
||||||
|
@ -72,15 +81,15 @@ final class StripProgressView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setProgress(_ progress: CGFloat, animated: Bool) {
|
func setProgress(_ progress: CGFloat, animated: Bool) {
|
||||||
stripProgressLayer.removeAnimation(forKey: "progressAnimationKey")
|
stripProgressLayer.removeAnimation(forKey: StripProgressLayer.progressAnimationKey)
|
||||||
if animated {
|
if animated {
|
||||||
let animation = CABasicAnimation(keyPath: "progress")
|
let animation = CABasicAnimation(keyPath: StripProgressLayer.progressKey)
|
||||||
animation.fromValue = stripProgressLayer.presentation()?.progress ?? stripProgressLayer.progress
|
animation.fromValue = stripProgressLayer.presentation()?.progress ?? stripProgressLayer.progress
|
||||||
animation.toValue = progress
|
animation.toValue = progress
|
||||||
animation.duration = 0.33
|
animation.duration = 0.33
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||||
animation.isRemovedOnCompletion = true
|
animation.isRemovedOnCompletion = true
|
||||||
stripProgressLayer.add(animation, forKey: "progressAnimationKey")
|
stripProgressLayer.add(animation, forKey: StripProgressLayer.progressAnimationKey)
|
||||||
stripProgressLayer.progress = progress
|
stripProgressLayer.progress = progress
|
||||||
} else {
|
} else {
|
||||||
stripProgressLayer.progress = progress
|
stripProgressLayer.progress = progress
|
||||||
|
|
|
@ -16,8 +16,7 @@ final class PollOptionTableViewCell: UITableViewCell {
|
||||||
static let checkmarkImageSize = CGSize(width: 26, height: 26)
|
static let checkmarkImageSize = CGSize(width: 26, height: 26)
|
||||||
|
|
||||||
private var viewStateDisposeBag = Set<AnyCancellable>()
|
private var viewStateDisposeBag = Set<AnyCancellable>()
|
||||||
var selectState: PollItem.Attribute.SelectState = .off
|
var attribute: PollItem.Attribute?
|
||||||
var voteState: PollItem.Attribute.VoteState?
|
|
||||||
|
|
||||||
let roundedBackgroundView = UIView()
|
let roundedBackgroundView = UIView()
|
||||||
let voteProgressStripView: StripProgressView = {
|
let voteProgressStripView: StripProgressView = {
|
||||||
|
@ -73,7 +72,7 @@ final class PollOptionTableViewCell: UITableViewCell {
|
||||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
super.setSelected(selected, animated: animated)
|
super.setSelected(selected, animated: animated)
|
||||||
|
|
||||||
guard let voteState = voteState else { return }
|
guard let voteState = attribute?.voteState else { return }
|
||||||
switch voteState {
|
switch voteState {
|
||||||
case .hidden:
|
case .hidden:
|
||||||
let color = Asset.Colors.Background.systemGroupedBackground.color
|
let color = Asset.Colors.Background.systemGroupedBackground.color
|
||||||
|
@ -86,7 +85,7 @@ final class PollOptionTableViewCell: UITableViewCell {
|
||||||
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||||
super.setHighlighted(highlighted, animated: animated)
|
super.setHighlighted(highlighted, animated: animated)
|
||||||
|
|
||||||
guard let voteState = voteState else { return }
|
guard let voteState = attribute?.voteState else { return }
|
||||||
switch voteState {
|
switch voteState {
|
||||||
case .hidden:
|
case .hidden:
|
||||||
let color = Asset.Colors.Background.systemGroupedBackground.color
|
let color = Asset.Colors.Background.systemGroupedBackground.color
|
||||||
|
@ -189,7 +188,7 @@ extension PollOptionTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTextAppearance() {
|
func updateTextAppearance() {
|
||||||
guard let voteState = voteState else {
|
guard let voteState = attribute?.voteState else {
|
||||||
optionLabel.textColor = Asset.Colors.Label.primary.color
|
optionLabel.textColor = Asset.Colors.Label.primary.color
|
||||||
optionLabel.layer.removeShadow()
|
optionLabel.layer.removeShadow()
|
||||||
return
|
return
|
||||||
|
@ -199,7 +198,7 @@ extension PollOptionTableViewCell {
|
||||||
case .hidden:
|
case .hidden:
|
||||||
optionLabel.textColor = Asset.Colors.Label.primary.color
|
optionLabel.textColor = Asset.Colors.Label.primary.color
|
||||||
optionLabel.layer.removeShadow()
|
optionLabel.layer.removeShadow()
|
||||||
case .reveal(_, let percentage):
|
case .reveal(_, let percentage, _):
|
||||||
if CGFloat(percentage) * voteProgressStripView.frame.width > optionLabelMiddlePaddingView.frame.minX {
|
if CGFloat(percentage) * voteProgressStripView.frame.width > optionLabelMiddlePaddingView.frame.minX {
|
||||||
optionLabel.textColor = .white
|
optionLabel.textColor = .white
|
||||||
optionLabel.layer.setupShadow(x: 0, y: 0, blur: 4, spread: 0)
|
optionLabel.layer.setupShadow(x: 0, y: 0, blur: 4, spread: 0)
|
||||||
|
|
|
@ -135,7 +135,7 @@ extension APIService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
// send vote request to remote
|
/// send vote request to remote
|
||||||
func vote(
|
func vote(
|
||||||
domain: String,
|
domain: String,
|
||||||
pollID: Mastodon.Entity.Poll.ID,
|
pollID: Mastodon.Entity.Poll.ID,
|
||||||
|
|
Loading…
Reference in New Issue