feat: implement multiple poll
This commit is contained in:
parent
d79666679a
commit
0df1a57865
|
@ -245,7 +245,6 @@ extension StatusSection {
|
|||
|
||||
cell.statusView.pollTableView.isHidden = false
|
||||
cell.statusView.pollStatusStackView.isHidden = false
|
||||
cell.statusView.pollVoteButton.isHidden = !poll.multiple
|
||||
cell.statusView.pollVoteCountLabel.text = {
|
||||
if poll.multiple {
|
||||
let count = poll.votersCount?.intValue ?? 0
|
||||
|
@ -279,7 +278,14 @@ extension StatusSection {
|
|||
}
|
||||
|
||||
cell.statusView.pollTableView.allowsSelection = !poll.expired
|
||||
cell.statusView.pollTableView.allowsMultipleSelection = poll.multiple
|
||||
|
||||
let votedOptions = poll.options.filter { option in
|
||||
(option.votedBy ?? Set()).map { $0.id }.contains(requestUserID)
|
||||
}
|
||||
let didVotedLocal = !votedOptions.isEmpty
|
||||
let didVotedRemote = (poll.votedBy ?? Set()).map { $0.id }.contains(requestUserID)
|
||||
cell.statusView.pollVoteButton.isEnabled = didVotedLocal
|
||||
cell.statusView.pollVoteButton.isHidden = !poll.multiple ? true : (didVotedRemote || poll.expired)
|
||||
|
||||
cell.statusView.pollTableViewDataSource = PollSection.tableViewDiffableDataSource(
|
||||
for: cell.statusView.pollTableView,
|
||||
|
@ -288,21 +294,18 @@ extension StatusSection {
|
|||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<PollSection, PollItem>()
|
||||
snapshot.appendSections([.main])
|
||||
let votedOptions = poll.options.filter { option in
|
||||
(option.votedBy ?? Set()).map { $0.id }.contains(requestUserID)
|
||||
}
|
||||
let isPollVoted = (poll.votedBy ?? Set()).map { $0.id }.contains(requestUserID)
|
||||
|
||||
let pollItems = poll.options
|
||||
.sorted(by: { $0.index.intValue < $1.index.intValue })
|
||||
.map { option -> PollItem in
|
||||
let attribute: PollItem.Attribute = {
|
||||
let selectState: PollItem.Attribute.SelectState = {
|
||||
// make isPollVoted check later to make the local change possible
|
||||
// check didVotedRemote later to make the local change possible
|
||||
if !votedOptions.isEmpty {
|
||||
return votedOptions.contains(option) ? .on : .off
|
||||
} else if poll.expired {
|
||||
return .none
|
||||
} else if isPollVoted, votedOptions.isEmpty {
|
||||
} else if didVotedRemote, votedOptions.isEmpty {
|
||||
return .none
|
||||
} else {
|
||||
return .off
|
||||
|
@ -312,7 +315,7 @@ extension StatusSection {
|
|||
var needsReveal: Bool
|
||||
if poll.expired {
|
||||
needsReveal = true
|
||||
} else if isPollVoted {
|
||||
} else if didVotedRemote {
|
||||
needsReveal = true
|
||||
} else {
|
||||
needsReveal = false
|
||||
|
|
|
@ -74,6 +74,45 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
// MARK: - PollTableView
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton) {
|
||||
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
toot(for: cell, indexPath: nil)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.setFailureType(to: Error.self)
|
||||
.compactMap { toot -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error>? in
|
||||
guard let toot = (toot?.reblog ?? toot) else { return nil }
|
||||
guard let poll = toot.poll else { return nil }
|
||||
|
||||
let votedOptions = poll.options.filter { ($0.votedBy ?? Set()).contains(where: { $0.id == activeMastodonAuthenticationBox.userID }) }
|
||||
let choices = votedOptions.map { $0.index.intValue }
|
||||
let domain = poll.toot.domain
|
||||
|
||||
button.isEnabled = false
|
||||
|
||||
return self.context.apiService.vote(
|
||||
domain: domain,
|
||||
pollID: poll.id,
|
||||
pollObjectID: poll.objectID,
|
||||
choices: choices,
|
||||
mastodonAuthenticationBox: activeMastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
.switchToLatest()
|
||||
.sink(receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
// TODO: handle error
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: multiple vote fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
button.isEnabled = true
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}, receiveValue: { response in
|
||||
// do nothing
|
||||
})
|
||||
.store(in: &context.disposeBag)
|
||||
}
|
||||
|
||||
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 }
|
||||
|
@ -83,16 +122,37 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
guard case let .opion(objectID, attribute) = item else { return }
|
||||
guard let option = managedObjectContext.object(with: objectID) as? PollOption else { return }
|
||||
|
||||
let domain = option.poll.toot.domain
|
||||
let poll = option.poll
|
||||
let pollObjectID = option.poll.objectID
|
||||
let domain = poll.toot.domain
|
||||
|
||||
if option.poll.multiple {
|
||||
var choices: [Int] = []
|
||||
|
||||
if poll.multiple {
|
||||
var votedOptions = poll.options.filter { ($0.votedBy ?? Set()).contains(where: { $0.id == activeMastodonAuthenticationBox.userID }) }
|
||||
if votedOptions.contains(option) {
|
||||
votedOptions.remove(option)
|
||||
} else {
|
||||
votedOptions.insert(option)
|
||||
}
|
||||
let choices = votedOptions.map { $0.index.intValue }
|
||||
context.apiService.vote(
|
||||
pollObjectID: option.poll.objectID,
|
||||
mastodonUserObjectID: activeMastodonAuthentication.user.objectID,
|
||||
choices: choices
|
||||
)
|
||||
.handleEvents(receiveOutput: { _ in
|
||||
// TODO: add haptic
|
||||
})
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { completion in
|
||||
// Do nothing
|
||||
} receiveValue: { _ in
|
||||
// Do nothing
|
||||
}
|
||||
.store(in: &context.disposeBag)
|
||||
} else {
|
||||
let choices = [option.index.intValue]
|
||||
context.apiService.vote(
|
||||
pollObjectID: option.poll.objectID,
|
||||
pollObjectID: pollObjectID,
|
||||
mastodonUserObjectID: activeMastodonAuthentication.user.objectID,
|
||||
choices: [option.index.intValue]
|
||||
)
|
||||
|
|
|
@ -13,6 +13,7 @@ import AlamofireImage
|
|||
|
||||
protocol StatusViewDelegate: class {
|
||||
func statusView(_ statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
||||
}
|
||||
|
||||
final class StatusView: UIView {
|
||||
|
@ -138,11 +139,12 @@ final class StatusView: UIView {
|
|||
}()
|
||||
let pollVoteButton: UIButton = {
|
||||
let button = HitTestExpandedButton()
|
||||
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14, weight: .regular))
|
||||
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14, weight: .semibold))
|
||||
button.setTitle(L10n.Common.Controls.Status.Poll.vote, for: .normal)
|
||||
button.setTitleColor(Asset.Colors.Button.highlight.color, for: .normal)
|
||||
button.setTitleColor(Asset.Colors.Button.highlight.color.withAlphaComponent(0.8), for: .highlighted)
|
||||
button.setTitleColor(Asset.Colors.Button.disabled.color, for: .disabled)
|
||||
button.isEnabled = false
|
||||
return button
|
||||
}()
|
||||
|
||||
|
@ -350,6 +352,7 @@ extension StatusView {
|
|||
statusContentWarningContainerStackViewBottomLayoutConstraint.isActive = false
|
||||
|
||||
contentWarningActionButton.addTarget(self, action: #selector(StatusView.contentWarningActionButtonPressed(_:)), for: .touchUpInside)
|
||||
pollVoteButton.addTarget(self, action: #selector(StatusView.pollVoteButtonPressed(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -385,10 +388,17 @@ extension StatusView {
|
|||
}
|
||||
|
||||
extension StatusView {
|
||||
|
||||
@objc private func contentWarningActionButtonPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.statusView(self, contentWarningActionButtonPressed: sender)
|
||||
}
|
||||
|
||||
@objc private func pollVoteButtonPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.statusView(self, pollVoteButtonPressed: sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AvatarConfigurableView
|
||||
|
|
|
@ -13,15 +13,16 @@ import CoreData
|
|||
import CoreDataStack
|
||||
|
||||
protocol StatusTableViewCellDelegate: class {
|
||||
var context: AppContext! { get}
|
||||
var context: AppContext! { get }
|
||||
var managedObjectContext: NSManagedObjectContext { get }
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, pollTableView: PollTableView, didSelectRowAt indexPath: IndexPath)
|
||||
|
||||
}
|
||||
|
||||
final class StatusTableViewCell: UITableViewCell {
|
||||
|
@ -101,6 +102,7 @@ extension StatusTableViewCell {
|
|||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension StatusTableViewCell: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||
if tableView === statusView.pollTableView, let diffableDataSource = statusView.pollTableViewDataSource {
|
||||
var pollID: String?
|
||||
|
@ -115,6 +117,7 @@ extension StatusTableViewCell: UITableViewDelegate {
|
|||
pollID = option.poll.id
|
||||
return !option.poll.expired
|
||||
} else {
|
||||
assertionFailure()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -143,20 +146,31 @@ extension StatusTableViewCell: UITableViewDelegate {
|
|||
(option.votedBy ?? Set()).map { $0.id }.contains(userID)
|
||||
}
|
||||
let didVotedLocal = !votedOptions.isEmpty
|
||||
guard !option.poll.expired, !didVotedRemote, !didVotedLocal else {
|
||||
return nil
|
||||
|
||||
if poll.multiple {
|
||||
guard !option.poll.expired, !didVotedRemote else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
guard !option.poll.expired, !didVotedRemote, !didVotedLocal else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return indexPath
|
||||
} else {
|
||||
assertionFailure()
|
||||
return indexPath
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if tableView === statusView.pollTableView {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||
delegate?.statusTableViewCell(self, pollTableView: statusView.pollTableView, didSelectRowAt: indexPath)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,9 +178,15 @@ extension StatusTableViewCell: UITableViewDelegate {
|
|||
|
||||
// MARK: - StatusViewDelegate
|
||||
extension StatusTableViewCell: StatusViewDelegate {
|
||||
|
||||
func statusView(_ statusView: StatusView, contentWarningActionButtonPressed button: UIButton) {
|
||||
delegate?.statusTableViewCell(self, statusView: statusView, contentWarningActionButtonPressed: button)
|
||||
}
|
||||
|
||||
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) {
|
||||
delegate?.statusTableViewCell(self, statusView: statusView, pollVoteButtonPressed: button)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MosaicImageViewDelegate
|
||||
|
|
|
@ -101,11 +101,13 @@ extension APIService {
|
|||
let votedOptions = poll.options.filter { option in
|
||||
(option.votedBy ?? Set()).map { $0.id }.contains(mastodonUser.id)
|
||||
}
|
||||
guard votedOptions.isEmpty else {
|
||||
// if did voted. Do not allow vote again
|
||||
|
||||
if !poll.multiple, !votedOptions.isEmpty {
|
||||
// if did voted for single poll. Do not allow vote again
|
||||
didVotedLocal = true
|
||||
return
|
||||
}
|
||||
|
||||
for option in options {
|
||||
let voted = choices.contains(option.index.intValue)
|
||||
option.update(voted: voted, by: mastodonUser)
|
||||
|
|
Loading…
Reference in New Issue