// // RoleHomeAlbumController.swift // Crush // // Created by Leon on 2025/7/24. // import JXPagingView import SnapKit import UIKit import Combine class RoleHomeAlbumController: CLBaseGridController { var aiId: Int? var userId: Int? private var cancellables = Set() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. setupViews() setupDats() setupEvents() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layout.invalidateLayout() } private func setupViews() { collectionView.snp.remakeConstraints { make in make.edges.equalToSuperview() } navigationView.isHidden = true collectionView.register(RoleHomeAlbumGridCell.self, forCellWithReuseIdentifier: "RoleHomeAlbumGridCell") let itemW = floor((UIScreen.width - 24 * 2 - 16) * 0.5) as CGFloat let itemH = itemW // layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: itemW, height: itemH) layout.minimumLineSpacing = 16 layout.minimumInteritemSpacing = 16 layout.sectionInset = UIEdgeInsets(top: 12, left: 24, bottom: 12 + UIWindow.safeAreaBottom, right: 24) layout.invalidateLayout() addRefreshHeader() addRefreshFooter() collectionView.mj_header?.beginRefreshing() } private func setupDats() { } override func loadData() { guard let id = aiId else {return } let pageData = RequestPageData() pageData.pn = page let pageParams = pageData.toNonNilDictionary() var params = [String: Any]() params.updateValue(id, forKey: "aiId") params.updateValue(pageParams, forKey: "page") AIRoleProvider.request(.aiRoleAlbumList(params: params), modelType: ResponseContentPageData.self) {[weak self] result in self?.collectionView.mj_header?.endRefreshing() self?.collectionView.mj_footer?.endRefreshing() switch result { case .success(let success): if let albums = success?.datas{ if(pageData.pn == 1){ self?.datas = albums self?.collectionView.mj_footer?.resetNoMoreData() self?.view.setupEmpty(empty: albums.count <= 0, msg: "暂无图片") }else{ self?.datas.append(contentsOf: albums) if albums.count <= 0{ self?.collectionView.mj_footer?.endRefreshingWithNoMoreData() } } self?.collectionView.reloadData() } case .failure: break } } } private func setupEvents() { NotificationCenter.default.addObserver(self, selector: #selector(notiAlbumsInfoChanged), name: AppNotificationName.aiRoleAlbumPhotoInfoChanged.notificationName, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(notiAlbumsAddOrDeleted), name: AppNotificationName.aiRoleAlbumAddOrDelete.notificationName, object: nil) PhotosViewModel.shared.$album.sink {[weak self] photo in guard let self = self else{return} guard let album = photo , let albumId = photo?.albumId else {return} for per in self.datas{ guard let perAlbum = per as? AlbumPhotoItem else {return} if albumId == perAlbum.albumId{ perAlbum.updateFrom(album) self.collectionView.reloadData() break } } }.store(in: &cancellables) } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return datas.count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RoleHomeAlbumGridCell", for: indexPath) as! RoleHomeAlbumGridCell let data = datas[indexPath.item] cell.isSelf = UserCore.shared.isSelfByUid(uid: userId) cell.config(data: data as? AlbumPhotoItem) return cell } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let album = datas[indexPath.item] as? AlbumPhotoItem else {return} // guard let uid = userId else{return} // if album.lockStatus == .locked && !UserCore.shared.isSelf(id: userId){ // return // } var photoModels = [PhotoBrowserModel]() for (index, per) in datas.enumerated() { guard let perAlbum = per as? AlbumPhotoItem else{ return } let model = PhotoBrowserModel() model.aiAlbum = perAlbum model.aiId = aiId model.imageUrl = perAlbum.getImgUrl() if index == indexPath.item{ if let cell = collectionView.cellForItem(at: indexPath) as? RoleHomeAlbumGridCell{ model.image = cell.imageView.image model.sourceRect = cell.screenRect ?? .zero } } model.deleteTapBlock = { [weak self] model, completeBlock in // req api to delete photo // #warning("test") // Hud.showIndicator() // DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // Hud.hideInidcator() // completeBlock(true) // } Hud.showIndicator() AIRoleProvider.request(.aiRoleAlbumDel(userId: self?.userId, albumId: model.aiAlbum.albumId), modelType: EmptyModel.self) { result in Hud.hideIndicator() switch result { case .success: completeBlock(true) NotificationCenter.post(name: .aiRoleAlbumAddOrDelete) case .failure: completeBlock(false) } } } photoModels.append(model) } if UserCore.shared.isSelf(id: userId){ ImageBrowser.show(models: photoModels, index: indexPath.item, type: .roleMine) }else{ ImageBrowser.show(models: photoModels, index: indexPath.item, type: .roleOthersInAlbum) } } @objc private func notiAlbumsInfoChanged(){ collectionView.mj_header?.beginRefreshing() } @objc private func notiAlbumsAddOrDeleted(){ collectionView.mj_header?.beginRefreshing() } } extension RoleHomeAlbumController: JXPagingViewListViewDelegate { func listView() -> UIView { return view } func listScrollView() -> UIScrollView { return collectionView } func listViewDidScrollCallback(callback: @escaping (UIScrollView) -> Void) { listViewDidScrollCallback = callback } } class RoleHomeAlbumGridCell: UICollectionViewCell { var imageView: AutoRatioImageView! /// 🚩正常图片 var normalContainer: UIView! /// 左上、默认flag var flagOfDefault: EPTagLabel! /// 右上角,黑色标识是否带锁(对于自己)。 var lockFlag: EPIconFlagView! var unlockedFlag: EPIconPrimaryButton! // 🔓 + 主题背景色 /// 左下 var likeView: HeartLikeCountView! /// 🚩LockContainer var lockContainer: UIView! var unlockCoinLabel: UILabel! // Flag public var usedInMeetCard : Bool = false // @Required public var data: AlbumPhotoItem? // @Required public var isSelf: Bool = false // 添加请求状态标记 private var isRequesting = false override init(frame: CGRect) { super.init(frame: frame) setupViews() setupData() setupEvent() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // func config(data:AlbumPhotoItem?){ self.data = data; guard let photo = data else {return} //imageView.loadImage(data?.getImgUrl()) imageView.setImage(with: data?.getImgUrl().urlValue) likeView.isLike = photo.likedStatus == .liked let likedCount = photo.likedCount ?? 0 let likeCountDisplay = String.displayNumber(NSNumber(value: likedCount), scale: 1) likeView.countLabel.text = likeCountDisplay// String.displayInteger(photo.likedCount ?? 0) if isSelf{ lockContainer.isHidden = true normalContainer.isHidden = false unlockedFlag.isHidden = true lockFlag.isHidden = true if let lockStatus = photo.lockStatus{ lockFlag.isHidden = lockStatus != .unlock } flagOfDefault.isHidden = !photo.isDefault.boolValue }else{ flagOfDefault.isHidden = true lockFlag.isHidden = true unlockedFlag.isHidden = true if let lockStatus = photo.lockStatus{ if lockStatus == .unlock{ // 解锁了的 lockContainer.isHidden = true normalContainer.isHidden = false unlockedFlag.isHidden = false flagOfDefault.isHidden = !photo.isDefault.boolValue }else if lockStatus == .locked{ // 他人带锁 lockContainer.isHidden = false normalContainer.isHidden = true let coin = Coin(cents: (data?.unlockPrice ?? 0)) unlockCoinLabel.text = "\(coin.formatted) unlock" } }else{ // 未上锁Open lockContainer.isHidden = true normalContainer.isHidden = false flagOfDefault.isHidden = !photo.isDefault.boolValue } if usedInMeetCard{ flagOfDefault.isHidden = true } } } private func setupViews() { contentView.backgroundColor = .c.csnn contentView.layer.cornerRadius = 16 contentView.layer.masksToBounds = true imageView = { let v = AutoRatioImageView() v.layer.cornerRadius = 16 v.layer.masksToBounds = true contentView.addSubview(v) v.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } return v }() normalContainer = { let v = UIView() contentView.addSubview(v) v.snp.makeConstraints { make in make.edges.equalToSuperview() } return v }() flagOfDefault = { let v = EPTagLabel(style: .blackOnColor) normalContainer.addSubview(v) v.snp.makeConstraints { make in make.leading.equalToSuperview().offset(8) make.top.equalToSuperview().offset(8) } return v }() lockFlag = { let v = EPIconFlagView(frame: .zero) v.setupRightTopLockStyle() normalContainer.addSubview(v) v.snp.makeConstraints { make in make.top.equalToSuperview().offset(8) make.trailing.equalToSuperview().offset(-8) } v.isHidden = true return v }() unlockedFlag = { let v = EPIconPrimaryButton(radius: .rectangle, iconSize: .xs, iconCode: .iconPublic) v.layer.cornerRadius = 4 normalContainer.addSubview(v) v.snp.makeConstraints { make in make.top.equalToSuperview().offset(8) make.trailing.equalToSuperview().offset(-8) } v.isHidden = true return v }() likeView = { let v = HeartLikeCountView() normalContainer.addSubview(v) v.snp.makeConstraints { make in make.leading.equalToSuperview().offset(8) make.bottom.equalToSuperview().offset(-8) } return v }() setupLockStateViews() flagOfDefault.text = "Default" likeView.countLabel.text = "0" // #warning("test") // testData() } private func setupLockStateViews() { lockContainer = { let v = UIView() contentView.addSubview(v) v.snp.makeConstraints { make in make.edges.equalToSuperview() } return v }() let stackV = { let v = UIStackView() v.axis = .vertical v.alignment = .center v.spacing = 16 lockContainer.addSubview(v) v.snp.makeConstraints { make in make.center.equalToSuperview() make.left.greaterThanOrEqualToSuperview() make.right.lessThanOrEqualToSuperview() } return v }() let lockIcon = { let v = UIImageView() v.image = MWIconFont.image(fromIcon: .iconPrivate, size: CGSize(width: 24, height: 24), color: .white) stackV.addArrangedSubview(v) return v }() lockIcon.isHidden = false let stackH = { let v = UIStackView() v.spacing = 4 v.alignment = .center stackV.addArrangedSubview(v) return v }() let coinIv = { let v = UIImageView() v.image = UIImage(named: "icon_16_diamond") stackH.addArrangedSubview(v) return v }() coinIv.isHidden = false unlockCoinLabel = { let v = UILabel() v.textColor = .text v.font = .t.tlm stackH.addArrangedSubview(v) return v }() } private func setupData(){ } private func setupEvent(){ likeView.likeButton.addTarget(self, action: #selector(tapLikeHeart), for: .touchUpInside) } private func testData() { imageView.image = UIImage(named: "egpic") unlockCoinLabel.text = "123 unlock" lockContainer.backgroundColor = .random normalContainer.isHidden = true lockContainer.isHidden = false } @objc private func tapLikeHeart() { guard UserCore.shared.checkUserLoginIfNotPushUserToLogin() else{return} // 防止重复点击 guard !isRequesting else { return } guard let album = self.data else { return } let albumId = album.albumId isRequesting = true // 禁用按钮 likeView.likeButton.isEnabled = false if album.likedStatus == .liked { // 取消点赞 album.likedStatus = .cancel album.likedCount = max((album.likedCount ?? 0) - 1, 0) config(data: album) AIRoleProvider.request(.aiRolePhotoLikeOrNo(albumId: albumId, likedStatus: .cancel), modelType: EmptyModel.self) { [weak self] result in self?.handleLikeRequestResult(result: result, albumId: albumId, isLike: false) } } else { // 点赞 likeView.playLotteLike { [weak self] completed in guard let album = self?.data else { return } album.likedStatus = .liked album.likedCount = (album.likedCount ?? 0) + 1 self?.config(data: album) } AIRoleProvider.request(.aiRolePhotoLikeOrNo(albumId: albumId, likedStatus: .liked), modelType: EmptyModel.self) { [weak self] result in self?.handleLikeRequestResult(result: result, albumId: albumId, isLike: true) } } } // 统一处理请求结果 private func handleLikeRequestResult(result: Result, albumId: Int, isLike: Bool) { isRequesting = false likeView.likeButton.isEnabled = true switch result { case .success: // 请求成功,无需额外处理 break case .failure: // 请求失败,恢复状态 if let currentId = self.data?.albumId, currentId == albumId { guard let album = self.data else { return } if isLike { // 点赞失败,恢复为未点赞状态 album.likedStatus = .cancel album.likedCount = max((album.likedCount ?? 0) - 1, 0) } else { // 取消点赞失败,恢复为点赞状态 album.likedStatus = .liked album.likedCount = (album.likedCount ?? 0) + 1 } config(data: album) } } } }