feat: update the notification tab "Mentions" segment table UI
This commit is contained in:
parent
86d475fe56
commit
0d39d061a1
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
|
|
||||||
enum NotificationItem {
|
enum NotificationItem {
|
||||||
case notification(objectID: NSManagedObjectID, attribute: Item.StatusAttribute)
|
case notification(objectID: NSManagedObjectID, attribute: Item.StatusAttribute)
|
||||||
|
case notificationStatus(objectID: NSManagedObjectID, attribute: Item.StatusAttribute) // display notification status without card wrapper
|
||||||
case bottomLoader
|
case bottomLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ extension NotificationItem: Equatable {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.notification(let idLeft, _), .notification(let idRight, _)):
|
case (.notification(let idLeft, _), .notification(let idRight, _)):
|
||||||
return idLeft == idRight
|
return idLeft == idRight
|
||||||
|
case (.notificationStatus(let idLeft, _), .notificationStatus(let idRight, _)):
|
||||||
|
return idLeft == idRight
|
||||||
case (.bottomLoader, .bottomLoader):
|
case (.bottomLoader, .bottomLoader):
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
|
@ -32,6 +34,8 @@ extension NotificationItem: Hashable {
|
||||||
switch self {
|
switch self {
|
||||||
case .notification(let id, _):
|
case .notification(let id, _):
|
||||||
hasher.combine(id)
|
hasher.combine(id)
|
||||||
|
case .notificationStatus(let id, _):
|
||||||
|
hasher.combine(id)
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
hasher.combine(String(describing: NotificationItem.bottomLoader.self))
|
hasher.combine(String(describing: NotificationItem.bottomLoader.self))
|
||||||
}
|
}
|
||||||
|
@ -43,6 +47,8 @@ extension NotificationItem {
|
||||||
switch self {
|
switch self {
|
||||||
case .notification(let objectID, _):
|
case .notification(let objectID, _):
|
||||||
return .mastodonNotification(objectID: objectID)
|
return .mastodonNotification(objectID: objectID)
|
||||||
|
case .notificationStatus(let objectID, _):
|
||||||
|
return .mastodonNotification(objectID: objectID)
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,10 @@ enum NotificationSection: Equatable, Hashable {
|
||||||
extension NotificationSection {
|
extension NotificationSection {
|
||||||
static func tableViewDiffableDataSource(
|
static func tableViewDiffableDataSource(
|
||||||
for tableView: UITableView,
|
for tableView: UITableView,
|
||||||
|
dependency: NeedsDependency,
|
||||||
managedObjectContext: NSManagedObjectContext,
|
managedObjectContext: NSManagedObjectContext,
|
||||||
delegate: NotificationTableViewCellDelegate,
|
delegate: NotificationTableViewCellDelegate,
|
||||||
dependency: NeedsDependency
|
statusTableViewCellDelegate: StatusTableViewCellDelegate
|
||||||
) -> UITableViewDiffableDataSource<NotificationSection, NotificationItem> {
|
) -> UITableViewDiffableDataSource<NotificationSection, NotificationItem> {
|
||||||
UITableViewDiffableDataSource(tableView: tableView) {
|
UITableViewDiffableDataSource(tableView: tableView) {
|
||||||
[weak delegate, weak dependency]
|
[weak delegate, weak dependency]
|
||||||
|
@ -32,137 +33,45 @@ extension NotificationSection {
|
||||||
switch notificationItem {
|
switch notificationItem {
|
||||||
case .notification(let objectID, let attribute):
|
case .notification(let objectID, let attribute):
|
||||||
guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification,
|
guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification,
|
||||||
!notification.isDeleted else {
|
!notification.isDeleted
|
||||||
return UITableViewCell()
|
else { return UITableViewCell() }
|
||||||
}
|
|
||||||
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell
|
||||||
cell.delegate = delegate
|
configure(
|
||||||
|
tableView: tableView,
|
||||||
// configure author
|
cell: cell,
|
||||||
cell.configure(
|
notification: notification,
|
||||||
with: AvatarConfigurableViewConfiguration(
|
dependency: dependency,
|
||||||
avatarImageURL: notification.account.avatarImageURL()
|
attribute: attribute
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
cell.delegate = delegate
|
||||||
|
return cell
|
||||||
|
|
||||||
func createActionImage() -> UIImage? {
|
case .notificationStatus(objectID: let objectID, attribute: let attribute):
|
||||||
return UIImage(
|
guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification,
|
||||||
systemName: notification.notificationType.actionImageName,
|
!notification.isDeleted,
|
||||||
withConfiguration: UIImage.SymbolConfiguration(
|
let status = notification.status,
|
||||||
pointSize: 12, weight: .semibold
|
let requestUserID = dependency.context.authenticationService.activeMastodonAuthenticationBox.value?.userID
|
||||||
)
|
else { return UITableViewCell() }
|
||||||
)?
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||||
.withTintColor(.systemBackground)
|
|
||||||
.af.imageAspectScaled(toFit: CGSize(width: 14, height: 14))
|
|
||||||
}
|
|
||||||
|
|
||||||
cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color
|
|
||||||
cell.avatarButton.badgeImageView.image = createActionImage()
|
|
||||||
cell.traitCollectionDidChange
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak cell] in
|
|
||||||
guard let cell = cell else { return }
|
|
||||||
cell.avatarButton.badgeImageView.image = createActionImage()
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
// configure author name, notification description, timestamp
|
|
||||||
let nameText = notification.account.displayNameWithFallback
|
|
||||||
let titleLabelText: String = {
|
|
||||||
switch notification.notificationType {
|
|
||||||
case .favourite: return L10n.Scene.Notification.userFavoritedYourPost(nameText)
|
|
||||||
case .follow: return L10n.Scene.Notification.userFollowedYou(nameText)
|
|
||||||
case .followRequest: return L10n.Scene.Notification.userRequestedToFollowYou(nameText)
|
|
||||||
case .mention: return L10n.Scene.Notification.userMentionedYou(nameText)
|
|
||||||
case .poll: return L10n.Scene.Notification.userYourPollHasEnded(nameText)
|
|
||||||
case .reblog: return L10n.Scene.Notification.userRebloggedYourPost(nameText)
|
|
||||||
default: return ""
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
do {
|
|
||||||
let nameContent = MastodonContent(content: nameText, emojis: notification.account.emojiMeta)
|
|
||||||
let nameMetaContent = try MastodonMetaContent.convert(document: nameContent)
|
|
||||||
|
|
||||||
let mastodonContent = MastodonContent(content: titleLabelText, emojis: notification.account.emojiMeta)
|
|
||||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
|
||||||
|
|
||||||
cell.titleLabel.configure(content: metaContent)
|
|
||||||
|
|
||||||
if let nameRange = metaContent.string.range(of: nameMetaContent.string) {
|
|
||||||
let nsRange = NSRange(nameRange, in: metaContent.string)
|
|
||||||
cell.titleLabel.textStorage.addAttributes([
|
|
||||||
.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20),
|
|
||||||
.foregroundColor: Asset.Colors.brandBlue.color,
|
|
||||||
], range: nsRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
let metaContent = PlaintextMetaContent(string: titleLabelText)
|
|
||||||
cell.titleLabel.configure(content: metaContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
let createAt = notification.createAt
|
|
||||||
cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
|
||||||
AppContext.shared.timestampUpdatePublisher
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak cell] _ in
|
|
||||||
guard let cell = cell else { return }
|
|
||||||
cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
// configure follow request (if exist)
|
|
||||||
if case .followRequest = notification.notificationType {
|
|
||||||
cell.acceptButton.publisher(for: .touchUpInside)
|
|
||||||
.sink { [weak cell] _ in
|
|
||||||
guard let cell = cell else { return }
|
|
||||||
cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton)
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
cell.rejectButton.publisher(for: .touchUpInside)
|
|
||||||
.sink { [weak cell] _ in
|
|
||||||
guard let cell = cell else { return }
|
|
||||||
cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton)
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
cell.buttonStackView.isHidden = false
|
|
||||||
} else {
|
|
||||||
cell.buttonStackView.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure status (if exist)
|
|
||||||
if let status = notification.status {
|
|
||||||
let frame = CGRect(
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right,
|
|
||||||
height: tableView.readableContentGuide.layoutFrame.height
|
|
||||||
)
|
|
||||||
StatusSection.configure(
|
|
||||||
cell: cell,
|
|
||||||
tableView: tableView,
|
|
||||||
timelineContext: .notifications,
|
|
||||||
dependency: dependency,
|
|
||||||
readableLayoutFrame: frame,
|
|
||||||
status: status,
|
|
||||||
requestUserID: notification.userID,
|
|
||||||
statusItemAttribute: attribute
|
|
||||||
)
|
|
||||||
cell.statusContainerView.isHidden = false
|
|
||||||
cell.containerStackView.alignment = .top
|
|
||||||
cell.containerStackViewBottomLayoutConstraint.constant = 0
|
|
||||||
} else {
|
|
||||||
if case .followRequest = notification.notificationType {
|
|
||||||
cell.containerStackView.alignment = .top
|
|
||||||
} else {
|
|
||||||
cell.containerStackView.alignment = .center
|
|
||||||
}
|
|
||||||
cell.statusContainerView.isHidden = true
|
|
||||||
cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// configure cell
|
||||||
|
StatusSection.configureStatusTableViewCell(
|
||||||
|
cell: cell,
|
||||||
|
tableView: tableView,
|
||||||
|
timelineContext: .notifications,
|
||||||
|
dependency: dependency,
|
||||||
|
readableLayoutFrame: tableView.readableContentGuide.layoutFrame,
|
||||||
|
status: status,
|
||||||
|
requestUserID: requestUserID,
|
||||||
|
statusItemAttribute: attribute
|
||||||
|
)
|
||||||
|
cell.statusView.headerContainerView.isHidden = true // set header hide
|
||||||
|
cell.statusView.actionToolbarContainer.isHidden = true // set toolbar hide
|
||||||
|
cell.statusView.actionToolbarPlaceholderPaddingView.isHidden = false
|
||||||
|
cell.delegate = statusTableViewCellDelegate
|
||||||
|
cell.isAccessibilityElement = true
|
||||||
|
StatusSection.configureStatusAccessibilityLabel(cell: cell)
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
|
@ -174,3 +83,136 @@ extension NotificationSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NotificationSection {
|
||||||
|
static func configure(
|
||||||
|
tableView: UITableView,
|
||||||
|
cell: NotificationStatusTableViewCell,
|
||||||
|
notification: MastodonNotification,
|
||||||
|
dependency: NeedsDependency,
|
||||||
|
attribute: Item.StatusAttribute
|
||||||
|
) {
|
||||||
|
// configure author
|
||||||
|
cell.configure(
|
||||||
|
with: AvatarConfigurableViewConfiguration(
|
||||||
|
avatarImageURL: notification.account.avatarImageURL()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func createActionImage() -> UIImage? {
|
||||||
|
return UIImage(
|
||||||
|
systemName: notification.notificationType.actionImageName,
|
||||||
|
withConfiguration: UIImage.SymbolConfiguration(
|
||||||
|
pointSize: 12, weight: .semibold
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
.withTintColor(.systemBackground)
|
||||||
|
.af.imageAspectScaled(toFit: CGSize(width: 14, height: 14))
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color
|
||||||
|
cell.avatarButton.badgeImageView.image = createActionImage()
|
||||||
|
cell.traitCollectionDidChange
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak cell] in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
cell.avatarButton.badgeImageView.image = createActionImage()
|
||||||
|
}
|
||||||
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
|
// configure author name, notification description, timestamp
|
||||||
|
let nameText = notification.account.displayNameWithFallback
|
||||||
|
let titleLabelText: String = {
|
||||||
|
switch notification.notificationType {
|
||||||
|
case .favourite: return L10n.Scene.Notification.userFavoritedYourPost(nameText)
|
||||||
|
case .follow: return L10n.Scene.Notification.userFollowedYou(nameText)
|
||||||
|
case .followRequest: return L10n.Scene.Notification.userRequestedToFollowYou(nameText)
|
||||||
|
case .mention: return L10n.Scene.Notification.userMentionedYou(nameText)
|
||||||
|
case .poll: return L10n.Scene.Notification.userYourPollHasEnded(nameText)
|
||||||
|
case .reblog: return L10n.Scene.Notification.userRebloggedYourPost(nameText)
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
do {
|
||||||
|
let nameContent = MastodonContent(content: nameText, emojis: notification.account.emojiMeta)
|
||||||
|
let nameMetaContent = try MastodonMetaContent.convert(document: nameContent)
|
||||||
|
|
||||||
|
let mastodonContent = MastodonContent(content: titleLabelText, emojis: notification.account.emojiMeta)
|
||||||
|
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||||
|
|
||||||
|
cell.titleLabel.configure(content: metaContent)
|
||||||
|
|
||||||
|
if let nameRange = metaContent.string.range(of: nameMetaContent.string) {
|
||||||
|
let nsRange = NSRange(nameRange, in: metaContent.string)
|
||||||
|
cell.titleLabel.textStorage.addAttributes([
|
||||||
|
.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20),
|
||||||
|
.foregroundColor: Asset.Colors.brandBlue.color,
|
||||||
|
], range: nsRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
let metaContent = PlaintextMetaContent(string: titleLabelText)
|
||||||
|
cell.titleLabel.configure(content: metaContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
let createAt = notification.createAt
|
||||||
|
cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
||||||
|
AppContext.shared.timestampUpdatePublisher
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak cell] _ in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
||||||
|
}
|
||||||
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
|
// configure follow request (if exist)
|
||||||
|
if case .followRequest = notification.notificationType {
|
||||||
|
cell.acceptButton.publisher(for: .touchUpInside)
|
||||||
|
.sink { [weak cell] _ in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton)
|
||||||
|
}
|
||||||
|
.store(in: &cell.disposeBag)
|
||||||
|
cell.rejectButton.publisher(for: .touchUpInside)
|
||||||
|
.sink { [weak cell] _ in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton)
|
||||||
|
}
|
||||||
|
.store(in: &cell.disposeBag)
|
||||||
|
cell.buttonStackView.isHidden = false
|
||||||
|
} else {
|
||||||
|
cell.buttonStackView.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure status (if exist)
|
||||||
|
if let status = notification.status {
|
||||||
|
let frame = CGRect(
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right,
|
||||||
|
height: tableView.readableContentGuide.layoutFrame.height
|
||||||
|
)
|
||||||
|
StatusSection.configure(
|
||||||
|
cell: cell,
|
||||||
|
tableView: tableView,
|
||||||
|
timelineContext: .notifications,
|
||||||
|
dependency: dependency,
|
||||||
|
readableLayoutFrame: frame,
|
||||||
|
status: status,
|
||||||
|
requestUserID: notification.userID,
|
||||||
|
statusItemAttribute: attribute
|
||||||
|
)
|
||||||
|
cell.statusContainerView.isHidden = false
|
||||||
|
cell.containerStackView.alignment = .top
|
||||||
|
cell.containerStackViewBottomLayoutConstraint.constant = 0
|
||||||
|
} else {
|
||||||
|
if case .followRequest = notification.notificationType {
|
||||||
|
cell.containerStackView.alignment = .top
|
||||||
|
} else {
|
||||||
|
cell.containerStackView.alignment = .center
|
||||||
|
}
|
||||||
|
cell.statusContainerView.isHidden = true
|
||||||
|
cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,21 +19,25 @@ extension NotificationViewController: StatusProvider {
|
||||||
|
|
||||||
func status(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future<Status?, Never> {
|
func status(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future<Status?, Never> {
|
||||||
return Future<Status?, Never> { promise in
|
return Future<Status?, Never> { promise in
|
||||||
guard let cell = cell,
|
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||||
let diffableDataSource = self.viewModel.diffableDataSource,
|
assertionFailure()
|
||||||
let indexPath = self.tableView.indexPath(for: cell),
|
promise(.success(nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }),
|
||||||
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
||||||
promise(.success(nil))
|
promise(.success(nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch item {
|
switch item {
|
||||||
case .notification(let objectID, _):
|
case .notification(let objectID, _),
|
||||||
|
.notificationStatus(let objectID, _):
|
||||||
self.viewModel.fetchedResultsController.managedObjectContext.perform {
|
self.viewModel.fetchedResultsController.managedObjectContext.perform {
|
||||||
let notification = self.viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! MastodonNotification
|
let notification = self.viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! MastodonNotification
|
||||||
promise(.success(notification.status))
|
promise(.success(notification.status))
|
||||||
}
|
}
|
||||||
default:
|
case .bottomLoader:
|
||||||
promise(.success(nil))
|
promise(.success(nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,3 +72,6 @@ extension NotificationViewController: StatusProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UserProvider
|
||||||
|
extension NotificationViewController: UserProvider { }
|
||||||
|
|
|
@ -14,8 +14,10 @@ import OSLog
|
||||||
import UIKit
|
import UIKit
|
||||||
import Meta
|
import Meta
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
|
import AVKit
|
||||||
|
|
||||||
|
final class NotificationViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||||
|
|
||||||
final class NotificationViewController: UIViewController, NeedsDependency {
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
|
@ -24,14 +26,17 @@ final class NotificationViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
private(set) lazy var viewModel = NotificationViewModel(context: context)
|
private(set) lazy var viewModel = NotificationViewModel(context: context)
|
||||||
|
|
||||||
|
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
||||||
|
|
||||||
let segmentControl: UISegmentedControl = {
|
let segmentControl: UISegmentedControl = {
|
||||||
let control = UISegmentedControl(items: [L10n.Scene.Notification.Title.everything, L10n.Scene.Notification.Title.mentions])
|
let control = UISegmentedControl(items: [L10n.Scene.Notification.Title.everything, L10n.Scene.Notification.Title.mentions])
|
||||||
control.selectedSegmentIndex = NotificationViewModel.NotificationSegment.EveryThing.rawValue
|
control.selectedSegmentIndex = NotificationViewModel.NotificationSegment.everyThing.rawValue
|
||||||
return control
|
return control
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let tableView: UITableView = {
|
let tableView: UITableView = {
|
||||||
let tableView = ControlContainableTableView()
|
let tableView = ControlContainableTableView()
|
||||||
|
tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self))
|
||||||
tableView.register(NotificationStatusTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationStatusTableViewCell.self))
|
tableView.register(NotificationStatusTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationStatusTableViewCell.self))
|
||||||
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||||
tableView.estimatedRowHeight = UITableView.automaticDimension
|
tableView.estimatedRowHeight = UITableView.automaticDimension
|
||||||
|
@ -82,7 +87,12 @@ extension NotificationViewController {
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
viewModel.tableView = tableView
|
viewModel.tableView = tableView
|
||||||
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
||||||
viewModel.setupDiffableDataSource(for: tableView, delegate: self, dependency: self)
|
viewModel.setupDiffableDataSource(
|
||||||
|
for: tableView,
|
||||||
|
dependency: self,
|
||||||
|
delegate: self,
|
||||||
|
statusTableViewCellDelegate: self
|
||||||
|
)
|
||||||
viewModel.viewDidLoad.send()
|
viewModel.viewDidLoad.send()
|
||||||
|
|
||||||
// bind refresh control
|
// bind refresh control
|
||||||
|
@ -128,9 +138,9 @@ extension NotificationViewController {
|
||||||
self.viewModel.needsScrollToTopAfterDataSourceUpdate = true
|
self.viewModel.needsScrollToTopAfterDataSourceUpdate = true
|
||||||
|
|
||||||
switch segment {
|
switch segment {
|
||||||
case .EveryThing:
|
case .everyThing:
|
||||||
self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID)
|
self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID)
|
||||||
case .Mentions:
|
case .mentions:
|
||||||
self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID, typeRaw: Mastodon.Entity.Notification.NotificationType.mention.rawValue)
|
self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID, typeRaw: Mastodon.Entity.Notification.NotificationType.mention.rawValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +159,7 @@ extension NotificationViewController {
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
tableView.deselectRow(with: transitionCoordinator, animated: animated)
|
aspectViewWillAppear(animated)
|
||||||
|
|
||||||
// fetch latest notification when scroll position is within half screen height to prevent list reload
|
// fetch latest notification when scroll position is within half screen height to prevent list reload
|
||||||
if tableView.contentOffset.y < view.frame.height * 0.5 {
|
if tableView.contentOffset.y < view.frame.height * 0.5 {
|
||||||
|
@ -182,6 +192,12 @@ extension NotificationViewController {
|
||||||
context.notificationService.clearNotificationCountForActiveUser()
|
context.notificationService.clearNotificationCountForActiveUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
|
aspectViewDidDisappear(animated)
|
||||||
|
}
|
||||||
|
|
||||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
super.viewWillTransition(to: size, with: coordinator)
|
super.viewWillTransition(to: size, with: coordinator)
|
||||||
|
|
||||||
|
@ -208,20 +224,20 @@ extension NotificationViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - StatusTableViewControllerAspect
|
// MARK: - TableViewCellHeightCacheableContainer
|
||||||
extension NotificationViewController: StatusTableViewControllerAspect { }
|
extension NotificationViewController: TableViewCellHeightCacheableContainer {
|
||||||
|
var cellFrameCache: NSCache<NSNumber, NSValue> { return viewModel.cellFrameCache }
|
||||||
extension NotificationViewController {
|
|
||||||
|
|
||||||
func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
switch item {
|
switch item {
|
||||||
case .notification(let objectID, _):
|
case .notification(let objectID, _),
|
||||||
|
.notificationStatus(let objectID, _):
|
||||||
guard let object = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return }
|
guard let object = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return }
|
||||||
let key = object.id as NSString
|
let key = object.objectID.hashValue
|
||||||
let frame = cell.frame
|
let frame = cell.frame
|
||||||
viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: key)
|
viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key))
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -231,10 +247,11 @@ extension NotificationViewController {
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension }
|
guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension }
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension }
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension }
|
||||||
switch item {
|
switch item {
|
||||||
case .notification(let objectID, _):
|
case .notification(let objectID, _),
|
||||||
|
.notificationStatus(let objectID, _):
|
||||||
guard let object = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return UITableView.automaticDimension }
|
guard let object = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return UITableView.automaticDimension }
|
||||||
let key = object.id as NSString
|
let key = object.objectID.hashValue
|
||||||
guard let frame = viewModel.cellFrameCache.object(forKey: key)?.cgRectValue else { return UITableView.automaticDimension }
|
guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: key))?.cgRectValue else { return UITableView.automaticDimension }
|
||||||
return frame.height
|
return frame.height
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
return TimelineLoaderTableViewCell.cellHeight
|
return TimelineLoaderTableViewCell.cellHeight
|
||||||
|
@ -242,22 +259,55 @@ extension NotificationViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - StatusTableViewControllerAspect
|
||||||
|
extension NotificationViewController: StatusTableViewControllerAspect { }
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
|
|
||||||
extension NotificationViewController: UITableViewDelegate {
|
extension NotificationViewController: UITableViewDelegate {
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||||
|
aspectTableView(tableView, estimatedHeightForRowAt: indexPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
open(item: item)
|
switch item {
|
||||||
|
case .notificationStatus:
|
||||||
|
aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath)
|
||||||
|
case .bottomLoader:
|
||||||
|
if !tableView.isDragging, !tableView.isDecelerating {
|
||||||
|
viewModel.loadOldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
cacheTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
|
aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
return handleTableView(tableView, estimatedHeightForRowAt: indexPath)
|
aspectTableView(tableView, didSelectRowAt: indexPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
|
return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
||||||
|
return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
||||||
|
return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
|
aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -278,19 +328,6 @@ extension NotificationViewController {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
|
||||||
switch item {
|
|
||||||
case .bottomLoader:
|
|
||||||
if !tableView.isDragging, !tableView.isDecelerating {
|
|
||||||
viewModel.loadOldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
||||||
|
@ -388,6 +425,7 @@ extension NotificationViewController: ScrollViewContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - LoadMoreConfigurableTableViewContainer
|
||||||
extension NotificationViewController: LoadMoreConfigurableTableViewContainer {
|
extension NotificationViewController: LoadMoreConfigurableTableViewContainer {
|
||||||
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
||||||
typealias LoadingState = NotificationViewModel.LoadOldestState.Loading
|
typealias LoadingState = NotificationViewModel.LoadOldestState.Loading
|
||||||
|
@ -395,6 +433,24 @@ extension NotificationViewController: LoadMoreConfigurableTableViewContainer {
|
||||||
var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadOldestStateMachine }
|
var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadOldestStateMachine }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AVPlayerViewControllerDelegate
|
||||||
|
extension NotificationViewController: AVPlayerViewControllerDelegate {
|
||||||
|
func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
handlePlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
handlePlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - statusTableViewCellDelegate
|
||||||
|
extension NotificationViewController: StatusTableViewCellDelegate {
|
||||||
|
var playerViewControllerDelegate: AVPlayerViewControllerDelegate? {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension NotificationViewController {
|
extension NotificationViewController {
|
||||||
|
|
||||||
enum CategorySwitch: String, CaseIterable {
|
enum CategorySwitch: String, CaseIterable {
|
||||||
|
@ -452,9 +508,9 @@ extension NotificationViewController {
|
||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
case .showEverything:
|
case .showEverything:
|
||||||
viewModel.selectedIndex.value = .EveryThing
|
viewModel.selectedIndex.value = .everyThing
|
||||||
case .showMentions:
|
case .showMentions:
|
||||||
viewModel.selectedIndex.value = .Mentions
|
viewModel.selectedIndex.value = .mentions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,16 @@ import MastodonSDK
|
||||||
extension NotificationViewModel {
|
extension NotificationViewModel {
|
||||||
func setupDiffableDataSource(
|
func setupDiffableDataSource(
|
||||||
for tableView: UITableView,
|
for tableView: UITableView,
|
||||||
|
dependency: NeedsDependency,
|
||||||
delegate: NotificationTableViewCellDelegate,
|
delegate: NotificationTableViewCellDelegate,
|
||||||
dependency: NeedsDependency
|
statusTableViewCellDelegate: StatusTableViewCellDelegate
|
||||||
) {
|
) {
|
||||||
diffableDataSource = NotificationSection.tableViewDiffableDataSource(
|
diffableDataSource = NotificationSection.tableViewDiffableDataSource(
|
||||||
for: tableView,
|
for: tableView,
|
||||||
|
dependency: dependency,
|
||||||
managedObjectContext: fetchedResultsController.managedObjectContext,
|
managedObjectContext: fetchedResultsController.managedObjectContext,
|
||||||
delegate: delegate,
|
delegate: delegate,
|
||||||
dependency: dependency
|
statusTableViewCellDelegate: statusTableViewCellDelegate
|
||||||
)
|
)
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
var snapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
||||||
|
@ -81,11 +83,23 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate {
|
||||||
}
|
}
|
||||||
var newSnapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
var newSnapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
||||||
newSnapshot.appendSections([.main])
|
newSnapshot.appendSections([.main])
|
||||||
let items: [NotificationItem] = notifications.map { notification in
|
|
||||||
let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute()
|
let segment = self.selectedIndex.value
|
||||||
return NotificationItem.notification(objectID: notification.objectID, attribute: attribute)
|
switch segment {
|
||||||
|
case .everyThing:
|
||||||
|
let items: [NotificationItem] = notifications.map { notification in
|
||||||
|
let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute()
|
||||||
|
return NotificationItem.notification(objectID: notification.objectID, attribute: attribute)
|
||||||
|
}
|
||||||
|
newSnapshot.appendItems(items, toSection: .main)
|
||||||
|
case .mentions:
|
||||||
|
let items: [NotificationItem] = notifications.map { notification in
|
||||||
|
let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute()
|
||||||
|
return NotificationItem.notificationStatus(objectID: notification.objectID, attribute: attribute)
|
||||||
|
}
|
||||||
|
newSnapshot.appendItems(items, toSection: .main)
|
||||||
}
|
}
|
||||||
newSnapshot.appendItems(items, toSection: .main)
|
|
||||||
if !notifications.isEmpty, self.noMoreNotification.value == false {
|
if !notifications.isEmpty, self.noMoreNotification.value == false {
|
||||||
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,13 +92,13 @@ extension NotificationViewModel.LoadOldestState {
|
||||||
} receiveValue: { [weak viewModel] response in
|
} receiveValue: { [weak viewModel] response in
|
||||||
guard let viewModel = viewModel else { return }
|
guard let viewModel = viewModel else { return }
|
||||||
switch viewModel.selectedIndex.value {
|
switch viewModel.selectedIndex.value {
|
||||||
case .EveryThing:
|
case .everyThing:
|
||||||
if response.value.isEmpty {
|
if response.value.isEmpty {
|
||||||
stateMachine.enter(NoMore.self)
|
stateMachine.enter(NoMore.self)
|
||||||
} else {
|
} else {
|
||||||
stateMachine.enter(Idle.self)
|
stateMachine.enter(Idle.self)
|
||||||
}
|
}
|
||||||
case .Mentions:
|
case .mentions:
|
||||||
viewModel.noMoreNotification.value = response.value.isEmpty
|
viewModel.noMoreNotification.value = response.value.isEmpty
|
||||||
let list = response.value.filter { $0.type == Mastodon.Entity.Notification.NotificationType.mention }
|
let list = response.value.filter { $0.type == Mastodon.Entity.Notification.NotificationType.mention }
|
||||||
if list.isEmpty {
|
if list.isEmpty {
|
||||||
|
|
|
@ -23,13 +23,13 @@ final class NotificationViewModel: NSObject {
|
||||||
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
|
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
|
||||||
|
|
||||||
let viewDidLoad = PassthroughSubject<Void, Never>()
|
let viewDidLoad = PassthroughSubject<Void, Never>()
|
||||||
let selectedIndex = CurrentValueSubject<NotificationSegment, Never>(.EveryThing)
|
let selectedIndex = CurrentValueSubject<NotificationSegment, Never>(.everyThing)
|
||||||
let noMoreNotification = CurrentValueSubject<Bool, Never>(false)
|
let noMoreNotification = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
let activeMastodonAuthenticationBox: CurrentValueSubject<MastodonAuthenticationBox?, Never>
|
let activeMastodonAuthenticationBox: CurrentValueSubject<MastodonAuthenticationBox?, Never>
|
||||||
let fetchedResultsController: NSFetchedResultsController<MastodonNotification>!
|
let fetchedResultsController: NSFetchedResultsController<MastodonNotification>!
|
||||||
let notificationPredicate = CurrentValueSubject<NSPredicate?, Never>(nil)
|
let notificationPredicate = CurrentValueSubject<NSPredicate?, Never>(nil)
|
||||||
let cellFrameCache = NSCache<NSString, NSValue>()
|
let cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||||
|
|
||||||
var needsScrollToTopAfterDataSourceUpdate = false
|
var needsScrollToTopAfterDataSourceUpdate = false
|
||||||
let dataSourceDidUpdated = PassthroughSubject<Void, Never>()
|
let dataSourceDidUpdated = PassthroughSubject<Void, Never>()
|
||||||
|
@ -161,7 +161,7 @@ final class NotificationViewModel: NSObject {
|
||||||
|
|
||||||
extension NotificationViewModel {
|
extension NotificationViewModel {
|
||||||
enum NotificationSegment: Int {
|
enum NotificationSegment: Int {
|
||||||
case EveryThing
|
case everyThing
|
||||||
case Mentions
|
case mentions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,6 +203,9 @@ final class StatusView: UIView {
|
||||||
return actionToolbarContainer
|
return actionToolbarContainer
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// set display when needs bottom padding
|
||||||
|
let actionToolbarPlaceholderPaddingView = UIView()
|
||||||
|
|
||||||
let contentMetaText: MetaText = {
|
let contentMetaText: MetaText = {
|
||||||
let metaText = MetaText()
|
let metaText = MetaText()
|
||||||
metaText.textView.backgroundColor = .clear
|
metaText.textView.backgroundColor = .clear
|
||||||
|
@ -452,6 +455,13 @@ extension StatusView {
|
||||||
actionToolbarContainer.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
actionToolbarContainer.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
||||||
actionToolbarContainer.setContentHuggingPriority(.required - 1, for: .vertical)
|
actionToolbarContainer.setContentHuggingPriority(.required - 1, for: .vertical)
|
||||||
|
|
||||||
|
actionToolbarPlaceholderPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
containerStackView.addArrangedSubview(actionToolbarPlaceholderPaddingView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
actionToolbarPlaceholderPaddingView.heightAnchor.constraint(equalToConstant: 12).priority(.required - 1),
|
||||||
|
])
|
||||||
|
actionToolbarPlaceholderPaddingView.isHidden = true
|
||||||
|
|
||||||
headerContainerView.isHidden = true
|
headerContainerView.isHidden = true
|
||||||
statusMosaicImageViewContainer.isHidden = true
|
statusMosaicImageViewContainer.isHidden = true
|
||||||
pollTableView.isHidden = true
|
pollTableView.isHidden = true
|
||||||
|
|
Loading…
Reference in New Issue