Compare commits

..

3 Commits

5 changed files with 56 additions and 14 deletions

View File

@ -65,15 +65,15 @@ class MastodonCachePersistentStore: NSPersistentContainer {
} }
@discardableResult @discardableResult
private func upsert(status: Status, incrementReferenceCount: Bool) -> StatusMO { private func upsert(status: Status, incrementReferenceCount: Bool, context: NSManagedObjectContext) -> StatusMO {
if let statusMO = self.status(for: status.id, in: self.backgroundContext) { if let statusMO = self.status(for: status.id, in: context) {
statusMO.updateFrom(apiStatus: status, container: self) statusMO.updateFrom(apiStatus: status, container: self)
if incrementReferenceCount { if incrementReferenceCount {
statusMO.incrementReferenceCount() statusMO.incrementReferenceCount()
} }
return statusMO return statusMO
} else { } else {
let statusMO = StatusMO(apiStatus: status, container: self, context: self.backgroundContext) let statusMO = StatusMO(apiStatus: status, container: self, context: context)
if incrementReferenceCount { if incrementReferenceCount {
statusMO.incrementReferenceCount() statusMO.incrementReferenceCount()
} }
@ -81,11 +81,12 @@ class MastodonCachePersistentStore: NSPersistentContainer {
} }
} }
func addOrUpdate(status: Status, incrementReferenceCount: Bool, completion: ((StatusMO) -> Void)? = nil) { func addOrUpdate(status: Status, incrementReferenceCount: Bool, context: NSManagedObjectContext? = nil, completion: ((StatusMO) -> Void)? = nil) {
backgroundContext.perform { let context = context ?? backgroundContext
let statusMO = self.upsert(status: status, incrementReferenceCount: incrementReferenceCount) context.perform {
if self.backgroundContext.hasChanges { let statusMO = self.upsert(status: status, incrementReferenceCount: incrementReferenceCount, context: context)
try! self.backgroundContext.save() if context.hasChanges {
try! context.save()
} }
completion?(statusMO) completion?(statusMO)
self.statusSubject.send(status.id) self.statusSubject.send(status.id)
@ -94,7 +95,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addAll(statuses: [Status], completion: (() -> Void)? = nil) { func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) } statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) }
if self.backgroundContext.hasChanges { if self.backgroundContext.hasChanges {
try! self.backgroundContext.save() try! self.backgroundContext.save()
} }
@ -194,7 +195,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
// filter out mentions, otherwise we would double increment the reference count of those accounts // filter out mentions, otherwise we would double increment the reference count of those accounts
// since the status has the same account as the notification // since the status has the same account as the notification
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account } let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) } statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) }
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) } accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
if self.backgroundContext.hasChanges { if self.backgroundContext.hasChanges {
try! self.backgroundContext.save() try! self.backgroundContext.save()
@ -214,7 +215,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) } accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
updatedAccounts.append(contentsOf: accounts.map { $0.id }) updatedAccounts.append(contentsOf: accounts.map { $0.id })
}, { (statuses) in }, { (statuses) in
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) } statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) }
updatedStatuses.append(contentsOf: statuses.map { $0.id }) updatedStatuses.append(contentsOf: statuses.map { $0.id })
}) })

View File

@ -176,7 +176,7 @@ extension MenuPreviewProvider {
if mastodonController.account != nil && mastodonController.account.id == status.account.id { if mastodonController.account != nil && mastodonController.account.id == status.account.id {
let pinned = status.pinned ?? false let pinned = status.pinned ?? false
actionsSection.append(createAction(identifier: "", title: pinned ? "Unpin" : "Pin", systemImageName: pinned ? "pin.slash" : "pin", handler: { [weak self] (_) in actionsSection.append(createAction(identifier: "pin", title: pinned ? "Unpin" : "Pin", systemImageName: pinned ? "pin.slash" : "pin", handler: { [weak self] (_) in
guard let self = self else { return } guard let self = self else { return }
let request = (pinned ? Status.unpin : Status.pin)(status.id) let request = (pinned ? Status.unpin : Status.pin)(status.id)
self.mastodonController?.run(request, completion: { [weak self] (response) in self.mastodonController?.run(request, completion: { [weak self] (response) in
@ -188,6 +188,20 @@ extension MenuPreviewProvider {
})) }))
} }
if status.poll != nil {
actionsSection.insert(createAction(identifier: "refresh", title: "Refresh Poll", systemImageName: "arrow.clockwise", handler: { [weak self] (_) in
guard let mastodonController = self?.mastodonController else { return }
let request = Client.getStatus(id: status.id)
mastodonController.run(request, completion: { (response) in
if case let .success(status, _) = response {
// todo: this shouldn't really use the viewContext, but for some reason saving the
// backgroundContext with the new version of the status isn't updating the viewContext
mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false, context: mastodonController.persistentContainer.viewContext)
}
})
}), at: 0)
}
var shareSection = [ var shareSection = [
openInSafariAction(url: status.url!), openInSafariAction(url: status.url!),
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in

View File

@ -34,8 +34,19 @@ class PollOptionView: UIView {
label.setEmojis(poll.emojis, identifier: poll.id) label.setEmojis(poll.emojis, identifier: poll.id)
addSubview(label) addSubview(label)
let percentLabel = UILabel()
percentLabel.translatesAutoresizingMaskIntoConstraints = false
percentLabel.text = "100%"
percentLabel.isHidden = true
addSubview(percentLabel)
if (poll.voted ?? false) || poll.effectiveExpired, if (poll.voted ?? false) || poll.effectiveExpired,
let optionVotes = option.votesCount { let optionVotes = option.votesCount {
let frac = poll.votesCount == 0 ? 0 : CGFloat(optionVotes) / CGFloat(poll.votesCount)
percentLabel.isHidden = false
percentLabel.text = String(format: "%.0f%%", frac * 100)
let fillView = UIView() let fillView = UIView()
fillView.translatesAutoresizingMaskIntoConstraints = false fillView.translatesAutoresizingMaskIntoConstraints = false
fillView.backgroundColor = tintColor.withAlphaComponent(0.6) fillView.backgroundColor = tintColor.withAlphaComponent(0.6)
@ -45,7 +56,7 @@ class PollOptionView: UIView {
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
fillView.leadingAnchor.constraint(equalTo: leadingAnchor), fillView.leadingAnchor.constraint(equalTo: leadingAnchor),
fillView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: poll.votesCount == 0 ? 0 : CGFloat(optionVotes) / CGFloat(poll.votesCount)), 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),
]) ])
@ -65,7 +76,11 @@ class PollOptionView: UIView {
label.topAnchor.constraint(equalTo: topAnchor), label.topAnchor.constraint(equalTo: topAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor), label.bottomAnchor.constraint(equalTo: bottomAnchor),
label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 8), label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 8),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), label.trailingAnchor.constraint(equalTo: percentLabel.leadingAnchor),
percentLabel.topAnchor.constraint(equalTo: topAnchor),
percentLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
]) ])
} }

View File

@ -26,6 +26,8 @@ class PollOptionsView: UIControl {
private let animationDuration: TimeInterval = 0.1 private let animationDuration: TimeInterval = 0.1
private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95) private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
private let generator = UIImpactFeedbackGenerator(style: .soft)
override var isEnabled: Bool { override var isEnabled: Bool {
didSet { didSet {
options.forEach { $0.checkbox.readOnly = !isEnabled } options.forEach { $0.checkbox.readOnly = !isEnabled }
@ -106,6 +108,9 @@ class PollOptionsView: UIControl {
} }
animator.startAnimation() animator.startAnimation()
generator.impactOccurred()
generator.prepare()
return true return true
} }
} }
@ -130,6 +135,11 @@ class PollOptionsView: UIControl {
view.transform = index == newIndex ? self.scaledTransform : .identity view.transform = index == newIndex ? self.scaledTransform : .identity
} }
} }
if newIndex != nil {
generator.impactOccurred()
generator.prepare()
}
} }
return true return true

View File

@ -136,6 +136,8 @@ class StatusPollView: UIView {
voteButton.isEnabled = false voteButton.isEnabled = false
voteButton.setTitle("Voted", for: .disabled) voteButton.setTitle("Voted", for: .disabled)
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
let request = Poll.vote(poll.id, choices: optionsView.checkedOptionIndices) let request = Poll.vote(poll.id, choices: optionsView.checkedOptionIndices)
mastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
switch response { switch response {