249 lines
10 KiB
Swift
249 lines
10 KiB
Swift
//
|
|
// SearchResultTableViewCell.swift
|
|
// Mastodon
|
|
//
|
|
// Created by sxiaojian on 2021/4/2.
|
|
//
|
|
|
|
import CoreData
|
|
import CoreDataStack
|
|
import Foundation
|
|
import MastodonSDK
|
|
import UIKit
|
|
import FLAnimatedImage
|
|
import MetaTextKit
|
|
import MastodonMeta
|
|
|
|
final class SearchResultTableViewCell: UITableViewCell {
|
|
|
|
let _imageView: AvatarImageView = {
|
|
let imageView = AvatarImageView()
|
|
imageView.tintColor = Asset.Colors.Label.primary.color
|
|
imageView.layer.cornerRadius = 4
|
|
imageView.clipsToBounds = true
|
|
return imageView
|
|
}()
|
|
|
|
let _titleLabel = MetaLabel(style: .statusName)
|
|
|
|
let _subTitleLabel: UILabel = {
|
|
let label = UILabel()
|
|
label.textColor = Asset.Colors.Label.secondary.color
|
|
label.font = .preferredFont(forTextStyle: .body)
|
|
return label
|
|
}()
|
|
|
|
let separatorLine = UIView.separatorLine
|
|
|
|
var separatorLineToEdgeLeadingLayoutConstraint: NSLayoutConstraint!
|
|
var separatorLineToEdgeTrailingLayoutConstraint: NSLayoutConstraint!
|
|
|
|
var separatorLineToMarginLeadingLayoutConstraint: NSLayoutConstraint!
|
|
var separatorLineToMarginTrailingLayoutConstraint: NSLayoutConstraint!
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
_imageView.af.cancelImageRequest()
|
|
}
|
|
|
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
configure()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
configure()
|
|
}
|
|
}
|
|
|
|
extension SearchResultTableViewCell {
|
|
private func configure() {
|
|
let containerStackView = UIStackView()
|
|
containerStackView.axis = .horizontal
|
|
containerStackView.distribution = .fill
|
|
containerStackView.spacing = 12
|
|
containerStackView.layoutMargins = UIEdgeInsets(top: 12, left: 0, bottom: 12, right: 0)
|
|
containerStackView.isLayoutMarginsRelativeArrangement = true
|
|
containerStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.addSubview(containerStackView)
|
|
NSLayoutConstraint.activate([
|
|
containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
|
containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
|
containerStackView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
|
containerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
|
|
])
|
|
|
|
_imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
containerStackView.addArrangedSubview(_imageView)
|
|
NSLayoutConstraint.activate([
|
|
_imageView.widthAnchor.constraint(equalToConstant: 42).priority(.required - 1),
|
|
_imageView.heightAnchor.constraint(equalToConstant: 42).priority(.required - 1),
|
|
])
|
|
|
|
let textStackView = UIStackView()
|
|
textStackView.axis = .vertical
|
|
textStackView.distribution = .fill
|
|
textStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
_titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
textStackView.addArrangedSubview(_titleLabel)
|
|
_subTitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
textStackView.addArrangedSubview(_subTitleLabel)
|
|
_subTitleLabel.setContentHuggingPriority(.defaultLow - 1, for: .vertical)
|
|
|
|
containerStackView.addArrangedSubview(textStackView)
|
|
|
|
separatorLine.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.addSubview(separatorLine)
|
|
separatorLineToEdgeLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
|
|
separatorLineToEdgeTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
|
|
separatorLineToMarginLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor)
|
|
separatorLineToMarginTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor)
|
|
NSLayoutConstraint.activate([
|
|
separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
|
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
|
|
])
|
|
resetSeparatorLineLayout()
|
|
}
|
|
|
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
super.traitCollectionDidChange(previousTraitCollection)
|
|
|
|
resetSeparatorLineLayout()
|
|
}
|
|
|
|
}
|
|
|
|
extension SearchResultTableViewCell {
|
|
|
|
private func resetSeparatorLineLayout() {
|
|
separatorLineToEdgeLeadingLayoutConstraint.isActive = false
|
|
separatorLineToEdgeTrailingLayoutConstraint.isActive = false
|
|
separatorLineToMarginLeadingLayoutConstraint.isActive = false
|
|
separatorLineToMarginTrailingLayoutConstraint.isActive = false
|
|
|
|
if traitCollection.userInterfaceIdiom == .phone {
|
|
// to edge
|
|
NSLayoutConstraint.activate([
|
|
separatorLineToEdgeLeadingLayoutConstraint,
|
|
separatorLineToEdgeTrailingLayoutConstraint,
|
|
])
|
|
} else {
|
|
if traitCollection.horizontalSizeClass == .compact {
|
|
// to edge
|
|
NSLayoutConstraint.activate([
|
|
separatorLineToEdgeLeadingLayoutConstraint,
|
|
separatorLineToEdgeTrailingLayoutConstraint,
|
|
])
|
|
} else {
|
|
// to margin
|
|
NSLayoutConstraint.activate([
|
|
separatorLineToMarginLeadingLayoutConstraint,
|
|
separatorLineToMarginTrailingLayoutConstraint,
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension SearchResultTableViewCell {
|
|
|
|
func config(with account: Mastodon.Entity.Account) {
|
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL()))
|
|
let name = account.displayName.isEmpty ? account.username : account.displayName
|
|
do {
|
|
let mastodonContent = MastodonContent(content: name, emojis: account.emojiMeta)
|
|
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
|
_titleLabel.configure(content: metaContent)
|
|
} catch {
|
|
let metaContent = PlaintextMetaContent(string: name)
|
|
_titleLabel.configure(content: metaContent)
|
|
}
|
|
_subTitleLabel.text = account.acct
|
|
}
|
|
|
|
func config(with account: MastodonUser) {
|
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL()))
|
|
do {
|
|
let mastodonContent = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojiMeta)
|
|
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
|
_titleLabel.configure(content: metaContent)
|
|
} catch {
|
|
let metaContent = PlaintextMetaContent(string: account.displayNameWithFallback)
|
|
_titleLabel.configure(content: metaContent)
|
|
}
|
|
_subTitleLabel.text = account.acct
|
|
}
|
|
|
|
func config(with tag: Mastodon.Entity.Tag) {
|
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: nil))
|
|
let image = UIImage(systemName: "number.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular))!.withRenderingMode(.alwaysTemplate)
|
|
_imageView.image = image
|
|
let metaContent = PlaintextMetaContent(string: "#" + tag.name)
|
|
_titleLabel.configure(content: metaContent)
|
|
guard let histories = tag.history else {
|
|
_subTitleLabel.text = ""
|
|
return
|
|
}
|
|
let recentHistory = histories.prefix(2)
|
|
let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +)
|
|
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
|
|
_subTitleLabel.text = string
|
|
}
|
|
|
|
func config(with tag: Tag) {
|
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: nil))
|
|
let image = UIImage(systemName: "number.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular))!.withRenderingMode(.alwaysTemplate)
|
|
_imageView.image = image
|
|
let metaContent = PlaintextMetaContent(string: "#" + tag.name)
|
|
_titleLabel.configure(content: metaContent)
|
|
guard let histories = tag.histories?.sorted(by: {
|
|
$0.createAt.compare($1.createAt) == .orderedAscending
|
|
}) else {
|
|
_subTitleLabel.text = ""
|
|
return
|
|
}
|
|
let recentHistory = histories.prefix(2)
|
|
let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +)
|
|
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
|
|
_subTitleLabel.text = string
|
|
}
|
|
}
|
|
|
|
// MARK: - AvatarStackedImageView
|
|
extension SearchResultTableViewCell: AvatarConfigurableView {
|
|
static var configurableAvatarImageSize: CGSize { CGSize(width: 42, height: 42) }
|
|
static var configurableAvatarImageCornerRadius: CGFloat { 4 }
|
|
var configurableAvatarImageView: FLAnimatedImageView? { _imageView }
|
|
}
|
|
|
|
#if canImport(SwiftUI) && DEBUG
|
|
import SwiftUI
|
|
|
|
struct SearchResultTableViewCell_Previews: PreviewProvider {
|
|
static var controls: some View {
|
|
Group {
|
|
UIViewPreview {
|
|
let cell = SearchResultTableViewCell()
|
|
cell.backgroundColor = .white
|
|
cell._imageView.image = UIImage(systemName: "number.circle.fill")
|
|
cell._titleLabel.text = "Electronic Frontier Foundation"
|
|
cell._subTitleLabel.text = "@eff@mastodon.social"
|
|
return cell
|
|
}
|
|
.previewLayout(.fixed(width: 228, height: 130))
|
|
}
|
|
}
|
|
|
|
static var previews: some View {
|
|
Group {
|
|
controls.colorScheme(.light)
|
|
controls.colorScheme(.dark)
|
|
}
|
|
.background(Color.gray)
|
|
}
|
|
}
|
|
|
|
#endif
|