feat: make toot poll display
This commit is contained in:
parent
8b63c2fda1
commit
aea2ddc078
|
@ -150,7 +150,7 @@
|
|||
<relationship name="favouritedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||
<relationship name="homeTimelineIndexes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="HomeTimelineIndex" inverseName="toot" inverseEntity="HomeTimelineIndex"/>
|
||||
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="toot" inverseEntity="Attachment"/>
|
||||
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Mention" inverseName="toot" inverseEntity="Mention"/>
|
||||
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Mention" inverseName="toot" inverseEntity="Mention"/>
|
||||
<relationship name="mutedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedToot" inverseEntity="MastodonUser"/>
|
||||
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="toot" inverseEntity="Poll"/>
|
||||
|
@ -168,9 +168,9 @@
|
|||
<element name="MastodonAuthentication" positionX="18" positionY="162" width="128" height="209"/>
|
||||
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="299"/>
|
||||
<element name="Mention" positionX="9" positionY="108" width="128" height="134"/>
|
||||
<element name="Tag" positionX="18" positionY="117" width="128" height="119"/>
|
||||
<element name="Toot" positionX="0" positionY="0" width="128" height="539"/>
|
||||
<element name="Poll" positionX="72" positionY="162" width="128" height="179"/>
|
||||
<element name="PollOption" positionX="81" positionY="171" width="128" height="134"/>
|
||||
<element name="Tag" positionX="18" positionY="117" width="128" height="119"/>
|
||||
<element name="Toot" positionX="0" positionY="0" width="128" height="539"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -153,7 +153,7 @@
|
|||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */; };
|
||||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; };
|
||||
DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */; };
|
||||
DB92CF7225E7BB98002C1017 /* PollTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB92CF7125E7BB98002C1017 /* PollTableViewCell.swift */; };
|
||||
DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB92CF7125E7BB98002C1017 /* PollOptionTableViewCell.swift */; };
|
||||
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; };
|
||||
DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; };
|
||||
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; };
|
||||
|
@ -379,7 +379,7 @@
|
|||
DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = "<group>"; };
|
||||
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
|
||||
DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineIndex.swift; sourceTree = "<group>"; };
|
||||
DB92CF7125E7BB98002C1017 /* PollTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB92CF7125E7BB98002C1017 /* PollOptionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = "<group>"; };
|
||||
DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = "<group>"; };
|
||||
DB98337E25C9452D00AD9700 /* APIService+APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+APIError.swift"; sourceTree = "<group>"; };
|
||||
|
@ -684,7 +684,7 @@
|
|||
2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */,
|
||||
2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */,
|
||||
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */,
|
||||
DB92CF7125E7BB98002C1017 /* PollTableViewCell.swift */,
|
||||
DB92CF7125E7BB98002C1017 /* PollOptionTableViewCell.swift */,
|
||||
);
|
||||
path = TableviewCell;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1469,7 +1469,7 @@
|
|||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
||||
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
||||
DB92CF7225E7BB98002C1017 /* PollTableViewCell.swift in Sources */,
|
||||
DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */,
|
||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
||||
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
||||
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||
|
|
|
@ -9,7 +9,7 @@ import Foundation
|
|||
import CoreData
|
||||
|
||||
enum PollItem {
|
||||
case pollOpion(objectID: NSManagedObjectID, attribute: Attribute)
|
||||
case opion(objectID: NSManagedObjectID, attribute: Attribute)
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,6 +17,10 @@ extension PollItem {
|
|||
class Attribute: Hashable {
|
||||
var voted: Bool = false
|
||||
|
||||
init(voted: Bool = false) {
|
||||
self.voted = voted
|
||||
}
|
||||
|
||||
static func == (lhs: PollItem.Attribute, rhs: PollItem.Attribute) -> Bool {
|
||||
return lhs.voted == rhs.voted
|
||||
}
|
||||
|
@ -30,10 +34,8 @@ extension PollItem {
|
|||
extension PollItem: Equatable {
|
||||
static func == (lhs: PollItem, rhs: PollItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.pollOpion(let objectIDLeft, _), .pollOpion(let objectIDRight, _)):
|
||||
case (.opion(let objectIDLeft, _), .opion(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +44,7 @@ extension PollItem: Equatable {
|
|||
extension PollItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .pollOpion(let objectID, _):
|
||||
case .opion(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import UIKit
|
|||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
enum PollSection {
|
||||
enum PollSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,25 @@ extension PollSection {
|
|||
managedObjectContext: NSManagedObjectContext
|
||||
) -> UITableViewDiffableDataSource<PollSection, PollItem> {
|
||||
return UITableViewDiffableDataSource<PollSection, PollItem>(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
return nil
|
||||
switch item {
|
||||
case .opion(let objectID, let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PollOptionTableViewCell.self), for: indexPath) as! PollOptionTableViewCell
|
||||
managedObjectContext.performAndWait {
|
||||
let option = managedObjectContext.object(with: objectID) as! PollOption
|
||||
PollSection.configure(cell: cell, pollOption: option, itemAttribute: attribute)
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PollSection {
|
||||
static func configure(
|
||||
cell: PollOptionTableViewCell,
|
||||
pollOption: PollOption,
|
||||
itemAttribute: PollItem.Attribute
|
||||
) {
|
||||
cell.optionLabel.text = pollOption.title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,31 @@ extension StatusSection {
|
|||
cell.statusView.statusMosaicImageView.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0
|
||||
|
||||
// set poll
|
||||
|
||||
if let poll = (toot.reblog ?? toot).poll {
|
||||
cell.statusView.statusPollTableView.isHidden = false
|
||||
|
||||
let managedObjectContext = toot.managedObjectContext!
|
||||
cell.statusView.statusPollTableViewDataSource = PollSection.tableViewDiffableDataSource(
|
||||
for: cell.statusView.statusPollTableView,
|
||||
managedObjectContext: managedObjectContext
|
||||
)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<PollSection, PollItem>()
|
||||
snapshot.appendSections([.main])
|
||||
let pollItems = poll.options
|
||||
.sorted(by: { $0.index.intValue < $1.index.intValue })
|
||||
.map { option -> PollItem in
|
||||
let isVoted = (option.votedBy ?? Set()).map { $0.id }.contains(requestUserID)
|
||||
let attribute = PollItem.Attribute(voted: isVoted)
|
||||
let option = PollItem.opion(objectID: option.objectID, attribute: attribute)
|
||||
return option
|
||||
}
|
||||
snapshot.appendItems(pollItems, toSection: .main)
|
||||
cell.statusView.statusPollTableViewDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||
// cell.statusView.statusPollTableView.layoutIfNeeded()
|
||||
} else {
|
||||
cell.statusView.statusPollTableView.isHidden = true
|
||||
}
|
||||
|
||||
// toolbar
|
||||
let replyCountTitle: String = {
|
||||
|
|
|
@ -152,6 +152,10 @@ extension HomeTimelineViewController {
|
|||
self.context.apiService.backgroundManagedObjectContext.delete(toot)
|
||||
}
|
||||
}
|
||||
.sink { _ in
|
||||
// do nothing
|
||||
}
|
||||
.store(in: &self.disposeBag)
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ protocol StatusViewDelegate: class {
|
|||
|
||||
final class StatusView: UIView {
|
||||
|
||||
var statusPollTableViewHeightObservation: NSKeyValueObservation?
|
||||
|
||||
static let avatarImageSize = CGSize(width: 42, height: 42)
|
||||
static let avatarImageCornerRadius: CGFloat = 4
|
||||
static let contentWarningBlurRadius: CGFloat = 12
|
||||
|
@ -24,6 +26,7 @@ final class StatusView: UIView {
|
|||
weak var delegate: StatusViewDelegate?
|
||||
var isStatusTextSensitive = false
|
||||
var statusPollTableViewDataSource: UITableViewDiffableDataSource<PollSection, PollItem>?
|
||||
var statusPollTableViewHeightLaoutConstraint: NSLayoutConstraint!
|
||||
|
||||
let headerContainerStackView = UIStackView()
|
||||
|
||||
|
@ -103,9 +106,11 @@ final class StatusView: UIView {
|
|||
let statusMosaicImageView = MosaicImageViewContainer()
|
||||
|
||||
let statusPollTableView: UITableView = {
|
||||
let tableView = UITableView()
|
||||
tableView.register(PollTableViewCell.self, forCellReuseIdentifier: String(describing: PollTableViewCell.self))
|
||||
let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
||||
tableView.register(PollOptionTableViewCell.self, forCellReuseIdentifier: String(describing: PollOptionTableViewCell.self))
|
||||
tableView.isScrollEnabled = false
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .clear
|
||||
return tableView
|
||||
}()
|
||||
|
||||
|
@ -144,6 +149,10 @@ final class StatusView: UIView {
|
|||
drawContentWarningImageView()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
statusPollTableViewHeightObservation = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -265,8 +274,23 @@ extension StatusView {
|
|||
])
|
||||
statusContentWarningContainerStackView.addArrangedSubview(contentWarningTitle)
|
||||
statusContentWarningContainerStackView.addArrangedSubview(contentWarningActionButton)
|
||||
|
||||
statusContainerStackView.addArrangedSubview(statusMosaicImageView)
|
||||
statusPollTableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
statusContainerStackView.addArrangedSubview(statusPollTableView)
|
||||
statusPollTableViewHeightLaoutConstraint = statusPollTableView.heightAnchor.constraint(equalToConstant: 44.0).priority(.required - 1)
|
||||
NSLayoutConstraint.activate([
|
||||
statusPollTableViewHeightLaoutConstraint,
|
||||
])
|
||||
|
||||
statusPollTableViewHeightObservation = statusPollTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] tableView, _ in
|
||||
guard let self = self else { return }
|
||||
guard self.statusPollTableView.contentSize.height != .zero else {
|
||||
self.statusPollTableViewHeightLaoutConstraint.constant = 44
|
||||
return
|
||||
}
|
||||
self.statusPollTableViewHeightLaoutConstraint.constant = self.statusPollTableView.contentSize.height
|
||||
})
|
||||
|
||||
// action toolbar container
|
||||
containerStackView.addArrangedSubview(actionToolbarContainer)
|
||||
|
@ -322,14 +346,13 @@ extension StatusView {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - AvatarConfigurableView
|
||||
extension StatusView: AvatarConfigurableView {
|
||||
static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize }
|
||||
static var configurableAvatarImageCornerRadius: CGFloat { return 4 }
|
||||
var configurableAvatarImageView: UIImageView? { return nil }
|
||||
var configurableAvatarButton: UIButton? { return avatarButton }
|
||||
var configurableVerifiedBadgeImageView: UIImageView? { nil }
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// PollTableViewCell.swift
|
||||
// PollOptionTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-2-25.
|
||||
|
@ -7,8 +7,11 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
final class PollTableViewCell: UITableViewCell {
|
||||
final class PollOptionTableViewCell: UITableViewCell {
|
||||
|
||||
static let height: CGFloat = optionHeight + 2 * verticalMargin
|
||||
static let optionHeight: CGFloat = 44
|
||||
static let verticalMargin: CGFloat = 5
|
||||
static let checkmarkImageSize = CGSize(width: 26, height: 26)
|
||||
|
||||
let roundedBackgroundView = UIView()
|
||||
|
@ -57,9 +60,11 @@ final class PollTableViewCell: UITableViewCell {
|
|||
|
||||
}
|
||||
|
||||
extension PollTableViewCell {
|
||||
extension PollOptionTableViewCell {
|
||||
|
||||
private func _init() {
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
roundedBackgroundView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||
|
||||
roundedBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -69,6 +74,7 @@ extension PollTableViewCell {
|
|||
roundedBackgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
roundedBackgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
contentView.bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor, constant: 5),
|
||||
roundedBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionTableViewCell.optionHeight).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
checkmarkBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -77,8 +83,8 @@ extension PollTableViewCell {
|
|||
checkmarkBackgroundView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor, constant: 9),
|
||||
checkmarkBackgroundView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor, constant: 9),
|
||||
roundedBackgroundView.bottomAnchor.constraint(equalTo: checkmarkBackgroundView.bottomAnchor, constant: 9),
|
||||
checkmarkBackgroundView.widthAnchor.constraint(equalToConstant: PollTableViewCell.checkmarkImageSize.width).priority(.defaultHigh),
|
||||
checkmarkBackgroundView.heightAnchor.constraint(equalToConstant: PollTableViewCell.checkmarkImageSize.height).priority(.defaultHigh),
|
||||
checkmarkBackgroundView.widthAnchor.constraint(equalToConstant: PollOptionTableViewCell.checkmarkImageSize.width).priority(.defaultHigh),
|
||||
checkmarkBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionTableViewCell.checkmarkImageSize.height).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -104,6 +110,8 @@ extension PollTableViewCell {
|
|||
roundedBackgroundView.trailingAnchor.constraint(equalTo: optionPercentageLabel.trailingAnchor, constant: 18),
|
||||
optionPercentageLabel.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
|
||||
])
|
||||
optionPercentageLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
|
||||
optionPercentageLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
||||
|
||||
configureCheckmark(state: .none)
|
||||
}
|
||||
|
@ -111,8 +119,12 @@ extension PollTableViewCell {
|
|||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
updateCornerRadius()
|
||||
}
|
||||
|
||||
private func updateCornerRadius() {
|
||||
roundedBackgroundView.layer.masksToBounds = true
|
||||
roundedBackgroundView.layer.cornerRadius = roundedBackgroundView.bounds.height * 0.5
|
||||
roundedBackgroundView.layer.cornerRadius = PollOptionTableViewCell.optionHeight * 0.5
|
||||
roundedBackgroundView.layer.cornerCurve = .circular
|
||||
|
||||
checkmarkBackgroundView.layer.masksToBounds = true
|
||||
|
@ -122,7 +134,7 @@ extension PollTableViewCell {
|
|||
|
||||
}
|
||||
|
||||
extension PollTableViewCell {
|
||||
extension PollOptionTableViewCell {
|
||||
|
||||
enum CheckmarkState {
|
||||
case none
|
||||
|
@ -168,17 +180,17 @@ struct PollTableViewCell_Previews: PreviewProvider {
|
|||
static var controls: some View {
|
||||
Group {
|
||||
UIViewPreview() {
|
||||
PollTableViewCell()
|
||||
PollOptionTableViewCell()
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 44 + 10))
|
||||
UIViewPreview() {
|
||||
let cell = PollTableViewCell()
|
||||
let cell = PollOptionTableViewCell()
|
||||
cell.configureCheckmark(state: .off)
|
||||
return cell
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 44 + 10))
|
||||
UIViewPreview() {
|
||||
let cell = PollTableViewCell()
|
||||
let cell = PollOptionTableViewCell()
|
||||
cell.configureCheckmark(state: .on)
|
||||
return cell
|
||||
}
|
|
@ -10,7 +10,6 @@ import UIKit
|
|||
import AVKit
|
||||
import Combine
|
||||
|
||||
|
||||
protocol StatusTableViewCellDelegate: class {
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
|
|
Loading…
Reference in New Issue