523 lines
17 KiB
Swift
523 lines
17 KiB
Swift
//
|
||
// 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<AnyCancellable>()
|
||
|
||
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<AlbumPhotoItem>.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<EmptyModel?, ResponseError>, 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)
|
||
}
|
||
}
|
||
}
|
||
}
|