Tusker/Tusker/Views/Attachments/AttachmentsContainerView.swift

352 lines
14 KiB
Swift

//
// AttachmentsContainerView.swift
// Tusker
//
// Created by Shadowfacts on 6/16/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class AttachmentsContainerView: UIView {
static let supportedAttachmentTypes = [Attachment.Kind.image, .video, .audio]
weak var delegate: AttachmentViewDelegate?
var statusID: String!
var attachments: [Attachment]!
let attachmentViews: NSHashTable<AttachmentView> = .weakObjects()
var blurView: UIVisualEffectView?
var hideButtonView: UIVisualEffectView?
var contentHidden: Bool! {
didSet {
guard let blurView = blurView,
let hideButtonView = hideButtonView else { return }
blurView.alpha = self.contentHidden ? 1 : 0
hideButtonView.alpha = self.contentHidden ? 0 : 1
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.isUserInteractionEnabled = true
createBlurView()
createHideButton()
}
func getAttachmentView(for attachment: Attachment) -> AttachmentView? {
return attachmentViews.allObjects.first { $0.attachment.id == attachment.id }
}
// MARK: - User Interaface
func updateUI(status: Status) {
self.statusID = status.id
attachments = status.attachments.filter { AttachmentsContainerView.supportedAttachmentTypes.contains($0.kind) }
attachmentViews.allObjects.forEach { $0.removeFromSuperview() }
attachmentViews.removeAllObjects()
if attachments.count > 0 {
self.isHidden = false
var accessibilityElements = [Any]()
switch attachments.count {
case 1:
let attachmentView = createAttachmentView(index: 0)
fillView(attachmentView)
sendSubviewToBack(attachmentView)
accessibilityElements.append(attachmentView)
case 2:
let left = createAttachmentView(index: 0)
let right = createAttachmentView(index: 1)
let stack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left,
right
])
fillView(stack)
sendSubviewToBack(stack)
NSLayoutConstraint.activate([
left.halfWidth()
])
accessibilityElements.append(left)
accessibilityElements.append(right)
case 3:
let left = createAttachmentView(index: 0)
let topRight = createAttachmentView(index: 1)
let bottomRight = createAttachmentView(index: 2)
let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left,
createAttachmentsStack(axis: .vertical, arrangedSubviews: [
topRight,
bottomRight
])
])
fillView(outerStack)
sendSubviewToBack(outerStack)
NSLayoutConstraint.activate([
left.halfWidth(),
topRight.halfHeight(),
])
accessibilityElements.append(left)
accessibilityElements.append(topRight)
accessibilityElements.append(bottomRight)
case 4:
let topLeft = createAttachmentView(index: 0)
let bottomLeft = createAttachmentView(index: 2)
let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
topLeft,
bottomLeft
])
let topRight = createAttachmentView(index: 1)
let bottomRight = createAttachmentView(index: 3)
let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left,
createAttachmentsStack(axis: .vertical, arrangedSubviews: [
topRight,
bottomRight
])
])
fillView(outerStack)
sendSubviewToBack(outerStack)
NSLayoutConstraint.activate([
left.halfWidth(),
topLeft.halfHeight(),
topRight.halfHeight(),
])
accessibilityElements.append(topLeft)
accessibilityElements.append(topRight)
accessibilityElements.append(bottomLeft)
accessibilityElements.append(bottomRight)
default: // more than 4
let moreView = UIView()
moreView.backgroundColor = .secondarySystemBackground
moreView.translatesAutoresizingMaskIntoConstraints = false
moreView.isUserInteractionEnabled = true
moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moreViewTapped)))
let moreLabel = UILabel()
moreLabel.text = "\(attachments.count - 3) more..."
moreLabel.textColor = .secondaryLabel
moreLabel.textAlignment = .center
moreLabel.translatesAutoresizingMaskIntoConstraints = false
moreView.addSubview(moreLabel)
moreView.accessibilityLabel = moreLabel.text
let topLeft = createAttachmentView(index: 0)
let bottomLeft = createAttachmentView(index: 2)
let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
topLeft,
bottomLeft
])
let topRight = createAttachmentView(index: 1)
let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left,
createAttachmentsStack(axis: .vertical, arrangedSubviews: [
topRight,
moreView
])
])
fillView(outerStack)
sendSubviewToBack(outerStack)
NSLayoutConstraint.activate([
left.halfWidth(),
topLeft.halfHeight(),
topRight.halfHeight(),
moreView.leadingAnchor.constraint(equalTo: moreLabel.leadingAnchor),
moreLabel.trailingAnchor.constraint(equalTo: moreView.trailingAnchor),
moreView.topAnchor.constraint(equalTo: moreLabel.topAnchor),
moreLabel.bottomAnchor.constraint(equalTo: moreView.bottomAnchor),
])
accessibilityElements.append(topLeft)
accessibilityElements.append(topRight)
accessibilityElements.append(bottomLeft)
accessibilityElements.append(moreView)
}
self.accessibilityElements = accessibilityElements
} else {
self.isHidden = true
}
contentHidden = Preferences.shared.blurAllMedia || status.sensitive
}
private func createAttachmentView(index: Int) -> AttachmentView {
let attachmentView = AttachmentView(attachment: attachments[index], index: index)
attachmentView.delegate = delegate
attachmentView.translatesAutoresizingMaskIntoConstraints = false
attachmentView.isAccessibilityElement = true
attachmentView.accessibilityTraits = [.image, .button]
attachmentView.accessibilityLabel = String(format: NSLocalizedString("Attachment %d", comment: "attachment at index accessiblity label"), index + 1)
attachmentViews.add(attachmentView)
return attachmentView
}
private func createAttachmentsStack(axis: NSLayoutConstraint.Axis, arrangedSubviews: [UIView]) -> UIStackView {
let stack = UIStackView(arrangedSubviews: arrangedSubviews)
stack.axis = axis
stack.spacing = 4
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}
private func createBlurView() {
let blur = UIBlurEffect(style: .dark)
let blurView = UIVisualEffectView(effect: blur)
blurView.alpha = 0
blurView.translatesAutoresizingMaskIntoConstraints = false
fillView(blurView)
let vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blur, style: .label))
vibrancyView.translatesAutoresizingMaskIntoConstraints = false
fillView(vibrancyView, in: blurView.contentView)
blurView.contentView.addSubview(vibrancyView)
let image = UIImage(systemName: "eye")!
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = "Sensitive Content"
let stack = UIStackView(arrangedSubviews: [
imageView,
label
])
stack.axis = .vertical
stack.alignment = .center
stack.translatesAutoresizingMaskIntoConstraints = false
vibrancyView.contentView.addSubview(stack)
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: image.size.width / image.size.height),
imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.2),
stack.centerXAnchor.constraint(equalTo: centerXAnchor),
stack.centerYAnchor.constraint(equalTo: centerYAnchor),
stack.widthAnchor.constraint(equalTo: widthAnchor)
])
self.blurView = blurView
blurView.isUserInteractionEnabled = true
blurView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(blurViewTapped)))
}
private func createHideButton() {
let blurEffect = UIBlurEffect(style: .regular)
let hideButtonBlurView = UIVisualEffectView(effect: blurEffect)
hideButtonBlurView.translatesAutoresizingMaskIntoConstraints = false
hideButtonBlurView.alpha = 1
hideButtonBlurView.isUserInteractionEnabled = true
hideButtonBlurView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(hideButtonTapped)))
addSubview(hideButtonBlurView)
self.hideButtonView = hideButtonBlurView
let maskLayer = CALayer()
let image = UIImage(systemName: "eye.slash.fill")!
maskLayer.contents = image.cgImage!
maskLayer.frame = CGRect(origin: .zero, size: image.size)
hideButtonBlurView.layer.mask = maskLayer
let hideButtonVibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect, style: .label))
hideButtonVibrancyView.translatesAutoresizingMaskIntoConstraints = false
hideButtonBlurView.contentView.addSubview(hideButtonVibrancyView)
let fillView = UIView()
fillView.translatesAutoresizingMaskIntoConstraints = false
fillView.backgroundColor = UIColor(displayP3Red: 0, green: 0, blue: 0, alpha: 0.5)
hideButtonVibrancyView.contentView.addSubview(fillView)
NSLayoutConstraint.activate([
hideButtonBlurView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
hideButtonBlurView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
hideButtonBlurView.widthAnchor.constraint(equalToConstant: image.size.width),
hideButtonBlurView.heightAnchor.constraint(equalToConstant: image.size.height),
hideButtonVibrancyView.leadingAnchor.constraint(equalTo: hideButtonBlurView.contentView.leadingAnchor),
hideButtonVibrancyView.trailingAnchor.constraint(equalTo: hideButtonBlurView.contentView.trailingAnchor),
hideButtonVibrancyView.topAnchor.constraint(equalTo: hideButtonBlurView.contentView.topAnchor),
hideButtonVibrancyView.bottomAnchor.constraint(equalTo: hideButtonBlurView.contentView.bottomAnchor),
fillView.leadingAnchor.constraint(equalTo: hideButtonBlurView.contentView.leadingAnchor),
fillView.trailingAnchor.constraint(equalTo: hideButtonBlurView.contentView.trailingAnchor),
fillView.topAnchor.constraint(equalTo: hideButtonBlurView.contentView.topAnchor),
fillView.bottomAnchor.constraint(equalTo: hideButtonBlurView.contentView.bottomAnchor),
])
}
private func fillView(_ view: UIView, in parentView: UIView? = nil) {
let parentView = parentView ?? self
parentView.addSubview(view)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
view.topAnchor.constraint(equalTo: parentView.topAnchor),
view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
])
}
// MARK: - Interaction
@objc func blurViewTapped() {
UIView.animate(withDuration: 0.2) {
self.contentHidden = false
}
}
@objc func hideButtonTapped() {
UIView.animate(withDuration: 0.2) {
self.contentHidden = true
}
}
@objc func showSensitiveContent() {
guard let blurView = blurView else { return }
blurView.alpha = 1
UIView.animate(withDuration: 0.2) {
blurView.alpha = 0
}
}
@objc func hideSensitiveContent() {
guard let blurView = self.blurView else { return }
blurView.alpha = 0
UIView.animate(withDuration: 0.2) {
blurView.alpha = 1
}
}
@objc func moreViewTapped() {
guard attachments.count > 4 else { return }
// the more view shows up in place of the fourth attachemtn view, show tapping it should start at the fourth attachment
delegate?.showAttachmentsGallery(startingAt: 3)
}
}
fileprivate extension UIView {
enum RelativeSize {
case full, half
var multiplier: CGFloat {
switch self {
case .full:
return 1
case .half:
return 0.5
}
}
}
func halfWidth(spacing: CGFloat = 4) -> NSLayoutConstraint {
return widthAnchor.constraint(equalTo: superview!.widthAnchor, multiplier: 0.5, constant: -spacing / 2)
}
func halfHeight(spacing: CGFloat = 4) -> NSLayoutConstraint {
return heightAnchor.constraint(equalTo: superview!.heightAnchor, multiplier: 0.5, constant: -spacing / 2)
}
}