forked from zelo72/mastodon-ios
feat: add PollSection and PollItem for diffable data source
This commit is contained in:
parent
80954b0492
commit
8b63c2fda1
|
@ -109,6 +109,9 @@
|
|||
DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44384E25E8C1FA008912A2 /* CALayer.swift */; };
|
||||
DB4481AD25EE155900BEFB67 /* Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481AC25EE155900BEFB67 /* Poll.swift */; };
|
||||
DB4481B325EE16D000BEFB67 /* PollOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481B225EE16D000BEFB67 /* PollOption.swift */; };
|
||||
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481B825EE289600BEFB67 /* UITableView.swift */; };
|
||||
DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481C525EE2ADA00BEFB67 /* PollSection.swift */; };
|
||||
DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */; };
|
||||
DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */; };
|
||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; };
|
||||
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; };
|
||||
|
@ -331,6 +334,9 @@
|
|||
DB44384E25E8C1FA008912A2 /* CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CALayer.swift; sourceTree = "<group>"; };
|
||||
DB4481AC25EE155900BEFB67 /* Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poll.swift; sourceTree = "<group>"; };
|
||||
DB4481B225EE16D000BEFB67 /* PollOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOption.swift; sourceTree = "<group>"; };
|
||||
DB4481B825EE289600BEFB67 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = "<group>"; };
|
||||
DB4481C525EE2ADA00BEFB67 /* PollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollSection.swift; sourceTree = "<group>"; };
|
||||
DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollItem.swift; sourceTree = "<group>"; };
|
||||
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardResponderService.swift; sourceTree = "<group>"; };
|
||||
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
|
||||
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = "<group>"; };
|
||||
|
@ -634,8 +640,8 @@
|
|||
2D76319C25C151DE00929FB9 /* Diffiable */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D7631B125C159E700929FB9 /* Item */,
|
||||
2D76319D25C151F600929FB9 /* Section */,
|
||||
2D7631B125C159E700929FB9 /* Item */,
|
||||
);
|
||||
path = Diffiable;
|
||||
sourceTree = "<group>";
|
||||
|
@ -644,6 +650,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
2D76319E25C1521200929FB9 /* StatusSection.swift */,
|
||||
DB4481C525EE2ADA00BEFB67 /* PollSection.swift */,
|
||||
);
|
||||
path = Section;
|
||||
sourceTree = "<group>";
|
||||
|
@ -686,6 +693,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
2D7631B225C159F700929FB9 /* Item.swift */,
|
||||
DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */,
|
||||
);
|
||||
path = Item;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1021,6 +1029,7 @@
|
|||
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */,
|
||||
2D42FF8E25C8228A004A627A /* UIButton.swift */,
|
||||
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */,
|
||||
DB4481B825EE289600BEFB67 /* UITableView.swift */,
|
||||
2D32EAB925CB9B0500C9ED86 /* UIView.swift */,
|
||||
0FAA101B25E10E760017CCDE /* UIFont.swift */,
|
||||
2D650FAA25ECDC9300851B58 /* Mastodon+Entidy+ErrorDetailReason.swift */,
|
||||
|
@ -1476,7 +1485,9 @@
|
|||
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
|
||||
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,
|
||||
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */,
|
||||
DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */,
|
||||
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
|
||||
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
|
||||
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */,
|
||||
2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */,
|
||||
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
|
||||
|
@ -1501,6 +1512,7 @@
|
|||
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
|
||||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
|
||||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||
DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */,
|
||||
DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */,
|
||||
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
|
||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// PollItem.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
enum PollItem {
|
||||
case pollOpion(objectID: NSManagedObjectID, attribute: Attribute)
|
||||
}
|
||||
|
||||
|
||||
extension PollItem {
|
||||
class Attribute: Hashable {
|
||||
var voted: Bool = false
|
||||
|
||||
static func == (lhs: PollItem.Attribute, rhs: PollItem.Attribute) -> Bool {
|
||||
return lhs.voted == rhs.voted
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(voted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PollItem: Equatable {
|
||||
static func == (lhs: PollItem, rhs: PollItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.pollOpion(let objectIDLeft, _), .pollOpion(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension PollItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .pollOpion(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// PollSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
enum PollSection {
|
||||
case main
|
||||
}
|
||||
|
||||
extension PollSection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
managedObjectContext: NSManagedObjectContext
|
||||
) -> UITableViewDiffableDataSource<PollSection, PollItem> {
|
||||
return UITableViewDiffableDataSource<PollSection, PollItem>(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -154,6 +154,9 @@ extension StatusSection {
|
|||
cell.statusView.statusMosaicImageView.blurVisualEffectView.effect = isStatusSensitive ? MosaicImageViewContainer.blurVisualEffect : nil
|
||||
cell.statusView.statusMosaicImageView.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0
|
||||
|
||||
// set poll
|
||||
|
||||
|
||||
// toolbar
|
||||
let replyCountTitle: String = {
|
||||
let count = (toot.reblog ?? toot).repliesCount?.intValue ?? 0
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// UITableView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-3-2.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITableView {
|
||||
|
||||
// static let groupedTableViewPaddingHeaderViewHeight: CGFloat = 16
|
||||
// static var groupedTableViewPaddingHeaderView: UIView {
|
||||
// return UIView(frame: CGRect(x: 0, y: 0, width: 100, height: groupedTableViewPaddingHeaderViewHeight))
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
extension UITableView {
|
||||
|
||||
func deselectRow(with transitionCoordinator: UIViewControllerTransitionCoordinator?, animated: Bool) {
|
||||
guard let indexPathForSelectedRow = indexPathForSelectedRow else { return }
|
||||
|
||||
guard let transitionCoordinator = transitionCoordinator else {
|
||||
deselectRow(at: indexPathForSelectedRow, animated: animated)
|
||||
return
|
||||
}
|
||||
|
||||
transitionCoordinator.animate(alongsideTransition: { _ in
|
||||
self.deselectRow(at: indexPathForSelectedRow, animated: animated)
|
||||
}, completion: { context in
|
||||
if context.isCancelled {
|
||||
self.selectRow(at: indexPathForSelectedRow, animated: animated, scrollPosition: .none)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func blinkRow(at indexPath: IndexPath) {
|
||||
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 1) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard let cell = self.cellForRow(at: indexPath) else { return }
|
||||
let backgroundColor = cell.backgroundColor
|
||||
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
cell.backgroundColor = Asset.Colors.Label.highlight.color.withAlphaComponent(0.5)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
cell.backgroundColor = backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,7 @@ extension HomeTimelineViewController {
|
|||
identifier: nil,
|
||||
options: .displayInline,
|
||||
children: [
|
||||
moveMenu,
|
||||
dropMenu,
|
||||
UIAction(title: "Show Public Timeline", image: UIImage(systemName: "list.dash"), attributes: []) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
|
@ -33,6 +34,41 @@ extension HomeTimelineViewController {
|
|||
return menu
|
||||
}
|
||||
|
||||
var moveMenu: UIMenu {
|
||||
return UIMenu(
|
||||
title: "Move to…",
|
||||
image: UIImage(systemName: "arrow.forward.circle"),
|
||||
identifier: nil,
|
||||
options: [],
|
||||
children: [
|
||||
UIAction(title: "First Gap", image: nil, attributes: [], handler: { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.moveToTopGapAction(action)
|
||||
}),
|
||||
UIAction(title: "First Poll Toot", image: nil, attributes: [], handler: { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.moveToFirstPollToot(action)
|
||||
}),
|
||||
// UIAction(title: "First Reply Toot", image: nil, attributes: [], handler: { [weak self] action in
|
||||
// guard let self = self else { return }
|
||||
// self.moveToFirstReplyToot(action)
|
||||
// }),
|
||||
// UIAction(title: "First Reply Reblog", image: nil, attributes: [], handler: { [weak self] action in
|
||||
// guard let self = self else { return }
|
||||
// self.moveToFirstReplyReblog(action)
|
||||
// }),
|
||||
// UIAction(title: "First Video Toot", image: nil, attributes: [], handler: { [weak self] action in
|
||||
// guard let self = self else { return }
|
||||
// self.moveToFirstVideoToot(action)
|
||||
// }),
|
||||
// UIAction(title: "First GIF Toot", image: nil, attributes: [], handler: { [weak self] action in
|
||||
// guard let self = self else { return }
|
||||
// self.moveToFirstGIFToot(action)
|
||||
// }),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
var dropMenu: UIMenu {
|
||||
return UIMenu(
|
||||
title: "Drop…",
|
||||
|
@ -40,9 +76,9 @@ extension HomeTimelineViewController {
|
|||
identifier: nil,
|
||||
options: [],
|
||||
children: [50, 100, 150, 200, 250, 300].map { count in
|
||||
UIAction(title: "Drop Recent \(count) Tweets", image: nil, attributes: [], handler: { [weak self] action in
|
||||
UIAction(title: "Drop Recent \(count) Toots", image: nil, attributes: [], handler: { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.dropRecentTweetsAction(action, count: count)
|
||||
self.dropRecentTootsAction(action, count: count)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -51,7 +87,42 @@ extension HomeTimelineViewController {
|
|||
|
||||
extension HomeTimelineViewController {
|
||||
|
||||
@objc private func dropRecentTweetsAction(_ sender: UIAction, count: Int) {
|
||||
@objc private func moveToTopGapAction(_ sender: UIAction) {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
let snapshotTransitioning = diffableDataSource.snapshot()
|
||||
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
|
||||
switch item {
|
||||
case .homeMiddleLoader: return true
|
||||
default: return false
|
||||
}
|
||||
})
|
||||
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
|
||||
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func moveToFirstPollToot(_ sender: UIAction) {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
let snapshotTransitioning = diffableDataSource.snapshot()
|
||||
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
|
||||
switch item {
|
||||
case .homeTimelineIndex(let objectID, _):
|
||||
let homeTimelineIndex = viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! HomeTimelineIndex
|
||||
let toot = homeTimelineIndex.toot.reblog ?? homeTimelineIndex.toot
|
||||
return toot.poll != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
|
||||
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
|
||||
tableView.blinkRow(at: IndexPath(row: index, section: 0))
|
||||
} else {
|
||||
print("Not found poll toot")
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func dropRecentTootsAction(_ sender: UIAction, count: Int) {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
let snapshotTransitioning = diffableDataSource.snapshot()
|
||||
|
||||
|
|
|
@ -110,10 +110,10 @@ final class HomeTimelineViewModel: NSObject {
|
|||
context.authenticationService.activeMastodonAuthentication
|
||||
.sink { [weak self] activeMastodonAuthentication in
|
||||
guard let self = self else { return }
|
||||
guard let twitterAuthentication = activeMastodonAuthentication else { return }
|
||||
let activeTwitterUserID = twitterAuthentication.userID
|
||||
guard let mastodonAuthentication = activeMastodonAuthentication else { return }
|
||||
let activeMastodonUserID = mastodonAuthentication.userID
|
||||
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
HomeTimelineIndex.predicate(userID: activeTwitterUserID),
|
||||
HomeTimelineIndex.predicate(userID: activeMastodonUserID),
|
||||
HomeTimelineIndex.notDeleted()
|
||||
])
|
||||
self.timelinePredicate.value = predicate
|
||||
|
|
|
@ -23,6 +23,7 @@ final class StatusView: UIView {
|
|||
|
||||
weak var delegate: StatusViewDelegate?
|
||||
var isStatusTextSensitive = false
|
||||
var statusPollTableViewDataSource: UITableViewDiffableDataSource<PollSection, PollItem>?
|
||||
|
||||
let headerContainerStackView = UIStackView()
|
||||
|
||||
|
@ -101,6 +102,13 @@ final class StatusView: UIView {
|
|||
}()
|
||||
let statusMosaicImageView = MosaicImageViewContainer()
|
||||
|
||||
let statusPollTableView: UITableView = {
|
||||
let tableView = UITableView()
|
||||
tableView.register(PollTableViewCell.self, forCellReuseIdentifier: String(describing: PollTableViewCell.self))
|
||||
tableView.isScrollEnabled = false
|
||||
return tableView
|
||||
}()
|
||||
|
||||
// do not use visual effect view due to we blur text only without background
|
||||
let contentWarningBlurContentImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
|
@ -222,7 +230,7 @@ extension StatusView {
|
|||
subtitleContainerStackView.axis = .horizontal
|
||||
subtitleContainerStackView.addArrangedSubview(usernameLabel)
|
||||
|
||||
// status container: [status | image / video | audio]
|
||||
// status container: [status | image / video | audio | poll]
|
||||
containerStackView.addArrangedSubview(statusContainerStackView)
|
||||
statusContainerStackView.axis = .vertical
|
||||
statusContainerStackView.spacing = 10
|
||||
|
@ -258,7 +266,7 @@ extension StatusView {
|
|||
statusContentWarningContainerStackView.addArrangedSubview(contentWarningTitle)
|
||||
statusContentWarningContainerStackView.addArrangedSubview(contentWarningActionButton)
|
||||
statusContainerStackView.addArrangedSubview(statusMosaicImageView)
|
||||
|
||||
statusContainerStackView.addArrangedSubview(statusPollTableView)
|
||||
|
||||
// action toolbar container
|
||||
containerStackView.addArrangedSubview(actionToolbarContainer)
|
||||
|
@ -266,6 +274,8 @@ extension StatusView {
|
|||
|
||||
headerContainerStackView.isHidden = true
|
||||
statusMosaicImageView.isHidden = true
|
||||
statusPollTableView.isHidden = true
|
||||
|
||||
contentWarningBlurContentImageView.isHidden = true
|
||||
statusContentWarningContainerStackView.isHidden = true
|
||||
statusContentWarningContainerStackViewBottomLayoutConstraint.isActive = false
|
||||
|
|
|
@ -33,6 +33,7 @@ final class StatusTableViewCell: UITableViewCell {
|
|||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
statusView.isStatusTextSensitive = false
|
||||
statusView.statusPollTableView.dataSource = nil
|
||||
statusView.cleanUpContentWarning()
|
||||
disposeBag.removeAll()
|
||||
observations.removeAll()
|
||||
|
|
Loading…
Reference in New Issue