Tusker/Tusker/Views/Status/StatusMetaIndicatorsView.swift

199 lines
7.3 KiB
Swift

//
// StatusMetaIndicatorsView.swift
// Tusker
//
// Created by Shadowfacts on 1/22/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class StatusMetaIndicatorsView: UIView {
var allowedIndicators: Indicator = .all
var squeezeHorizontal = false
// The axis in which the indicators grow
var primaryAxis: NSLayoutConstraint.Axis = .vertical
// Only used when using single axis mode
var secondaryAxisAlignment: Alignment = .leading
private var images: [UIImageView] = []
private var isUsingSingleAxis = false
private var statusID: String?
private var needsSingleAxis: Bool {
traitCollection.preferredContentSizeCategory > .extraLarge
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
NotificationCenter.default.addObserver(self, selector: #selector(configureImageViews), name: UIAccessibility.boldTextStatusDidChangeNotification, object: nil)
#if os(visionOS)
registerForTraitChanges([UITraitPreferredContentSizeCategory.self]) { (self: Self, previousTraitCollection) in
if self.isUsingSingleAxis != self.needsSingleAxis {
for image in self.images {
self.configureImageView(image)
}
self.placeImageViews(self.images)
}
}
#endif
}
#if !os(visionOS)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if isUsingSingleAxis != needsSingleAxis {
for image in images {
configureImageView(image)
}
placeImageViews(images)
}
}
#endif
@objc private func configureImageViews() {
for image in images {
configureImageView(image)
}
}
private func configureImageView(_ imageView: UIImageView) {
let weight: UIImage.SymbolWeight = UIAccessibility.isBoldTextEnabled ? .regular : traitCollection.preferredContentSizeCategory > .large ? .light : .thin
let scale: UIImage.SymbolScale = traitCollection.preferredContentSizeCategory > .extraLarge ? .large : .default
imageView.preferredSymbolConfiguration = .init(pointSize: 0, weight: weight, scale: scale)
}
func updateUI(status: StatusMO) {
guard statusID != status.id else {
return
}
statusID = status.id
var images: [UIImage] = []
if allowedIndicators.contains(.reply) && Preferences.shared.showIsStatusReplyIcon && status.inReplyToID != nil {
images.append(UIImage(systemName: "bubble.left.and.bubble.right")!)
}
if allowedIndicators.contains(.visibility) && Preferences.shared.alwaysShowStatusVisibilityIcon {
images.append(UIImage(systemName: status.visibility.unfilledImageName)!)
}
if allowedIndicators.contains(.localOnly) && status.localOnly {
images.append(UIImage(named: "link.broken")!)
}
let views = images.map {
let v = UIImageView(image: $0)
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .scaleAspectFit
v.tintColor = .secondaryLabel
configureImageView(v)
return v
}
placeImageViews(views)
}
private func placeImageViews(_ imageViews: [UIImageView]) {
images.forEach { $0.removeFromSuperview() }
images = imageViews
guard !images.isEmpty else {
return
}
isUsingSingleAxis = needsSingleAxis
if needsSingleAxis {
for v in images {
addSubview(v)
switch (primaryAxis, secondaryAxisAlignment) {
case (.horizontal, .leading):
v.topAnchor.constraint(equalTo: topAnchor).isActive = true
case (.horizontal, .trailing):
v.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
case (.vertical, .leading):
v.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
case (.vertical, .trailing):
v.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
case (_, _):
fatalError()
}
}
if primaryAxis == .vertical {
images.first!.topAnchor.constraint(equalTo: topAnchor).isActive = true
images.last!.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
} else {
images.first!.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
images.last!.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
for (a, b) in zip(images, images.dropFirst()) {
if primaryAxis == .vertical {
b.topAnchor.constraint(equalTo: a.bottomAnchor, constant: 4).isActive = true
} else {
b.leadingAnchor.constraint(equalTo: a.trailingAnchor, constant: 4).isActive = true
}
}
return
}
guard primaryAxis == .vertical || imageViews.count <= 2 else {
fatalError("StatusMetaIndicatorsView does not support horizontal primary axis with more than 2 views yet")
}
for (index, v) in images.enumerated() {
addSubview(v)
if index % 2 == 0 {
if index == images.count - 1 {
v.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
v.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor).isActive = true
} else {
v.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
}
} else {
if squeezeHorizontal {
v.leadingAnchor.constraint(equalTo: self.images[index - 1].trailingAnchor, constant: 4).isActive = true
} else {
v.leadingAnchor.constraint(greaterThanOrEqualTo: self.images[index - 1].trailingAnchor, constant: 4).isActive = true
}
v.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
let row = index / 2
if row == 0 {
v.topAnchor.constraint(equalTo: topAnchor).isActive = true
} else {
v.topAnchor.constraint(equalTo: self.images[index - 1].bottomAnchor, constant: 4).isActive = true
}
v.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor).isActive = true
}
}
struct Indicator: OptionSet {
let rawValue: Int
static let reply = Indicator(rawValue: 1 << 0)
static let visibility = Indicator(rawValue: 1 << 1)
static let localOnly = Indicator(rawValue: 1 << 2)
static let all: Indicator = [.reply, .visibility, .localOnly]
}
enum Alignment {
case leading, trailing
}
}