Skip to content

Commit

Permalink
Refactor Polls to not use Core Data (#1265)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimar authored Apr 17, 2024
1 parent b120d32 commit 24e573d
Show file tree
Hide file tree
Showing 33 changed files with 399 additions and 432 deletions.
47 changes: 1 addition & 46 deletions Mastodon/Diffable/Status/StatusSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,54 +158,9 @@ extension StatusSection {
}()

cell.pollOptionView.viewModel.authContext = authContext

managedObjectContext.performAndWait {
guard let option = record.object(in: managedObjectContext) else {
assertionFailure()
return
}

cell.pollOptionView.configure(pollOption: option, status: statusView.viewModel.originalStatus)

// trigger update if needs
let needsUpdatePoll: Bool = {
// check first option in poll to trigger update poll only once
guard
let poll = option.poll,
option.index == 0
else { return false }

guard !poll.expired else {
return false
}

let now = Date()
let timeIntervalSinceUpdate = now.timeIntervalSince(poll.updatedAt)
#if DEBUG
let autoRefreshTimeInterval: TimeInterval = 3 // speedup testing
#else
let autoRefreshTimeInterval: TimeInterval = 30
#endif

guard timeIntervalSinceUpdate > autoRefreshTimeInterval else {
return false
}

return true
}()
cell.pollOptionView.configure(pollOption: record)

if needsUpdatePoll {
guard let poll = option.poll else { return }
let pollRecord: ManagedObjectRecord<Poll> = .init(objectID: poll.objectID)
Task { [weak context] in
guard let context = context else { return }
_ = try await context.apiService.poll(
poll: pollRecord,
authenticationBox: authContext.mastodonAuthenticationBox
)
}
}
} // end managedObjectContext.performAndWait
return cell
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,3 +523,70 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
} // end Task
}
}

// MARK: - poll
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {

func tableViewCell(
_ cell: UITableViewCell,
notificationView: NotificationView,
pollTableView tableView: UITableView,
didSelectRowAt indexPath: IndexPath
) {
guard let pollTableViewDiffableDataSource = notificationView.statusView.pollTableViewDiffableDataSource else { return }
guard let pollItem = pollTableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return }

guard case let .option(pollOption) = pollItem else {
assertionFailure("only works for status data provider")
return
}

let poll = pollOption.poll

if !poll.multiple {
poll.options.forEach { $0.isSelected = false }
pollOption.isSelected = true
} else {
pollOption.isSelected.toggle()
}
}

func tableViewCell(
_ cell: UITableViewCell,
notificationView: NotificationView,
pollVoteButtonPressed button: UIButton
) {
guard let pollTableViewDiffableDataSource = notificationView.statusView.pollTableViewDiffableDataSource else { return }
guard let firstPollItem = pollTableViewDiffableDataSource.snapshot().itemIdentifiers.first else { return }
guard case let .option(firstPollOption) = firstPollItem else { return }

notificationView.statusView.viewModel.isVoting = true

Task { @MainActor in
let poll = firstPollOption.poll

let choices = poll.options
.filter { $0.isSelected == true }
.compactMap { poll.options.firstIndex(of: $0) }

do {
let newPoll = try await context.apiService.vote(
poll: poll.entity,
choices: choices,
authenticationBox: authContext.mastodonAuthenticationBox
).value

guard let entity = poll.status?.entity else { return }

let newStatus: MastodonStatus = .fromEntity(entity)
newStatus.poll = MastodonPoll(poll: newPoll, status: newStatus)

self.update(status: newStatus, intent: .pollVote)
} catch {
notificationView.statusView.viewModel.isVoting = false
}

} // end Task
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import MastodonUI
import MastodonLocalization
import MastodonAsset
import LinkPresentation
import MastodonSDK

// MARK: - header
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
Expand Down Expand Up @@ -263,66 +264,20 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
) {
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
guard let pollItem = pollTableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return }

let managedObjectContext = context.managedObjectContext

Task {
guard case let .option(pollOption) = pollItem else {
assertionFailure("only works for status data provider")
return
}

var _poll: ManagedObjectRecord<Poll>?
var _isMultiple: Bool?
var _choice: Int?

try await managedObjectContext.performChanges {
guard let pollOption = pollOption.object(in: managedObjectContext) else { return }
guard let poll = pollOption.poll else { return }
_poll = .init(objectID: poll.objectID)

_isMultiple = poll.multiple
guard !poll.isVoting else { return }

if !poll.multiple {
for option in poll.options where option != pollOption {
option.update(isSelected: false)
}

// mark voting
poll.update(isVoting: true)
// set choice
_choice = Int(pollOption.index)
}

pollOption.update(isSelected: !pollOption.isSelected)
poll.update(updatedAt: Date())
}

// Trigger vote API request for
guard let poll = _poll,
_isMultiple == false,
let choice = _choice
else { return }

do {
_ = try await context.apiService.vote(
poll: poll,
choices: [choice],
authenticationBox: authContext.mastodonAuthenticationBox
)
} catch {
// restore voting state
try await managedObjectContext.performChanges {
guard
let pollOption = pollOption.object(in: managedObjectContext),
let poll = pollOption.poll
else { return }
poll.update(isVoting: false)
}
}

} // end Task
guard case let .option(pollOption) = pollItem else {
assertionFailure("only works for status data provider")
return
}

let poll = pollOption.poll

if !poll.multiple {
poll.options.forEach { $0.isSelected = false }
pollOption.isSelected = true
} else {
pollOption.isSelected.toggle()
}
}

func tableViewCell(
Expand All @@ -333,46 +288,31 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
guard let firstPollItem = pollTableViewDiffableDataSource.snapshot().itemIdentifiers.first else { return }
guard case let .option(firstPollOption) = firstPollItem else { return }

let managedObjectContext = context.managedObjectContext

Task {
var _poll: ManagedObjectRecord<Poll>?
var _choices: [Int]?

try await managedObjectContext.performChanges {
guard let poll = firstPollOption.object(in: managedObjectContext)?.poll else { return }
_poll = .init(objectID: poll.objectID)

guard poll.multiple else { return }

// mark voting
poll.update(isVoting: true)
// set choice
_choices = poll.options
.filter { $0.isSelected }
.map { Int($0.index) }

poll.update(updatedAt: Date())
}

// Trigger vote API request for
guard let poll = _poll,
let choices = _choices
else { return }


statusView.viewModel.isVoting = true

Task { @MainActor in
let poll = firstPollOption.poll

let choices = poll.options
.filter { $0.isSelected == true }
.compactMap { poll.options.firstIndex(of: $0) }

do {
_ = try await context.apiService.vote(
poll: poll,
let newPoll = try await context.apiService.vote(
poll: poll.entity,
choices: choices,
authenticationBox: authContext.mastodonAuthenticationBox
)
).value

guard let entity = poll.status?.entity else { return }

let newStatus: MastodonStatus = .fromEntity(entity)
newStatus.poll = MastodonPoll(poll: newPoll, status: newStatus)

self.update(status: newStatus, intent: .pollVote)
} catch {
// restore voting state
try await managedObjectContext.performChanges {
guard let poll = poll.object(in: managedObjectContext) else { return }
poll.update(isVoting: false)
}
statusView.viewModel.isVoting = false
}

} // end Task
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ protocol NotificationTableViewCellDelegate: AnyObject, AutoGenerateProtocolDeleg
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, pollVoteButtonPressed button: UIButton)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, quoteStatusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, quoteStatusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
Expand Down Expand Up @@ -70,6 +72,14 @@ extension NotificationViewDelegate where Self: NotificationViewContainerTableVie
func notificationView(_ notificationView: NotificationView, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) {
delegate?.tableViewCell(self, notificationView: notificationView, statusView: statusView, mediaGridContainerView: mediaGridContainerView, mediaView: mediaView, didSelectMediaViewAt: index)
}

func notificationView(_ notificationView: NotificationView, statusView: StatusView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.tableViewCell(self, notificationView: notificationView, pollTableView: tableView, didSelectRowAt: indexPath)
}

func notificationView(_ notificationView: NotificationView, statusView: StatusView, pollVoteButtonPressed button: UIButton) {
delegate?.tableViewCell(self, notificationView: notificationView, pollVoteButtonPressed: button)
}

func notificationView(_ notificationView: NotificationView, statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action) {
delegate?.tableViewCell(self, notificationView: notificationView, statusView: statusView, actionToolbarContainer: actionToolbarContainer, buttonDidPressed: button, action: action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public protocol NotificationViewDelegate: AnyObject {
func notificationView(_ notificationView: NotificationView, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
func notificationView(_ notificationView: NotificationView, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView)
func notificationView(_ notificationView: NotificationView, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int)

func notificationView(_ notificationView: NotificationView, statusView: StatusView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath)
func notificationView(_ notificationView: NotificationView, statusView: StatusView, pollVoteButtonPressed button: UIButton)
func notificationView(_ notificationView: NotificationView, statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)

func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton)
Expand Down Expand Up @@ -547,11 +548,11 @@ extension NotificationView: StatusViewDelegate {
}

public func statusView(_ statusView: StatusView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
assertionFailure()
delegate?.notificationView(self, statusView: statusView, pollTableView: tableView, didSelectRowAt: indexPath)
}

public func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) {
assertionFailure()
delegate?.notificationView(self, statusView: statusView, pollVoteButtonPressed: button)
}

public func statusView(_ statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action) {
Expand Down
Loading

0 comments on commit 24e573d

Please sign in to comment.