358 lines
12 KiB
Swift
358 lines
12 KiB
Swift
|
|
//
|
||
|
|
// MeRootPageView.swift
|
||
|
|
// Crush
|
||
|
|
//
|
||
|
|
// Created by Leon on 2025/7/22.
|
||
|
|
//
|
||
|
|
|
||
|
|
import UIKit
|
||
|
|
|
||
|
|
class MeRootPageView: UIView {
|
||
|
|
var headerView: MeHeaderView!
|
||
|
|
|
||
|
|
var layout: UICollectionViewFlowLayout!
|
||
|
|
var cv: UICollectionView!
|
||
|
|
|
||
|
|
// Layout
|
||
|
|
var headerHeight: CGFloat = 300
|
||
|
|
|
||
|
|
// Data
|
||
|
|
var datas:[AIRoleInfo] = [AIRoleInfo]()
|
||
|
|
|
||
|
|
var tapAvatarAction : (()-> Void)?
|
||
|
|
|
||
|
|
override init(frame: CGRect) {
|
||
|
|
super.init(frame: frame)
|
||
|
|
setupViews()
|
||
|
|
}
|
||
|
|
|
||
|
|
required init?(coder: NSCoder) {
|
||
|
|
fatalError("init(coder:) has not been implemented")
|
||
|
|
}
|
||
|
|
|
||
|
|
private func setupViews() {
|
||
|
|
cv = {
|
||
|
|
// item's height: w:h = 165:260, + 112
|
||
|
|
let lr = CGFloat.lrs
|
||
|
|
let itemW = (UIScreen.width - lr * 2 - 16) * 0.5
|
||
|
|
let itemH = itemW * 260 / 165.0 + 112
|
||
|
|
|
||
|
|
layout = UICollectionViewFlowLayout()
|
||
|
|
layout.scrollDirection = .vertical
|
||
|
|
layout.minimumLineSpacing = 0
|
||
|
|
layout.minimumInteritemSpacing = 16
|
||
|
|
layout.sectionInset = .init(top: 0, left: lr, bottom: 24 + UIWindow.safeAreaBottom, right: lr)
|
||
|
|
layout.itemSize = .init(width: itemW, height: itemH)
|
||
|
|
layout.headerReferenceSize = .init(width: UIScreen.width, height: headerHeight)
|
||
|
|
|
||
|
|
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||
|
|
view.backgroundColor = .clear //.c.cbd
|
||
|
|
view.delegate = self
|
||
|
|
view.dataSource = self
|
||
|
|
view.register(MeRootPageRollCell.self, forCellWithReuseIdentifier: "MeRootPageRollCell")
|
||
|
|
view.register(MeHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "MeHeaderView")
|
||
|
|
addSubview(view)
|
||
|
|
view.snp.makeConstraints { make in
|
||
|
|
make.leading.trailing.equalToSuperview()
|
||
|
|
make.top.equalToSuperview().offset(UIWindow.navBarTotalHeight)
|
||
|
|
make.bottom.equalToSuperview()
|
||
|
|
}
|
||
|
|
|
||
|
|
return view
|
||
|
|
}()
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
func config(datas:[AIRoleInfo]?){
|
||
|
|
guard let roles = datas else{
|
||
|
|
self.datas = []
|
||
|
|
setupEmpty(empty: true)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
self.datas = roles
|
||
|
|
setupEmpty(empty: roles.count <= 0)
|
||
|
|
cv.reloadData()
|
||
|
|
}
|
||
|
|
|
||
|
|
private func setupEmpty(empty: Bool){
|
||
|
|
if(empty){
|
||
|
|
showStartYEmpty(text: "No Character Yet", startY: 442 + UIWindow.statusBarHeight)
|
||
|
|
}else{
|
||
|
|
removeEmpty()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
extension MeRootPageView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||
|
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||
|
|
return self.datas.count
|
||
|
|
}
|
||
|
|
|
||
|
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MeRootPageRollCell", for: indexPath) as! MeRootPageRollCell
|
||
|
|
cell.cellType = .meRoleList
|
||
|
|
let data = datas[indexPath.item]
|
||
|
|
cell.config(data: data)
|
||
|
|
return cell
|
||
|
|
}
|
||
|
|
|
||
|
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||
|
|
let data = datas[indexPath.item]
|
||
|
|
AppRouter.goAIRoleHome(aiId: data.aiId)
|
||
|
|
}
|
||
|
|
|
||
|
|
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
|
||
|
|
if kind == UICollectionView.elementKindSectionHeader {
|
||
|
|
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "MeHeaderView", for: indexPath) as? MeHeaderView
|
||
|
|
headerView.heightChangeBlock = { [weak self] height in
|
||
|
|
self?.headerHeight = height
|
||
|
|
// dlog("height change :\(height)")
|
||
|
|
self?.layout.headerReferenceSize = .init(width: UIScreen.width, height: height)
|
||
|
|
self?.layout.invalidateLayout()
|
||
|
|
}
|
||
|
|
headerView.avatarView.tapAction = {[weak self] in
|
||
|
|
self?.tapAvatarAction?()
|
||
|
|
}
|
||
|
|
return headerView
|
||
|
|
}
|
||
|
|
return UICollectionReusableView()
|
||
|
|
}
|
||
|
|
|
||
|
|
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
|
||
|
|
return .init(width: UIScreen.width, height: headerHeight)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
enum RoleGridCellType {
|
||
|
|
case meRoleList
|
||
|
|
case discoverList
|
||
|
|
}
|
||
|
|
|
||
|
|
class MeRootPageRollCell: UICollectionViewCell {
|
||
|
|
var bgView: UIView!
|
||
|
|
|
||
|
|
var gradientBorderView: GradientBorderView!
|
||
|
|
var avatarView: UIImageView!
|
||
|
|
|
||
|
|
var overlayOnIv: GradientView!
|
||
|
|
var likeIconLabel: CLIconLabel!
|
||
|
|
var seeBanIv: UIImageView!
|
||
|
|
// ---
|
||
|
|
var nameLabel: UILabel!
|
||
|
|
var descLabel: UILabel!
|
||
|
|
|
||
|
|
// Mobile tags
|
||
|
|
var tagsStackH: UIStackView!
|
||
|
|
|
||
|
|
|
||
|
|
var cellType: RoleGridCellType = .meRoleList
|
||
|
|
|
||
|
|
// Datas
|
||
|
|
var datas: [AIRoleInfo]?
|
||
|
|
|
||
|
|
override init(frame: CGRect) {
|
||
|
|
super.init(frame: frame)
|
||
|
|
setupViews()
|
||
|
|
}
|
||
|
|
|
||
|
|
required init?(coder: NSCoder) {
|
||
|
|
fatalError("init(coder:) has not been implemented")
|
||
|
|
}
|
||
|
|
|
||
|
|
private func setupViews() {
|
||
|
|
bgView = {
|
||
|
|
let v = UIView()
|
||
|
|
v.layer.cornerRadius = 16
|
||
|
|
v.layer.masksToBounds = true
|
||
|
|
v.backgroundColor = .clear // .c.csbn
|
||
|
|
contentView.addSubview(v)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.leading.trailing.equalToSuperview()
|
||
|
|
make.height.equalTo(v.snp.width).multipliedBy(260.0 / 165.0)
|
||
|
|
make.top.equalToSuperview().offset(0)
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
gradientBorderView = {
|
||
|
|
let gradient = CLSystemToken.gradient(token: .ccvn)
|
||
|
|
let color1 = gradient.firstColor!
|
||
|
|
let color2 = gradient.secondColor!
|
||
|
|
|
||
|
|
let v = GradientBorderView(colors: [color1, color2], gradientType: .leftToRight)
|
||
|
|
v.gBorderWidth = 1
|
||
|
|
v.layer.cornerRadius = 16
|
||
|
|
v.layer.masksToBounds = true
|
||
|
|
v.backgroundColor = .c.csbn
|
||
|
|
bgView.addSubview(v)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.edges.equalToSuperview()
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
avatarView = {
|
||
|
|
let v = UIImageView()
|
||
|
|
v.contentMode = .scaleAspectFill
|
||
|
|
v.backgroundColor = .random.withAlphaComponent(0.3)
|
||
|
|
v.layer.cornerRadius = 16
|
||
|
|
v.layer.masksToBounds = true
|
||
|
|
v.clipsToBounds = true
|
||
|
|
bgView.addSubview(v)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1))
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
overlayOnIv = {
|
||
|
|
let colors = [UIColor.black.withAlphaComponent(0), UIColor.black]
|
||
|
|
let v = GradientView(colors: colors, gradientType: .topToBottom)
|
||
|
|
v.layer.cornerRadius = 16
|
||
|
|
v.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||
|
|
v.layer.masksToBounds = true
|
||
|
|
bgView.addSubview(v)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.leading.trailing.equalTo(avatarView)
|
||
|
|
make.bottom.equalTo(avatarView)
|
||
|
|
make.height.equalTo(44)
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
likeIconLabel = {
|
||
|
|
let v = CLIconLabel()
|
||
|
|
bgView.addSubview(v)
|
||
|
|
v.iconImageView.image = MWIconFont.image(fromIcon: .like, size: .init(width: 12, height: 12), color: .c.ctpn)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.height.equalTo(20)
|
||
|
|
make.leading.equalToSuperview().offset(16)
|
||
|
|
make.bottom.equalToSuperview().offset(-8)
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
seeBanIv = {
|
||
|
|
let v = UIImageView()
|
||
|
|
bgView.addSubview(v)
|
||
|
|
v.backgroundColor = .c.csedn
|
||
|
|
v.layer.cornerRadius = 4
|
||
|
|
v.layer.masksToBounds = true
|
||
|
|
v.contentMode = .center
|
||
|
|
v.image = MWIconFont.image(fromIcon: .eyeOff, size: CGSize(width: 12, height: 12), color: .c.ctpn)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.size.equalTo(CGSize(width: 24, height: 24))
|
||
|
|
make.top.equalToSuperview().offset(8)
|
||
|
|
make.trailing.equalToSuperview().offset(-8)
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
nameLabel = {
|
||
|
|
let v = UILabel()
|
||
|
|
v.font = .t.tts
|
||
|
|
v.textColor = .c.ctpn
|
||
|
|
v.textAlignment = .left
|
||
|
|
v.numberOfLines = 1
|
||
|
|
contentView.addSubview(v)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.top.equalTo(bgView.snp.bottom).offset(8)
|
||
|
|
make.leading.equalToSuperview()
|
||
|
|
make.trailing.equalToSuperview()
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
descLabel = {
|
||
|
|
let v = UILabel()
|
||
|
|
v.font = .t.tbs
|
||
|
|
v.textColor = .c.ctsn
|
||
|
|
v.textAlignment = .left
|
||
|
|
v.numberOfLines = 2
|
||
|
|
contentView.addSubview(v)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.top.equalTo(nameLabel.snp.bottom).offset(4)
|
||
|
|
make.leading.equalToSuperview()
|
||
|
|
make.trailing.equalToSuperview()
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
tagsStackH = {
|
||
|
|
let v = UIStackView()
|
||
|
|
v.axis = .horizontal
|
||
|
|
v.spacing = 8
|
||
|
|
v.alignment = .center
|
||
|
|
contentView.addSubview(v)
|
||
|
|
v.snp.makeConstraints { make in
|
||
|
|
make.top.equalTo(descLabel.snp.bottom).offset(8)
|
||
|
|
make.leading.equalToSuperview()
|
||
|
|
make.trailing.lessThanOrEqualToSuperview()
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}()
|
||
|
|
|
||
|
|
// do {
|
||
|
|
// let tag = RoleTag()
|
||
|
|
// tag.title = "Sensibility"
|
||
|
|
// tag.style = .purple
|
||
|
|
// tagsStackH.addArrangedSubview(tag)
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// do {
|
||
|
|
// let tag = RoleTag()
|
||
|
|
// tag.title = "Romantic"
|
||
|
|
// tag.style = .theme
|
||
|
|
// tagsStackH.addArrangedSubview(tag)
|
||
|
|
// }
|
||
|
|
|
||
|
|
// #warning("test data")
|
||
|
|
// testData()
|
||
|
|
}
|
||
|
|
|
||
|
|
private func testData() {
|
||
|
|
nameLabel.text = "测试数据"
|
||
|
|
avatarView.image = UIImage(named: "eg")
|
||
|
|
descLabel.text = "desc desc desc desc desc desc desc desc"
|
||
|
|
likeIconLabel.contentLabel.text = "11.2k"
|
||
|
|
}
|
||
|
|
|
||
|
|
public func config(data: AIRoleInfo?){
|
||
|
|
guard let role = data else {return}
|
||
|
|
// 私密
|
||
|
|
seeBanIv.isHidden = !(data?.permission == 2)
|
||
|
|
|
||
|
|
nameLabel.text = role.nickname
|
||
|
|
descLabel.text = role.introduction ?? ""
|
||
|
|
avatarView.loadImage(role.homeImageUrl)
|
||
|
|
|
||
|
|
let countDisplay = String.displayNumber(NSNumber(value: (data?.likedNum ?? 0)), scale: 1)
|
||
|
|
likeIconLabel.contentLabel.text = countDisplay
|
||
|
|
|
||
|
|
tagsStackH.removeSubviews()
|
||
|
|
if let characterName = role.characterName{
|
||
|
|
let tag = RoleTag()
|
||
|
|
tag.title = characterName
|
||
|
|
// if cellType == .meRoleList{
|
||
|
|
// tag.style = .default
|
||
|
|
// }else{
|
||
|
|
// tag.style = .blurPurple
|
||
|
|
// }
|
||
|
|
tag.style = .default
|
||
|
|
tagsStackH.addArrangedSubview(tag)
|
||
|
|
}
|
||
|
|
if let tagName = role.tagName{
|
||
|
|
let tag = RoleTag()
|
||
|
|
tag.title = tagName
|
||
|
|
// if cellType == .meRoleList{
|
||
|
|
// tag.style = .default
|
||
|
|
// }else{
|
||
|
|
// tag.style = .blurTheme
|
||
|
|
// }
|
||
|
|
tag.style = .default
|
||
|
|
tagsStackH.addArrangedSubview(tag)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|