chore: remove Redundant codes
This commit is contained in:
parent
bb03c10ef6
commit
da19f8f641
|
@ -47,7 +47,7 @@ extension NotificationSection {
|
||||||
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
|
cell.delegate = delegate
|
||||||
let frame = CGRect(x: 0, y: 0, width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, height: tableView.readableContentGuide.layoutFrame.height)
|
let frame = CGRect(x: 0, y: 0, width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, height: tableView.readableContentGuide.layoutFrame.height)
|
||||||
NotificationSection.configure(cell: cell,
|
StatusSection.configure(cell: cell,
|
||||||
dependency: dependency,
|
dependency: dependency,
|
||||||
readableLayoutFrame: frame,
|
readableLayoutFrame: frame,
|
||||||
timestampUpdatePublisher: timestampUpdatePublisher,
|
timestampUpdatePublisher: timestampUpdatePublisher,
|
||||||
|
@ -116,354 +116,3 @@ extension NotificationSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationSection {
|
|
||||||
static func configure(
|
|
||||||
cell: NotificationStatusTableViewCell,
|
|
||||||
dependency: NeedsDependency,
|
|
||||||
readableLayoutFrame: CGRect?,
|
|
||||||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
|
||||||
status: Status,
|
|
||||||
requestUserID: String,
|
|
||||||
statusItemAttribute: Item.StatusAttribute
|
|
||||||
) {
|
|
||||||
// setup attribute
|
|
||||||
statusItemAttribute.setupForStatus(status: status)
|
|
||||||
|
|
||||||
// set header
|
|
||||||
NotificationSection.configureHeader(cell: cell, status: status)
|
|
||||||
ManagedObjectObserver.observe(object: status)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { _ in
|
|
||||||
// do nothing
|
|
||||||
} receiveValue: { change in
|
|
||||||
guard case .update(let object) = change.changeType,
|
|
||||||
let newStatus = object as? Status else { return }
|
|
||||||
NotificationSection.configureHeader(cell: cell, status: newStatus)
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
|
|
||||||
// set name username
|
|
||||||
cell.statusView.nameLabel.text = {
|
|
||||||
let author = (status.reblog ?? status).author
|
|
||||||
return author.displayName.isEmpty ? author.username : author.displayName
|
|
||||||
}()
|
|
||||||
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
|
|
||||||
// set avatar
|
|
||||||
|
|
||||||
cell.statusView.avatarButton.isHidden = false
|
|
||||||
cell.statusView.avatarStackedContainerButton.isHidden = true
|
|
||||||
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
|
||||||
|
|
||||||
// set text
|
|
||||||
cell.statusView.activeTextLabel.configure(content: (status.reblog ?? status).content)
|
|
||||||
|
|
||||||
// set status text content warning
|
|
||||||
let isStatusTextSensitive = statusItemAttribute.isStatusTextSensitive ?? false
|
|
||||||
let spoilerText = (status.reblog ?? status).spoilerText ?? ""
|
|
||||||
cell.statusView.isStatusTextSensitive = isStatusTextSensitive
|
|
||||||
cell.statusView.updateContentWarningDisplay(isHidden: !isStatusTextSensitive)
|
|
||||||
cell.statusView.contentWarningTitle.text = {
|
|
||||||
if spoilerText.isEmpty {
|
|
||||||
return L10n.Common.Controls.Status.statusContentWarning
|
|
||||||
} else {
|
|
||||||
return L10n.Common.Controls.Status.statusContentWarning + ": \(spoilerText)"
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// prepare media attachments
|
|
||||||
let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
|
|
||||||
|
|
||||||
// set image
|
|
||||||
let mosiacImageViewModel = MosaicImageViewModel(mediaAttachments: mediaAttachments)
|
|
||||||
let imageViewMaxSize: CGSize = {
|
|
||||||
let maxWidth: CGFloat = {
|
|
||||||
// use timelinePostView width as container width
|
|
||||||
// that width follows readable width and keep constant width after rotate
|
|
||||||
let containerFrame = readableLayoutFrame ?? cell.statusView.frame
|
|
||||||
var containerWidth = containerFrame.width
|
|
||||||
containerWidth -= 10
|
|
||||||
containerWidth -= StatusView.avatarImageSize.width
|
|
||||||
return containerWidth
|
|
||||||
}()
|
|
||||||
let scale: CGFloat = {
|
|
||||||
switch mosiacImageViewModel.metas.count {
|
|
||||||
case 1: return 1.3
|
|
||||||
default: return 0.7
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return CGSize(width: maxWidth, height: maxWidth * scale)
|
|
||||||
}()
|
|
||||||
if mosiacImageViewModel.metas.count == 1 {
|
|
||||||
let meta = mosiacImageViewModel.metas[0]
|
|
||||||
let imageView = cell.statusView.statusMosaicImageViewContainer.setupImageView(aspectRatio: meta.size, maxSize: imageViewMaxSize)
|
|
||||||
imageView.af.setImage(
|
|
||||||
withURL: meta.url,
|
|
||||||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
|
||||||
imageTransition: .crossDissolve(0.2)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let imageViews = cell.statusView.statusMosaicImageViewContainer.setupImageViews(count: mosiacImageViewModel.metas.count, maxHeight: imageViewMaxSize.height)
|
|
||||||
for (i, imageView) in imageViews.enumerated() {
|
|
||||||
let meta = mosiacImageViewModel.metas[i]
|
|
||||||
imageView.af.setImage(
|
|
||||||
withURL: meta.url,
|
|
||||||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
|
||||||
imageTransition: .crossDissolve(0.2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cell.statusView.statusMosaicImageViewContainer.isHidden = mosiacImageViewModel.metas.isEmpty
|
|
||||||
let isStatusSensitive = statusItemAttribute.isStatusSensitive ?? false
|
|
||||||
cell.statusView.statusMosaicImageViewContainer.contentWarningOverlayView.blurVisualEffectView.effect = isStatusSensitive ? ContentWarningOverlayView.blurVisualEffect : nil
|
|
||||||
cell.statusView.statusMosaicImageViewContainer.contentWarningOverlayView.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0
|
|
||||||
cell.statusView.statusMosaicImageViewContainer.contentWarningOverlayView.isUserInteractionEnabled = isStatusSensitive
|
|
||||||
|
|
||||||
// set audio
|
|
||||||
if let _ = mediaAttachments.filter({ $0.type == .audio }).first {
|
|
||||||
cell.statusView.audioView.isHidden = false
|
|
||||||
cell.statusView.audioView.playButton.isSelected = false
|
|
||||||
cell.statusView.audioView.slider.isEnabled = false
|
|
||||||
cell.statusView.audioView.slider.setValue(0, animated: false)
|
|
||||||
} else {
|
|
||||||
cell.statusView.audioView.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// set GIF & video
|
|
||||||
let playerViewMaxSize: CGSize = {
|
|
||||||
let maxWidth: CGFloat = {
|
|
||||||
// use statusView width as container width
|
|
||||||
// that width follows readable width and keep constant width after rotate
|
|
||||||
let containerFrame = readableLayoutFrame ?? cell.statusView.frame
|
|
||||||
return containerFrame.width
|
|
||||||
}()
|
|
||||||
let scale: CGFloat = 1.3
|
|
||||||
return CGSize(width: maxWidth, height: maxWidth * scale)
|
|
||||||
}()
|
|
||||||
|
|
||||||
cell.statusView.playerContainerView.contentWarningOverlayView.blurVisualEffectView.effect = isStatusSensitive ? ContentWarningOverlayView.blurVisualEffect : nil
|
|
||||||
cell.statusView.playerContainerView.contentWarningOverlayView.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0
|
|
||||||
cell.statusView.playerContainerView.contentWarningOverlayView.isUserInteractionEnabled = isStatusSensitive
|
|
||||||
|
|
||||||
if let videoAttachment = mediaAttachments.filter({ $0.type == .gifv || $0.type == .video }).first,
|
|
||||||
let videoPlayerViewModel = dependency.context.videoPlaybackService.dequeueVideoPlayerViewModel(for: videoAttachment)
|
|
||||||
{
|
|
||||||
let parent = cell.delegate?.parent()
|
|
||||||
let playerContainerView = cell.statusView.playerContainerView
|
|
||||||
let playerViewController = playerContainerView.setupPlayer(
|
|
||||||
aspectRatio: videoPlayerViewModel.videoSize,
|
|
||||||
maxSize: playerViewMaxSize,
|
|
||||||
parent: parent
|
|
||||||
)
|
|
||||||
playerViewController.player = videoPlayerViewModel.player
|
|
||||||
playerViewController.showsPlaybackControls = videoPlayerViewModel.videoKind != .gif
|
|
||||||
playerContainerView.setMediaKind(kind: videoPlayerViewModel.videoKind)
|
|
||||||
if videoPlayerViewModel.videoKind == .gif {
|
|
||||||
playerContainerView.setMediaIndicator(isHidden: false)
|
|
||||||
} else {
|
|
||||||
videoPlayerViewModel.timeControlStatus.sink { timeControlStatus in
|
|
||||||
UIView.animate(withDuration: 0.33) {
|
|
||||||
switch timeControlStatus {
|
|
||||||
case .playing:
|
|
||||||
playerContainerView.setMediaIndicator(isHidden: true)
|
|
||||||
case .paused, .waitingToPlayAtSpecifiedRate:
|
|
||||||
playerContainerView.setMediaIndicator(isHidden: false)
|
|
||||||
@unknown default:
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
}
|
|
||||||
playerContainerView.isHidden = false
|
|
||||||
|
|
||||||
} else {
|
|
||||||
cell.statusView.playerContainerView.playerViewController.player?.pause()
|
|
||||||
cell.statusView.playerContainerView.playerViewController.player = nil
|
|
||||||
}
|
|
||||||
// set poll
|
|
||||||
let poll = (status.reblog ?? status).poll
|
|
||||||
NotificationSection.configurePoll(
|
|
||||||
cell: cell,
|
|
||||||
poll: poll,
|
|
||||||
requestUserID: requestUserID,
|
|
||||||
updateProgressAnimated: false,
|
|
||||||
timestampUpdatePublisher: timestampUpdatePublisher
|
|
||||||
)
|
|
||||||
if let poll = poll {
|
|
||||||
ManagedObjectObserver.observe(object: poll)
|
|
||||||
.sink { _ in
|
|
||||||
// do nothing
|
|
||||||
} receiveValue: { change in
|
|
||||||
guard case .update(let object) = change.changeType,
|
|
||||||
let newPoll = object as? Poll else { return }
|
|
||||||
NotificationSection.configurePoll(
|
|
||||||
cell: cell,
|
|
||||||
poll: newPoll,
|
|
||||||
requestUserID: requestUserID,
|
|
||||||
updateProgressAnimated: true,
|
|
||||||
timestampUpdatePublisher: timestampUpdatePublisher
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set date
|
|
||||||
let createdAt = (status.reblog ?? status).createdAt
|
|
||||||
cell.statusView.dateLabel.text = createdAt.shortTimeAgoSinceNow
|
|
||||||
timestampUpdatePublisher
|
|
||||||
.sink { _ in
|
|
||||||
cell.statusView.dateLabel.text = createdAt.shortTimeAgoSinceNow
|
|
||||||
}
|
|
||||||
.store(in: &cell.disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func configureHeader(
|
|
||||||
cell: NotificationStatusTableViewCell,
|
|
||||||
status: Status
|
|
||||||
) {
|
|
||||||
if status.reblog != nil {
|
|
||||||
cell.statusView.headerContainerStackView.isHidden = false
|
|
||||||
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage)
|
|
||||||
cell.statusView.headerInfoLabel.text = {
|
|
||||||
let author = status.author
|
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
|
||||||
return L10n.Common.Controls.Status.userReblogged(name)
|
|
||||||
}()
|
|
||||||
} else if let replyTo = status.replyTo {
|
|
||||||
cell.statusView.headerContainerStackView.isHidden = false
|
|
||||||
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage)
|
|
||||||
cell.statusView.headerInfoLabel.text = {
|
|
||||||
let author = replyTo.author
|
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
|
||||||
return L10n.Common.Controls.Status.userRepliedTo(name)
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
cell.statusView.headerContainerStackView.isHidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func configurePoll(
|
|
||||||
cell: NotificationStatusTableViewCell,
|
|
||||||
poll: Poll?,
|
|
||||||
requestUserID: String,
|
|
||||||
updateProgressAnimated: Bool,
|
|
||||||
timestampUpdatePublisher: AnyPublisher<Date, Never>
|
|
||||||
) {
|
|
||||||
guard let poll = poll,
|
|
||||||
let managedObjectContext = poll.managedObjectContext
|
|
||||||
else {
|
|
||||||
cell.statusView.pollTableView.isHidden = true
|
|
||||||
cell.statusView.pollStatusStackView.isHidden = true
|
|
||||||
cell.statusView.pollVoteButton.isHidden = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cell.statusView.pollTableView.isHidden = false
|
|
||||||
cell.statusView.pollStatusStackView.isHidden = false
|
|
||||||
cell.statusView.pollVoteCountLabel.text = {
|
|
||||||
if poll.multiple {
|
|
||||||
let count = poll.votersCount?.intValue ?? 0
|
|
||||||
if count > 1 {
|
|
||||||
return L10n.Common.Controls.Status.Poll.VoterCount.single(count)
|
|
||||||
} else {
|
|
||||||
return L10n.Common.Controls.Status.Poll.VoterCount.multiple(count)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let count = poll.votesCount.intValue
|
|
||||||
if count > 1 {
|
|
||||||
return L10n.Common.Controls.Status.Poll.VoteCount.single(count)
|
|
||||||
} else {
|
|
||||||
return L10n.Common.Controls.Status.Poll.VoteCount.multiple(count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if poll.expired {
|
|
||||||
cell.pollCountdownSubscription = nil
|
|
||||||
cell.statusView.pollCountdownLabel.text = L10n.Common.Controls.Status.Poll.closed
|
|
||||||
} else if let expiresAt = poll.expiresAt {
|
|
||||||
cell.statusView.pollCountdownLabel.text = L10n.Common.Controls.Status.Poll.timeLeft(expiresAt.shortTimeAgoSinceNow)
|
|
||||||
cell.pollCountdownSubscription = timestampUpdatePublisher
|
|
||||||
.sink { _ in
|
|
||||||
cell.statusView.pollCountdownLabel.text = L10n.Common.Controls.Status.Poll.timeLeft(expiresAt.shortTimeAgoSinceNow)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// assertionFailure()
|
|
||||||
cell.pollCountdownSubscription = nil
|
|
||||||
cell.statusView.pollCountdownLabel.text = "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
cell.statusView.pollTableView.allowsSelection = !poll.expired
|
|
||||||
|
|
||||||
let votedOptions = poll.options.filter { option in
|
|
||||||
(option.votedBy ?? Set()).map(\.id).contains(requestUserID)
|
|
||||||
}
|
|
||||||
let didVotedLocal = !votedOptions.isEmpty
|
|
||||||
let didVotedRemote = (poll.votedBy ?? Set()).map(\.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,
|
|
||||||
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 attribute: PollItem.Attribute = {
|
|
||||||
let selectState: PollItem.Attribute.SelectState = {
|
|
||||||
// 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 didVotedRemote, votedOptions.isEmpty {
|
|
||||||
return .none
|
|
||||||
} else {
|
|
||||||
return .off
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
let voteState: PollItem.Attribute.VoteState = {
|
|
||||||
var needsReveal: Bool
|
|
||||||
if poll.expired {
|
|
||||||
needsReveal = true
|
|
||||||
} else if didVotedRemote {
|
|
||||||
needsReveal = true
|
|
||||||
} else {
|
|
||||||
needsReveal = false
|
|
||||||
}
|
|
||||||
guard needsReveal else { return .hidden }
|
|
||||||
let percentage: Double = {
|
|
||||||
guard poll.votesCount.intValue > 0 else { return 0.0 }
|
|
||||||
return Double(option.votesCount?.intValue ?? 0) / Double(poll.votesCount.intValue)
|
|
||||||
}()
|
|
||||||
let voted = votedOptions.isEmpty ? true : votedOptions.contains(option)
|
|
||||||
return .reveal(voted: voted, percentage: percentage, animated: updateProgressAnimated)
|
|
||||||
}()
|
|
||||||
return PollItem.Attribute(selectState: selectState, voteState: voteState)
|
|
||||||
}()
|
|
||||||
let option = PollItem.opion(objectID: option.objectID, attribute: attribute)
|
|
||||||
return option
|
|
||||||
}
|
|
||||||
snapshot.appendItems(pollItems, toSection: .main)
|
|
||||||
cell.statusView.pollTableViewDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func configureEmptyStateHeader(
|
|
||||||
cell: TimelineHeaderTableViewCell,
|
|
||||||
attribute: Item.EmptyStateHeaderAttribute
|
|
||||||
) {
|
|
||||||
cell.timelineHeaderView.iconImageView.image = attribute.reason.iconImage
|
|
||||||
cell.timelineHeaderView.messageLabel.text = attribute.reason.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NotificationSection {
|
|
||||||
private static func formattedNumberTitleForActionButton(_ number: Int?) -> String {
|
|
||||||
guard let number = number, number > 0 else { return "" }
|
|
||||||
return String(number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,6 +10,12 @@ import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import AVKit
|
||||||
|
|
||||||
|
protocol StatusCell : DisposeBagCollectable {
|
||||||
|
var statusView: StatusView { get }
|
||||||
|
var pollCountdownSubscription: AnyCancellable? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
enum StatusSection: Equatable, Hashable {
|
enum StatusSection: Equatable, Hashable {
|
||||||
case main
|
case main
|
||||||
|
@ -127,7 +133,7 @@ extension StatusSection {
|
||||||
extension StatusSection {
|
extension StatusSection {
|
||||||
|
|
||||||
static func configure(
|
static func configure(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusCell,
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency,
|
||||||
readableLayoutFrame: CGRect?,
|
readableLayoutFrame: CGRect?,
|
||||||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
||||||
|
@ -260,14 +266,27 @@ extension StatusSection {
|
||||||
if let videoAttachment = mediaAttachments.filter({ $0.type == .gifv || $0.type == .video }).first,
|
if let videoAttachment = mediaAttachments.filter({ $0.type == .gifv || $0.type == .video }).first,
|
||||||
let videoPlayerViewModel = dependency.context.videoPlaybackService.dequeueVideoPlayerViewModel(for: videoAttachment)
|
let videoPlayerViewModel = dependency.context.videoPlaybackService.dequeueVideoPlayerViewModel(for: videoAttachment)
|
||||||
{
|
{
|
||||||
let parent = cell.delegate?.parent()
|
var parent: UIViewController?
|
||||||
|
var playerViewControllerDelegate: AVPlayerViewControllerDelegate? = nil
|
||||||
|
switch cell {
|
||||||
|
case is StatusTableViewCell:
|
||||||
|
let statusTableViewCell = cell as! StatusTableViewCell
|
||||||
|
parent = statusTableViewCell.delegate?.parent()
|
||||||
|
playerViewControllerDelegate = statusTableViewCell.delegate?.playerViewControllerDelegate
|
||||||
|
case is NotificationTableViewCell:
|
||||||
|
let notificationTableViewCell = cell as! NotificationTableViewCell
|
||||||
|
parent = notificationTableViewCell.delegate?.parent()
|
||||||
|
default:
|
||||||
|
parent = nil
|
||||||
|
assertionFailure("unknown cell")
|
||||||
|
}
|
||||||
let playerContainerView = cell.statusView.playerContainerView
|
let playerContainerView = cell.statusView.playerContainerView
|
||||||
let playerViewController = playerContainerView.setupPlayer(
|
let playerViewController = playerContainerView.setupPlayer(
|
||||||
aspectRatio: videoPlayerViewModel.videoSize,
|
aspectRatio: videoPlayerViewModel.videoSize,
|
||||||
maxSize: playerViewMaxSize,
|
maxSize: playerViewMaxSize,
|
||||||
parent: parent
|
parent: parent
|
||||||
)
|
)
|
||||||
playerViewController.delegate = cell.delegate?.playerViewControllerDelegate
|
playerViewController.delegate = playerViewControllerDelegate
|
||||||
playerViewController.player = videoPlayerViewModel.player
|
playerViewController.player = videoPlayerViewModel.player
|
||||||
playerViewController.showsPlaybackControls = videoPlayerViewModel.videoKind != .gif
|
playerViewController.showsPlaybackControls = videoPlayerViewModel.videoKind != .gif
|
||||||
playerContainerView.setMediaKind(kind: videoPlayerViewModel.videoKind)
|
playerContainerView.setMediaKind(kind: videoPlayerViewModel.videoKind)
|
||||||
|
@ -325,7 +344,9 @@ extension StatusSection {
|
||||||
StatusSection.configureActionToolBar(cell: cell, status: status, requestUserID: requestUserID)
|
StatusSection.configureActionToolBar(cell: cell, status: status, requestUserID: requestUserID)
|
||||||
|
|
||||||
// separator line
|
// separator line
|
||||||
cell.separatorLine.isHidden = statusItemAttribute.isSeparatorLineHidden
|
if let statusTableViewCell = cell as? StatusTableViewCell {
|
||||||
|
statusTableViewCell.separatorLine.isHidden = statusItemAttribute.isSeparatorLineHidden
|
||||||
|
}
|
||||||
|
|
||||||
// set date
|
// set date
|
||||||
let createdAt = (status.reblog ?? status).createdAt
|
let createdAt = (status.reblog ?? status).createdAt
|
||||||
|
@ -388,7 +409,7 @@ extension StatusSection {
|
||||||
|
|
||||||
|
|
||||||
static func configureHeader(
|
static func configureHeader(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusCell,
|
||||||
status: Status
|
status: Status
|
||||||
) {
|
) {
|
||||||
if status.reblog != nil {
|
if status.reblog != nil {
|
||||||
|
@ -416,7 +437,7 @@ extension StatusSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func configureActionToolBar(
|
static func configureActionToolBar(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusCell,
|
||||||
status: Status,
|
status: Status,
|
||||||
requestUserID: String
|
requestUserID: String
|
||||||
) {
|
) {
|
||||||
|
@ -447,7 +468,7 @@ extension StatusSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func configurePoll(
|
static func configurePoll(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusCell,
|
||||||
poll: Poll?,
|
poll: Poll?,
|
||||||
requestUserID: String,
|
requestUserID: String,
|
||||||
updateProgressAnimated: Bool,
|
updateProgressAnimated: Bool,
|
||||||
|
|
|
@ -131,9 +131,33 @@ extension NotificationViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NotificationViewController {
|
||||||
|
func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
let key = item.hashValue
|
||||||
|
let frame = cell.frame
|
||||||
|
viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||||
|
guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension }
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension }
|
||||||
|
guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else {
|
||||||
|
if case .bottomLoader = item {
|
||||||
|
return TimelineLoaderTableViewCell.cellHeight
|
||||||
|
} else {
|
||||||
|
return UITableView.automaticDimension
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ceil(frame.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
|
|
||||||
extension NotificationViewController: UITableViewDelegate {
|
extension NotificationViewController: UITableViewDelegate {
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
final class NotificationStatusTableViewCell: UITableViewCell {
|
final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
|
||||||
static let actionImageBorderWidth: CGFloat = 2
|
static let actionImageBorderWidth: CGFloat = 2
|
||||||
static let statusPadding = UIEdgeInsets(top: 50, left: 73, bottom: 24, right: 24)
|
static let statusPadding = UIEdgeInsets(top: 50, left: 73, bottom: 24, right: 24)
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
|
@ -46,7 +46,7 @@ extension StatusTableViewCellDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class StatusTableViewCell: UITableViewCell {
|
final class StatusTableViewCell: UITableViewCell, StatusCell {
|
||||||
|
|
||||||
static let bottomPaddingHeight: CGFloat = 10
|
static let bottomPaddingHeight: CGFloat = 10
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import UIKit
|
||||||
|
|
||||||
class AudioContainerViewModel {
|
class AudioContainerViewModel {
|
||||||
static func configure(
|
static func configure(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusCell,
|
||||||
audioAttachment: Attachment,
|
audioAttachment: Attachment,
|
||||||
audioService: AudioPlaybackService
|
audioService: AudioPlaybackService
|
||||||
) {
|
) {
|
||||||
|
@ -51,7 +51,7 @@ class AudioContainerViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func observePlayer(
|
static func observePlayer(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusCell,
|
||||||
audioAttachment: Attachment,
|
audioAttachment: Attachment,
|
||||||
audioService: AudioPlaybackService
|
audioService: AudioPlaybackService
|
||||||
) {
|
) {
|
||||||
|
|
Loading…
Reference in New Issue