metatext/Views/UIKit/AttachmentsView.swift

227 lines
10 KiB
Swift
Raw Normal View History

2020-08-28 19:56:28 +00:00
// Copyright © 2020 Metabolist. All rights reserved.
2020-10-15 07:44:01 +00:00
import Combine
2021-01-19 00:46:38 +00:00
import Mastodon
2020-08-28 19:56:28 +00:00
import UIKit
2020-09-01 07:33:49 +00:00
import ViewModels
2020-08-28 19:56:28 +00:00
2021-01-08 06:11:33 +00:00
final class AttachmentsView: UIView {
2020-08-28 19:56:28 +00:00
private let containerStackView = UIStackView()
private let leftStackView = UIStackView()
private let rightStackView = UIStackView()
2020-10-14 00:03:01 +00:00
private let curtain = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial))
private let curtainButton = UIButton(type: .system)
private let hideButtonBackground = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial))
private let hideButton = UIButton()
2020-10-12 05:37:34 +00:00
private var aspectRatioConstraint: NSLayoutConstraint?
2020-10-15 07:44:01 +00:00
private var cancellables = Set<AnyCancellable>()
2020-08-28 19:56:28 +00:00
2021-01-08 06:11:33 +00:00
var viewModel: AttachmentsRenderingViewModel? {
2020-08-28 19:56:28 +00:00
didSet {
for stackView in [leftStackView, rightStackView] {
for view in stackView.arrangedSubviews {
stackView.removeArrangedSubview(view)
view.removeFromSuperview()
}
}
2021-01-08 06:11:33 +00:00
guard let viewModel = viewModel else { return }
2020-08-28 19:56:28 +00:00
2021-01-08 06:11:33 +00:00
rightStackView.isHidden = viewModel.attachmentViewModels.count == 1
2020-08-28 19:56:28 +00:00
2021-01-08 06:11:33 +00:00
for (index, attachmentViewModel) in viewModel.attachmentViewModels.enumerated() {
let attachmentView = AttachmentView(viewModel: attachmentViewModel, parentViewModel: viewModel)
attachmentView.playing = viewModel.shouldShowAttachments && attachmentViewModel.shouldAutoplay
attachmentView.removeButton.isHidden = !viewModel.canRemoveAttachments
2021-02-02 18:02:30 +00:00
attachmentView.isAccessibilityElement = !viewModel.canRemoveAttachments
2020-10-20 06:41:10 +00:00
2021-01-08 06:11:33 +00:00
if viewModel.attachmentViewModels.count == 2 && index == 1
|| viewModel.attachmentViewModels.count == 3 && index != 0
|| viewModel.attachmentViewModels.count > 3 && index % 2 != 0 {
2020-10-11 21:03:25 +00:00
rightStackView.addArrangedSubview(attachmentView)
2020-08-28 19:56:28 +00:00
} else {
2020-10-11 21:03:25 +00:00
leftStackView.addArrangedSubview(attachmentView)
2020-08-28 19:56:28 +00:00
}
}
2020-10-12 05:37:34 +00:00
let newAspectRatio: CGFloat
2021-01-08 06:11:33 +00:00
if viewModel.attachmentViewModels.count == 1,
let aspectRatio = viewModel.attachmentViewModels.first?.attachment.aspectRatio {
2020-10-12 05:37:34 +00:00
newAspectRatio = max(CGFloat(aspectRatio), 16 / 9)
} else {
newAspectRatio = 16 / 9
}
aspectRatioConstraint?.isActive = false
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: newAspectRatio)
aspectRatioConstraint?.priority = .justBelowMax
aspectRatioConstraint?.isActive = true
2020-10-14 00:03:01 +00:00
2021-01-08 06:11:33 +00:00
curtain.isHidden = viewModel.shouldShowAttachments
2020-10-14 00:03:01 +00:00
curtainButton.setTitle(
2021-01-08 06:11:33 +00:00
NSLocalizedString((viewModel.sensitive)
2020-10-14 00:03:01 +00:00
? "attachment.sensitive-content"
: "attachment.media-hidden",
comment: ""),
for: .normal)
2021-01-08 06:11:33 +00:00
hideButtonBackground.isHidden = !viewModel.shouldShowHideAttachmentsButton
2021-02-02 18:02:30 +00:00
if curtain.isHidden {
let type: Attachment.AttachmentType
if viewModel.attachmentViewModels
.allSatisfy({ $0.attachment.type == .image || $0.attachment.type == .gifv }) {
type = .image
} else if viewModel.attachmentViewModels.allSatisfy({ $0.attachment.type == .video }) {
type = .video
} else if viewModel.attachmentViewModels.allSatisfy({ $0.attachment.type == .audio }) {
type = .audio
} else {
type = .unknown
}
2021-02-09 05:53:52 +00:00
var accessibilityLabel = type.accessibilityNames(count: viewModel.attachmentViewModels.count)
for attachmentViewModel in viewModel.attachmentViewModels {
guard let description = attachmentViewModel.attachment.description,
!description.isEmpty
else { continue }
accessibilityLabel.appendWithSeparator(attachmentViewModel.attachment.type.accessibilityName)
accessibilityLabel.appendWithSeparator(description)
}
self.accessibilityLabel = accessibilityLabel
2021-02-02 18:02:30 +00:00
} else {
accessibilityLabel = curtainButton.title(for: .normal)
}
2020-08-28 19:56:28 +00:00
}
}
override init(frame: CGRect) {
super.init(frame: frame)
2020-10-13 20:11:27 +00:00
initialSetup()
2020-08-28 19:56:28 +00:00
}
2020-10-13 20:11:27 +00:00
@available(*, unavailable)
2020-08-28 19:56:28 +00:00
required init?(coder: NSCoder) {
2020-10-13 20:11:27 +00:00
fatalError("init(coder:) has not been implemented")
2020-08-28 19:56:28 +00:00
}
}
2021-01-08 06:11:33 +00:00
extension AttachmentsView {
2021-01-19 00:46:38 +00:00
static func estimatedHeight(width: CGFloat,
2021-01-26 00:06:35 +00:00
identityContext: IdentityContext,
2021-01-19 00:46:38 +00:00
status: Status,
configuration: CollectionItem.StatusConfiguration) -> CGFloat {
let height: CGFloat
if status.displayStatus.mediaAttachments.count == 1,
let aspectRatio = status.mediaAttachments.first?.aspectRatio {
height = width / max(CGFloat(aspectRatio), 16 / 9)
} else {
height = width / (16 / 9)
}
return height
}
2020-10-15 07:44:01 +00:00
var shouldAutoplay: Bool {
guard !isHidden, let viewModel = viewModel, viewModel.shouldShowAttachments else { return false }
2020-10-22 22:16:06 +00:00
return viewModel.attachmentViewModels.allSatisfy(\.shouldAutoplay)
2020-10-15 07:44:01 +00:00
}
var attachmentViewAccessibilityCustomActions: [UIAccessibilityCustomAction] {
attachmentViews.compactMap { attachmentView in
guard let accessibilityLabel = attachmentView.accessibilityLabel else { return nil }
return UIAccessibilityCustomAction(name: accessibilityLabel) { _ in
attachmentView.selectAttachment()
return true
}
}
}
2020-10-15 07:44:01 +00:00
}
2021-01-08 06:11:33 +00:00
private extension AttachmentsView {
2020-10-14 00:03:01 +00:00
// swiftlint:disable:next function_body_length
2020-10-13 20:11:27 +00:00
func initialSetup() {
2020-08-28 19:56:28 +00:00
backgroundColor = .clear
layoutMargins = .zero
clipsToBounds = true
2020-09-29 01:32:28 +00:00
layer.cornerRadius = .defaultCornerRadius
2020-08-28 19:56:28 +00:00
addSubview(containerStackView)
containerStackView.translatesAutoresizingMaskIntoConstraints = false
containerStackView.distribution = .fillEqually
2020-09-29 01:32:28 +00:00
containerStackView.spacing = .compactSpacing
2020-08-28 19:56:28 +00:00
leftStackView.distribution = .fillEqually
2020-09-29 01:32:28 +00:00
leftStackView.spacing = .compactSpacing
2020-08-28 19:56:28 +00:00
leftStackView.axis = .vertical
rightStackView.distribution = .fillEqually
2020-09-29 01:32:28 +00:00
rightStackView.spacing = .compactSpacing
2020-08-28 19:56:28 +00:00
rightStackView.axis = .vertical
containerStackView.addArrangedSubview(leftStackView)
containerStackView.addArrangedSubview(rightStackView)
2020-09-29 01:32:28 +00:00
2020-10-14 00:03:01 +00:00
let toggleShowAttachmentsAction = UIAction { [weak self] _ in
self?.viewModel?.toggleShowAttachments()
}
addSubview(hideButtonBackground)
hideButtonBackground.translatesAutoresizingMaskIntoConstraints = false
hideButtonBackground.clipsToBounds = true
hideButtonBackground.layer.cornerRadius = .defaultCornerRadius
hideButton.addAction(toggleShowAttachmentsAction, for: .touchUpInside)
hideButtonBackground.contentView.addSubview(hideButton)
hideButton.translatesAutoresizingMaskIntoConstraints = false
hideButton.setImage(
UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
for: .normal)
addSubview(curtain)
curtain.translatesAutoresizingMaskIntoConstraints = false
curtain.contentView.addSubview(curtainButton)
curtainButton.addAction(toggleShowAttachmentsAction, for: .touchUpInside)
curtainButton.translatesAutoresizingMaskIntoConstraints = false
curtainButton.titleLabel?.font = .preferredFont(forTextStyle: .headline)
curtainButton.titleLabel?.adjustsFontForContentSizeCategory = true
2020-09-29 01:32:28 +00:00
NSLayoutConstraint.activate([
2020-10-14 00:03:01 +00:00
containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
containerStackView.topAnchor.constraint(equalTo: topAnchor),
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
hideButtonBackground.topAnchor.constraint(equalTo: topAnchor, constant: .defaultSpacing),
hideButtonBackground.leadingAnchor.constraint(equalTo: leadingAnchor, constant: .defaultSpacing),
hideButton.topAnchor.constraint(
equalTo: hideButtonBackground.contentView.topAnchor,
constant: .compactSpacing),
hideButton.leadingAnchor.constraint(
equalTo: hideButtonBackground.contentView.leadingAnchor,
constant: .compactSpacing),
hideButtonBackground.contentView.trailingAnchor.constraint(
equalTo: hideButton.trailingAnchor,
constant: .compactSpacing),
hideButtonBackground.contentView.bottomAnchor.constraint(
equalTo: hideButton.bottomAnchor,
constant: .compactSpacing),
curtain.topAnchor.constraint(equalTo: topAnchor),
curtain.leadingAnchor.constraint(equalTo: leadingAnchor),
curtain.trailingAnchor.constraint(equalTo: trailingAnchor),
curtain.bottomAnchor.constraint(equalTo: bottomAnchor),
curtainButton.topAnchor.constraint(equalTo: curtain.contentView.topAnchor),
curtainButton.leadingAnchor.constraint(equalTo: curtain.contentView.leadingAnchor),
curtainButton.trailingAnchor.constraint(equalTo: curtain.contentView.trailingAnchor),
curtainButton.bottomAnchor.constraint(equalTo: curtain.contentView.bottomAnchor)
2020-09-29 01:32:28 +00:00
])
2020-10-15 07:44:01 +00:00
}
2021-01-08 06:11:33 +00:00
var attachmentViews: [AttachmentView] {
2020-10-15 07:44:01 +00:00
(leftStackView.arrangedSubviews + rightStackView.arrangedSubviews)
2021-01-08 06:11:33 +00:00
.compactMap { $0 as? AttachmentView }
2020-08-28 19:56:28 +00:00
}
}