metatext/Views/UIKit/AttachmentView.swift

241 lines
9.9 KiB
Swift
Raw Permalink 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 AVKit
2021-03-04 01:15:40 +00:00
import BlurHash
2021-02-06 06:54:26 +00:00
import Combine
2021-02-22 23:59:33 +00:00
import SDWebImage
2020-09-05 02:31:43 +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 AttachmentView: UIView {
2020-10-15 07:44:01 +00:00
let playerView = PlayerView()
2021-02-22 23:59:33 +00:00
let imageView = SDAnimatedImageView()
2021-01-08 06:11:33 +00:00
let removeButton = UIButton(type: .close)
2021-03-05 20:36:49 +00:00
let uncaptionedLabel = CapsuleLabel()
2021-01-08 06:11:33 +00:00
let selectionButton = UIButton()
2020-10-15 07:44:01 +00:00
var playing: Bool = false {
didSet {
if playing {
play()
2020-10-22 05:05:50 +00:00
imageView.tag = 0
playerView.tag = viewModel.tag
2020-10-15 07:44:01 +00:00
} else {
stop()
2020-10-22 05:05:50 +00:00
imageView.tag = viewModel.tag
playerView.tag = 0
2020-10-15 07:44:01 +00:00
}
}
}
private let viewModel: AttachmentViewModel
2021-01-08 06:11:33 +00:00
private let parentViewModel: AttachmentsRenderingViewModel
2021-02-06 06:54:26 +00:00
private var playerCancellable: AnyCancellable?
2020-08-28 19:56:28 +00:00
2021-01-08 06:11:33 +00:00
init(viewModel: AttachmentViewModel, parentViewModel: AttachmentsRenderingViewModel) {
2020-08-28 19:56:28 +00:00
self.viewModel = viewModel
2021-01-08 06:11:33 +00:00
self.parentViewModel = parentViewModel
2020-08-28 19:56:28 +00:00
super.init(frame: .zero)
2020-10-15 07:44:01 +00:00
initialSetup()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
2021-01-04 07:17:38 +00:00
override func layoutSubviews() {
super.layoutSubviews()
if let focus = viewModel.attachment.meta?.focus {
2021-01-10 01:26:51 +00:00
let viewsAndMediaSizes: [(UIView, CGSize?)] = [
2021-01-04 07:17:38 +00:00
(imageView, imageView.image?.size),
(playerView, playerView.player?.currentItem?.presentationSize)]
2021-01-10 01:26:51 +00:00
for (view, mediaSize) in viewsAndMediaSizes {
guard let size = mediaSize else { continue }
2021-01-04 07:17:38 +00:00
2021-01-10 01:26:51 +00:00
view.setContentsRect(focus: focus, mediaSize: size)
2021-01-04 07:17:38 +00:00
}
}
}
2020-10-15 07:44:01 +00:00
}
2021-01-08 06:11:33 +00:00
extension AttachmentView {
2020-10-15 07:44:01 +00:00
func play() {
2021-03-29 06:04:14 +00:00
let player = PlayerCache.shared.player(url: viewModel.attachment.url.url)
2020-10-15 07:44:01 +00:00
2021-02-06 06:54:26 +00:00
playerCancellable = NotificationCenter.default.publisher(
for: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
.sink { _ in
player.currentItem?.seek(to: .zero) { success in
guard success else { return }
player.play()
}
2020-10-15 07:44:01 +00:00
}
player.isMuted = true
player.play()
playerView.player = player
playerView.isHidden = false
}
func stop() {
if let item = playerView.player?.currentItem {
let imageGenerator = AVAssetImageGenerator(asset: item.asset)
imageGenerator.requestedTimeToleranceAfter = .zero
imageGenerator.requestedTimeToleranceBefore = .zero
if let image = try? imageGenerator.copyCGImage(at: item.currentTime(), actualTime: nil) {
imageView.image = .init(cgImage: image)
}
}
playerView.player = nil
playerView.isHidden = true
}
func selectAttachment() {
parentViewModel.attachmentSelected(viewModel: viewModel)
}
2020-10-15 07:44:01 +00:00
}
2021-01-08 06:11:33 +00:00
private extension AttachmentView {
2020-10-15 07:44:01 +00:00
// swiftlint:disable:next function_body_length
func initialSetup() {
2020-08-28 19:56:28 +00:00
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
2021-02-22 23:59:33 +00:00
imageView.autoPlayAnimatedImage = false
2020-10-22 05:05:50 +00:00
imageView.tag = viewModel.tag
2020-08-28 19:56:28 +00:00
2020-10-15 07:44:01 +00:00
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
let playView = UIVisualEffectView(effect: blurEffect)
let playVibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect))
let playImageView = UIImageView(
image: UIImage(systemName: "play.circle",
withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle)))
playImageView.translatesAutoresizingMaskIntoConstraints = false
playVibrancyView.translatesAutoresizingMaskIntoConstraints = false
playVibrancyView.contentView.addSubview(playImageView)
playView.contentView.addSubview(playVibrancyView)
addSubview(playView)
playView.translatesAutoresizingMaskIntoConstraints = false
playView.clipsToBounds = true
playView.layer.cornerRadius = .defaultCornerRadius
playView.isHidden = viewModel.attachment.type == .image
addSubview(playerView)
playerView.translatesAutoresizingMaskIntoConstraints = false
2020-10-21 08:07:13 +00:00
playerView.videoGravity = .resizeAspectFill
2020-10-15 07:44:01 +00:00
playerView.isHidden = true
2021-01-08 06:11:33 +00:00
addSubview(selectionButton)
selectionButton.translatesAutoresizingMaskIntoConstraints = false
selectionButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
selectionButton.addAction(
UIAction { [weak self] _ in self?.selectAttachment() },
2021-01-08 06:11:33 +00:00
for: .touchUpInside)
selectionButton.accessibilityLabel = NSLocalizedString("compose.attachment.edit", comment: "")
if let description = viewModel.attachment.description, !description.isEmpty {
selectionButton.accessibilityLabel?.appendWithSeparator(description)
}
2021-01-08 06:11:33 +00:00
addSubview(removeButton)
removeButton.translatesAutoresizingMaskIntoConstraints = false
removeButton.showsMenuAsPrimaryAction = true
2021-02-02 18:02:30 +00:00
removeButton.accessibilityLabel = NSLocalizedString("compose.attachment.remove", comment: "")
2021-01-08 06:11:33 +00:00
removeButton.menu = UIMenu(
children: [
UIAction(
title: NSLocalizedString("remove", comment: ""),
image: UIImage(systemName: "trash"),
attributes: .destructive) { [weak self] _ in
guard let self = self else { return }
self.parentViewModel.removeAttachment(viewModel: self.viewModel)
}])
2020-08-28 19:56:28 +00:00
2021-03-05 20:36:49 +00:00
addSubview(uncaptionedLabel)
uncaptionedLabel.translatesAutoresizingMaskIntoConstraints = false
uncaptionedLabel.text = NSLocalizedString("compose.attachment.uncaptioned", comment: "")
uncaptionedLabel.isHidden = !(parentViewModel.canRemoveAttachments
&& (viewModel.attachment.description?.isEmpty ?? true))
2021-01-31 14:52:20 +00:00
2020-08-28 19:56:28 +00:00
switch viewModel.attachment.type {
2020-10-15 07:44:01 +00:00
case .image, .video, .gifv:
2021-03-04 01:15:40 +00:00
let placeholderImage: UIImage?
if let blurHash = viewModel.attachment.blurhash {
placeholderImage = UIImage(blurHash: blurHash, size: .blurHashSize)
} else {
placeholderImage = nil
}
imageView.sd_setImage(
2021-03-29 06:04:14 +00:00
with: viewModel.attachment.previewUrl?.url,
2021-03-04 01:15:40 +00:00
placeholderImage: placeholderImage) { [weak self] _, _, _, _ in
2021-02-22 23:59:33 +00:00
self?.layoutSubviews()
}
2020-10-20 06:41:10 +00:00
case .audio:
playImageView.image = UIImage(systemName: "waveform.circle",
withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle))
backgroundColor = .secondarySystemBackground
2021-02-07 01:03:15 +00:00
case .unknown:
playImageView.image = UIImage(systemName: "link",
withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle))
backgroundColor = .secondarySystemBackground
2020-08-28 19:56:28 +00:00
}
2020-09-29 01:32:28 +00:00
NSLayoutConstraint.activate([
2020-10-15 07:44:01 +00:00
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
playerView.leadingAnchor.constraint(equalTo: leadingAnchor),
playerView.trailingAnchor.constraint(equalTo: trailingAnchor),
playerView.topAnchor.constraint(equalTo: topAnchor),
playerView.bottomAnchor.constraint(equalTo: bottomAnchor),
playImageView.centerXAnchor.constraint(equalTo: playView.contentView.centerXAnchor),
playImageView.centerYAnchor.constraint(equalTo: playView.contentView.centerYAnchor),
playVibrancyView.leadingAnchor.constraint(equalTo: playView.leadingAnchor),
playVibrancyView.topAnchor.constraint(equalTo: playView.topAnchor),
playVibrancyView.trailingAnchor.constraint(equalTo: playView.trailingAnchor),
playVibrancyView.bottomAnchor.constraint(equalTo: playView.bottomAnchor),
playView.centerXAnchor.constraint(equalTo: centerXAnchor),
playView.centerYAnchor.constraint(equalTo: centerYAnchor),
playView.trailingAnchor.constraint(
equalTo: playImageView.trailingAnchor, constant: .compactSpacing),
playView.bottomAnchor.constraint(
equalTo: playImageView.bottomAnchor, constant: .compactSpacing),
playImageView.topAnchor.constraint(
equalTo: playView.topAnchor, constant: .compactSpacing),
playImageView.leadingAnchor.constraint(
equalTo: playView.leadingAnchor, constant: .compactSpacing),
2021-01-08 06:11:33 +00:00
selectionButton.leadingAnchor.constraint(equalTo: leadingAnchor),
selectionButton.trailingAnchor.constraint(equalTo: trailingAnchor),
selectionButton.topAnchor.constraint(equalTo: topAnchor),
selectionButton.bottomAnchor.constraint(equalTo: bottomAnchor),
removeButton.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
2021-01-31 14:52:20 +00:00
removeButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
2021-03-05 20:36:49 +00:00
uncaptionedLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
uncaptionedLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
2020-09-29 01:32:28 +00:00
])
2021-02-02 18:02:30 +00:00
var accessibilityLabel = viewModel.attachment.type.accessibilityName
if let description = viewModel.attachment.description {
accessibilityLabel.appendWithSeparator(description)
}
self.accessibilityLabel = accessibilityLabel
2020-08-28 19:56:28 +00:00
}
}