forked from zelo72/mastodon-ios
180 lines
6.5 KiB
Swift
180 lines
6.5 KiB
Swift
//
|
|
// SidebarListContentView.swift
|
|
// Mastodon
|
|
//
|
|
// Created by Cirno MainasuK on 2021-9-24.
|
|
//
|
|
|
|
import os.log
|
|
import UIKit
|
|
import MetaTextKit
|
|
import FLAnimatedImage
|
|
|
|
final class SidebarListContentView: UIView, UIContentView {
|
|
|
|
let logger = Logger(subsystem: "SidebarListContentView", category: "UI")
|
|
|
|
let imageView = UIImageView()
|
|
let avatarButton: CircleAvatarButton = {
|
|
let button = CircleAvatarButton()
|
|
button.borderWidth = 2
|
|
button.borderColor = UIColor.label
|
|
return button
|
|
}()
|
|
|
|
private var currentConfiguration: ContentConfiguration!
|
|
var configuration: UIContentConfiguration {
|
|
get {
|
|
currentConfiguration
|
|
}
|
|
set {
|
|
guard let newConfiguration = newValue as? ContentConfiguration else { return }
|
|
apply(configuration: newConfiguration)
|
|
}
|
|
}
|
|
|
|
init(configuration: ContentConfiguration) {
|
|
super.init(frame: .zero)
|
|
|
|
_init()
|
|
apply(configuration: configuration)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
_init()
|
|
}
|
|
|
|
}
|
|
|
|
extension SidebarListContentView {
|
|
private func _init() {
|
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(imageView)
|
|
NSLayoutConstraint.activate([
|
|
imageView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
|
|
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 16),
|
|
imageView.widthAnchor.constraint(equalToConstant: 40).priority(.required - 1),
|
|
imageView.heightAnchor.constraint(equalToConstant: 40).priority(.required - 1),
|
|
])
|
|
|
|
avatarButton.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(avatarButton)
|
|
NSLayoutConstraint.activate([
|
|
avatarButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
|
avatarButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
|
|
avatarButton.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0).priority(.required - 2),
|
|
avatarButton.heightAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: 1.0).priority(.required - 2),
|
|
])
|
|
avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .vertical)
|
|
avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .horizontal)
|
|
|
|
imageView.contentMode = .scaleAspectFit
|
|
avatarButton.contentMode = .scaleAspectFit
|
|
|
|
imageView.isUserInteractionEnabled = false
|
|
avatarButton.isUserInteractionEnabled = false
|
|
}
|
|
|
|
private func apply(configuration: ContentConfiguration) {
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
|
|
guard currentConfiguration != configuration else { return }
|
|
currentConfiguration = configuration
|
|
|
|
guard let item = configuration.item else { return }
|
|
|
|
// configure state
|
|
let tintColor = item.isHighlighted ? ThemeService.tintColor.withAlphaComponent(0.5) : ThemeService.tintColor
|
|
imageView.tintColor = tintColor
|
|
avatarButton.tintColor = tintColor
|
|
|
|
// configure model
|
|
imageView.isHidden = item.imageURL != nil
|
|
avatarButton.isHidden = item.imageURL == nil
|
|
imageView.image = item.isActive ? item.activeImage.withRenderingMode(.alwaysTemplate) : item.image.withRenderingMode(.alwaysTemplate)
|
|
avatarButton.avatarImageView.setImage(
|
|
url: item.imageURL,
|
|
placeholder: avatarButton.avatarImageView.image ?? .placeholder(color: .systemFill), // reuse to avoid blink
|
|
scaleToSize: nil
|
|
)
|
|
avatarButton.borderWidth = item.isActive ? 2 : 0
|
|
avatarButton.setNeedsLayout()
|
|
}
|
|
}
|
|
|
|
extension SidebarListContentView {
|
|
struct Item: Hashable {
|
|
// state
|
|
var isSelected: Bool = false
|
|
var isHighlighted: Bool = false
|
|
var isActive: Bool
|
|
|
|
// model
|
|
let title: String
|
|
var image: UIImage
|
|
var activeImage: UIImage
|
|
let imageURL: URL?
|
|
|
|
|
|
static func == (lhs: SidebarListContentView.Item, rhs: SidebarListContentView.Item) -> Bool {
|
|
return lhs.isSelected == rhs.isSelected
|
|
&& lhs.isHighlighted == rhs.isHighlighted
|
|
&& lhs.isActive == rhs.isActive
|
|
&& lhs.title == rhs.title
|
|
&& lhs.image == rhs.image
|
|
&& lhs.activeImage == rhs.activeImage
|
|
&& lhs.imageURL == rhs.imageURL
|
|
}
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
hasher.combine(isSelected)
|
|
hasher.combine(isHighlighted)
|
|
hasher.combine(isActive)
|
|
hasher.combine(title)
|
|
hasher.combine(image)
|
|
hasher.combine(activeImage)
|
|
imageURL.flatMap { hasher.combine($0) }
|
|
}
|
|
}
|
|
|
|
struct ContentConfiguration: UIContentConfiguration, Hashable {
|
|
let logger = Logger(subsystem: "SidebarListContentView.ContentConfiguration", category: "ContentConfiguration")
|
|
|
|
var item: Item?
|
|
|
|
func makeContentView() -> UIView & UIContentView {
|
|
SidebarListContentView(configuration: self)
|
|
}
|
|
|
|
func updated(for state: UIConfigurationState) -> ContentConfiguration {
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
|
|
var updatedConfiguration = self
|
|
|
|
if let state = state as? UICellConfigurationState {
|
|
updatedConfiguration.item?.isSelected = state.isHighlighted || state.isSelected
|
|
updatedConfiguration.item?.isHighlighted = state.isHighlighted
|
|
} else {
|
|
assertionFailure()
|
|
updatedConfiguration.item?.isSelected = false
|
|
updatedConfiguration.item?.isHighlighted = false
|
|
}
|
|
|
|
return updatedConfiguration
|
|
}
|
|
|
|
static func == (
|
|
lhs: ContentConfiguration,
|
|
rhs: ContentConfiguration
|
|
) -> Bool {
|
|
return lhs.item == rhs.item
|
|
}
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
hasher.combine(item)
|
|
}
|
|
}
|
|
}
|