452 lines
15 KiB
Swift
452 lines
15 KiB
Swift
//
|
||
// EncounterAICardHeader.swift
|
||
// Crush
|
||
//
|
||
// Created by Leon on 2025/9/11.
|
||
//
|
||
|
||
class EncounterAICardHeader:UIView{
|
||
var imageFieldContainer: UIView!
|
||
var imageBgButton: UIButton!
|
||
var imageView: UIImageView!
|
||
var topRightTag: EPTagLabel!
|
||
|
||
var overlay: GradientView!
|
||
var overlayBgButton: UIButton!
|
||
var bottomButtonsStackH : UIStackView!
|
||
var dislikeButton: UIButton!
|
||
var giftButton : EPIconPrimaryButton!
|
||
var likeButton : UIButton!
|
||
|
||
var likeAndCount: CLIconLabel!
|
||
var nameLabel: CLLabel!
|
||
var tagsStackH: UIStackView!
|
||
var introductionStackH: UIStackView!
|
||
var introductionLabel: LineSpaceLabel!
|
||
var introductionLabelTopButton: UIButton!
|
||
var expendButton: EPIconGhostButton!
|
||
var aiGeneratedLabel: CLLabel!
|
||
|
||
var albumTitleView: UIView!
|
||
var albumTitleLabel : CLLabel!
|
||
|
||
var data: MeetCard?
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
setupViews()
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
private func setupViews() {
|
||
imageFieldContainer = {
|
||
let v = UIView()
|
||
v.backgroundColor = .clear
|
||
v.clipsToBounds = true
|
||
addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.top.leading.trailing.equalToSuperview()
|
||
make.height.equalTo(EncounterAICardView.cardHeight)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
imageBgButton = {
|
||
let v = UIButton()
|
||
imageFieldContainer.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
return v
|
||
}()
|
||
|
||
imageView = {
|
||
let v = UIImageView()
|
||
v.contentMode = .scaleAspectFill
|
||
imageFieldContainer.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
return v
|
||
}()
|
||
|
||
topRightTag = {
|
||
let v = EPTagLabel(style: .primary, size: .large)
|
||
imageFieldContainer.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.top.equalToSuperview().offset(24)
|
||
make.trailing.equalToSuperview().offset(-16)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
overlay = {
|
||
let color = UIColor.c.cbd
|
||
let v = GradientView(colors: [color.withAlphaComponent(0), color.withAlphaComponent(0.6), color.withAlphaComponent(0.9)], gradientType: .topToBottom)
|
||
imageFieldContainer.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.leading.trailing.equalToSuperview()
|
||
make.bottom.equalToSuperview()
|
||
make.height.equalTo(368)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
bottomButtonsStackH = {
|
||
let v = UIStackView()
|
||
v.spacing = 32
|
||
v.alignment = .center
|
||
imageFieldContainer.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.bottom.equalToSuperview().offset(-40)
|
||
make.centerX.equalToSuperview()
|
||
}
|
||
return v
|
||
}()
|
||
|
||
dislikeButton = {
|
||
let v = UIButton()
|
||
let image = UIImage(named: "encounter_icon_dislike")
|
||
v.setImage(image, for: .normal)
|
||
v.backgroundColor = .c.cseln
|
||
v.cornerRadius = 32
|
||
v.contentMode = .center
|
||
bottomButtonsStackH.addArrangedSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.size.equalTo(CGSize(width: 64, height: 64))
|
||
}
|
||
return v
|
||
}()
|
||
|
||
giftButton = {
|
||
let v = EPIconPrimaryButton(radius: .round, iconSize: .large, iconCode: .giftBorder)
|
||
bottomButtonsStackH.addArrangedSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.size.equalTo(CGSize(width: 48, height: 48))
|
||
}
|
||
return v
|
||
}()
|
||
|
||
likeButton = {
|
||
let v = UIButton()
|
||
let image = UIImage(named: "encounter_icon_like")
|
||
v.setImage(image, for: .normal)
|
||
v.backgroundColor = .c.cseln
|
||
v.cornerRadius = 32
|
||
v.contentMode = .center
|
||
bottomButtonsStackH.addArrangedSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.size.equalTo(CGSize(width: 64, height: 64))
|
||
}
|
||
return v
|
||
}()
|
||
|
||
setupMiddlePart()
|
||
setupAlbumTitlePart()
|
||
|
||
overlayBgButton = {
|
||
let v = UIButton()
|
||
overlay.insertSubview(v, at: 0)
|
||
v.snp.makeConstraints { make in
|
||
make.top.leading.trailing.equalToSuperview()
|
||
make.bottom.equalTo(introductionStackH.snp.top).offset(-8)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
// #warning("test")
|
||
// testData()
|
||
}
|
||
|
||
private func setupMiddlePart(){
|
||
nameLabel = {
|
||
let v = CLLabel()
|
||
v.numberOfLines = 1 // 暂时限制1行
|
||
v.font = .t.thl
|
||
overlay.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().offset(24)
|
||
make.trailing.equalToSuperview().offset(-96)
|
||
make.bottom.equalTo(overlay.snp.top).offset(112)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
likeAndCount = {
|
||
let v = CLIconLabel()
|
||
v.iconSize = CGSize(width: 20, height: 20)
|
||
v.contentLabel.font = .t.tnds
|
||
v.iconImageView.image = MWIconFont.image(fromIcon: .like, size: CGSize(width: 20, height: 20), color: .white)
|
||
overlay.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.trailing.equalToSuperview().offset(-24)
|
||
make.centerY.equalTo(nameLabel)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
tagsStackH = {
|
||
let v = UIStackView()
|
||
v.spacing = 8
|
||
overlay.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().offset(24)
|
||
make.top.equalTo(nameLabel.snp.bottom).offset(8)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
introductionStackH = {
|
||
let v = UIStackView()
|
||
v.spacing = 0
|
||
v.alignment = .bottom
|
||
overlay.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().offset(24)
|
||
make.trailing.equalToSuperview().offset(-24)
|
||
make.top.equalTo(tagsStackH.snp.bottom).offset(8)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
introductionLabel = {
|
||
let v = LineSpaceLabel()
|
||
let typography = CLSystemToken.typography(token: .tbm)
|
||
v.textColor = .text
|
||
v.config(typography)
|
||
v.numberOfLines = 3
|
||
introductionStackH.addArrangedSubview(v)
|
||
return v
|
||
}()
|
||
|
||
introductionLabelTopButton = {
|
||
let v = UIButton()
|
||
v.addTarget(self, action: #selector(tapIntroductionTop), for: .touchUpInside)
|
||
overlay.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.edges.equalTo(introductionLabel)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
expendButton = {
|
||
let v = EPIconGhostButton(radius: .none, iconSize: .xs, iconCode: .iconFullimage)
|
||
v.addTarget(self, action: #selector(tapExpandButton), for: .touchUpInside)
|
||
introductionStackH.addArrangedSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.size.equalTo(v.bgImageSize())
|
||
}
|
||
v.isHidden = true // 默认隐藏
|
||
return v
|
||
}()
|
||
|
||
aiGeneratedLabel = {
|
||
let v = CLLabel()
|
||
v.textColor = .c.ctsn
|
||
v.font = .t.tbs
|
||
overlay.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().offset(24)
|
||
make.trailing.equalToSuperview().offset(-24)
|
||
make.top.equalTo(introductionStackH.snp.bottom).offset(8)
|
||
}
|
||
return v
|
||
}()
|
||
}
|
||
|
||
private func setupAlbumTitlePart(){
|
||
albumTitleView = {
|
||
let v = UIView()
|
||
addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().offset(CGFloat.lrs)
|
||
make.trailing.equalToSuperview().offset(-CGFloat.lrs)
|
||
make.top.equalTo(imageFieldContainer.snp.bottom).offset(16)
|
||
make.bottom.equalToSuperview().offset(-24)
|
||
make.height.equalTo(32)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
albumTitleLabel = {
|
||
let v = CLLabel()
|
||
v.font = .t.ttm
|
||
albumTitleView.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.center.equalToSuperview()
|
||
}
|
||
return v
|
||
}()
|
||
|
||
do{
|
||
let left = {
|
||
let v = CLLine()
|
||
albumTitleView.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.height.equalTo(1)
|
||
make.leading.equalToSuperview()
|
||
make.trailing.equalTo(albumTitleLabel.snp.leading).offset(-16)
|
||
make.centerY.equalTo(albumTitleLabel)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
let right = {
|
||
let v = CLLine()
|
||
albumTitleView.addSubview(v)
|
||
v.snp.makeConstraints { make in
|
||
make.height.equalTo(1)
|
||
make.trailing.equalToSuperview()
|
||
make.leading.equalTo(albumTitleLabel.snp.trailing).offset(16)
|
||
make.centerY.equalTo(albumTitleLabel)
|
||
}
|
||
return v
|
||
}()
|
||
|
||
left.isHidden = false
|
||
right.isHidden = false
|
||
}
|
||
|
||
albumTitleLabel.text = "Album"
|
||
topRightTag.text = "Secret Admirer"
|
||
topRightTag.isHidden = true
|
||
}
|
||
|
||
private func testData(){
|
||
|
||
nameLabel.text = "Alice, 24"
|
||
likeAndCount.contentLabel.text = "1.2k"
|
||
|
||
do {
|
||
let tag = RoleTag()
|
||
tag.title = "Sensibility"
|
||
tag.style = .blurPurple
|
||
tagsStackH.addArrangedSubview(tag)
|
||
}
|
||
|
||
do {
|
||
let tag = RoleTag()
|
||
tag.title = "Romantic"
|
||
tag.style = .blurTheme
|
||
tagsStackH.addArrangedSubview(tag)
|
||
}
|
||
|
||
introductionLabel.text = "She is a new trainee teacher who has just graduated. You are the most rebellious student in the whole school. In order to prove her ability, she took the initiative to apply to be your class teacher. In the upcoming college entrance examination, you two…"
|
||
aiGeneratedLabel.text = "Content generated by AI"
|
||
|
||
imageView.image = UIImage.egpic
|
||
}
|
||
|
||
// MARK: - Public Methods
|
||
|
||
func config(_ card: MeetCard) {
|
||
data = card
|
||
|
||
// 配置头像
|
||
if let imageUrl = card.imageUrl {
|
||
imageView.loadImage(imageUrl)
|
||
}
|
||
|
||
// 配置右上角标签
|
||
if data?.secretAdmirer ?? false{
|
||
topRightTag.isHidden = false
|
||
}else {
|
||
topRightTag.isHidden = true
|
||
}
|
||
|
||
// 配置姓名和年龄
|
||
let name = card.nickname ?? "Unknown"
|
||
let age = card.age != nil ? ", \(card.age!)" : ""
|
||
nameLabel.text = "\(name)\(age)"
|
||
|
||
// 配置点赞数
|
||
let likeCount = card.likedCount ?? 0
|
||
likeAndCount.contentLabel.text = formatLikeCount(likeCount)
|
||
|
||
// 清除现有标签
|
||
tagsStackH.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||
|
||
// 配置性格标签
|
||
if let character = card.character, !character.isEmpty {
|
||
let tag = RoleTag()
|
||
tag.title = character
|
||
//tag.style = .blurTheme
|
||
tag.style = .default
|
||
tagsStackH.addArrangedSubview(tag)
|
||
}
|
||
|
||
// 配置标签
|
||
if let tag = card.tag, !tag.isEmpty {
|
||
let tagView = RoleTag()
|
||
tagView.title = tag
|
||
//tagView.style = .blurPurple
|
||
tagView.style = .default
|
||
tagsStackH.addArrangedSubview(tagView)
|
||
}
|
||
|
||
// 配置简介
|
||
introductionLabel.text = card.introduction ?? ""
|
||
updateExpandButtonVisibility() // 添加这行来更新按钮显示状态
|
||
aiGeneratedLabel.text = "Content generated by AI"
|
||
}
|
||
|
||
private func formatLikeCount(_ count: Int) -> String {
|
||
if count >= 1000 {
|
||
return String(format: "%.1fk", Double(count) / 1000.0)
|
||
} else {
|
||
return "\(count)"
|
||
}
|
||
}
|
||
|
||
private func updateExpandButtonVisibility() {
|
||
guard let text = introductionLabel.text, !text.isEmpty else {
|
||
expendButton.isHidden = true
|
||
return
|
||
}
|
||
|
||
// 计算文本在3行限制下的实际高度
|
||
let maxWidth = introductionLabel.frame.width > 0 ? introductionLabel.frame.width : (UIScreen.main.bounds.width - 48) // 减去左右边距
|
||
let maxSize = CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)
|
||
|
||
// 计算3行文本的高度
|
||
let threeLineHeight = introductionLabel.font.lineHeight * 3 + introductionLabel.font.lineHeight * 2 // 3行 + 2个行间距
|
||
|
||
// 计算实际文本需要的高度
|
||
let actualSize = text.boundingRect(
|
||
with: maxSize,
|
||
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
||
attributes: [.font: introductionLabel.font!],
|
||
context: nil
|
||
)
|
||
|
||
// 如果实际高度超过3行高度,显示展开按钮
|
||
let hideBrowseIntroduction = actualSize.height <= threeLineHeight
|
||
expendButton.isHidden = hideBrowseIntroduction
|
||
introductionLabelTopButton.isHidden = hideBrowseIntroduction
|
||
}
|
||
|
||
// 重写layoutSubviews来在布局完成后更新按钮状态
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
updateExpandButtonVisibility()
|
||
}
|
||
|
||
// MARK: - Action
|
||
|
||
@objc private func tapExpandButton(){
|
||
guard let introduction = data?.introduction else{return}
|
||
|
||
let vc = RoleIntroBrowseDialogController()
|
||
vc.introduction = introduction
|
||
vc.imageUrl = data?.imageUrl
|
||
vc.show()
|
||
}
|
||
|
||
@objc private func tapIntroductionTop(){
|
||
tapExpandButton()
|
||
}
|
||
}
|