// // RoleHomePagerController.swift // Crush // // Created by Leon on 2025/7/24. // import Combine import UIKit class RoleHomePagerController: CLViewController { var navRightButton: EPIconGhostButton! var likeView : HeartLikeCountView! var aiId: Int = 0 @Published var info: AIRoleInfo? /// 待修改的头像 var avatarModel: UploadPhotoM? // 添加请求状态标记 private var isRequesting = false private var cancellables = Set() override func viewDidLoad() { super.viewDidLoad() setupViews() setupDats() setupEvents() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) disabledFullScreenPan() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) enabledFullScreenPan() } private func setupViews() { navigationView.alpha0Title = "User Name" navigationView.bgView.alpha = 0 } private func setupDats() { loadAIInfo(block: nil) container.aboutVc.aiId = aiId container.albumVc.aiId = aiId } private func loadAIInfo(block: (() -> Void)?) { Hud.showIndicator() AIRoleProvider.request(.queryAIBaseInfo(aiId: aiId), modelType: AIRoleInfo.self) { [weak self] result in Hud.hideIndicator() switch result { case let .success(success): self?.info = success case let .failure(failure): switch failure { case let .serviceError(code, _): if code == .aiRoleNotExist{ let alert = Alert(title: "The Role has been deleted", text: "The role has been deleted and cannot be accessed and interacted.") let action1 = AlertAction(title: "Got it", actionStyle: .confirm) {[weak self] in self?.close() } alert.addAction(action1) alert.show() } default: break } } block?() } } private func setupEvents() { NotificationCenter.default.addObserver(self, selector: #selector(notificationAIRoleInfoUpdated(noti:)), name: AppNotificationName.aiRoleInfoChanged.notificationName, object: nil) container.createButton.addTarget(self, action: #selector(tapCreateButton), for: .touchUpInside) container.headerView.avatarIv.tapAction = {[weak self] in self?.tapEditAvatar() } container.headerView.tapChatAction = { aiId in AppRouter.goChatVC(aiId: aiId) } container.headerView.tapEditAction = {[weak self] in self?.tapEditAI() } $info.sink { [weak self] result in self?.navigationView.alpha0Title = result?.nickname ?? "" self?.container.config(info: result) self?.container.headerView.config(info: result) self?.container.aboutVc.config(info: result) if let user = result{ let uid = user.userId self?.setupNaviRightViews(UserCore.shared.user?.userId == uid) } if self?.likeView != nil{ self?.likeView.isLike = result?.liked ?? false } }.store(in: &cancellables) } // MARK: - Helper private func setupNaviRightViews(_ isSelf: Bool = false){ // if navRightButton != nil{ // return // } navigationView.rightStackH.removeSubviews() navigationView.paddingRightForRightStack = 16 likeView = { let v = HeartLikeCountView(viewSize: .xl) v.purIconStyle() v.likeButton.addTarget(self, action: #selector(tapLikeButton), for: .touchUpInside) navigationView.rightStackH.addArrangedSubview(v) return v }() if let user = info { likeView.isLike = user.liked ?? false } if isSelf{ navRightButton = { let v = EPIconGhostButton(radius: .none, iconSize: .medium, iconCode: .more) v.addTarget(self, action: #selector(tapNaviMore(_:)), for: .touchUpInside) navigationView.rightStackH.addArrangedSubview(v) v.snp.makeConstraints { make in make.size.equalTo(v.bgImageSize()) } return v }() }else{ navRightButton = { let v = EPIconGhostButton(radius: .none, iconSize: .medium, iconCode: .shareBorder) v.addTarget(self, action: #selector(tapShare), for: .touchUpInside) navigationView.rightStackH.addArrangedSubview(v) v.snp.makeConstraints { make in make.size.equalTo(v.bgImageSize()) } return v }() } } // MARK: - Action @objc private func tapNaviMore(_ sender: UIButton) { // let sheet = Sheet() // let share = SheetAction(title: "Share", autoDismiss: true) { [weak self] in // self?.tapShare() // } // let delete = SheetAction(title: "Delete", autoDismiss: true) { [weak self] in // self?.alertDeleteRole() // } // sheet.addAction(share) // sheet.addAction(delete) // sheet.show() let pop = CLPopoverListView() let rect = view.convert(sender.frame, from: sender.superview) var items = [CLPopoverListTextItem]() do { let listItem = CLPopoverListTextItem() listItem.title = "Share" listItem.image = MWIconFont.image(fromIcon: .shareBorder, size: CGSize(width: 20, height: 20), color: .text) listItem.updateLayout() listItem.selectedHandler = {[weak self] _ in self?.tapShare() } items.append(listItem) } do { let listItem = CLPopoverListTextItem() listItem.title = "Delete" listItem.image = MWIconFont.image(fromIcon: .iconDelete, size: CGSize(width: 20, height: 20), color: .text) listItem.updateLayout() listItem.selectedHandler = {[weak self] _ in self?.alertDeleteRole() } items.append(listItem) } pop.setupCommonPopover(rect, inView: view, items: items, block: nil) } @objc private func tapShare(){ guard let aiId = info?.aiId else{return} let content = "Come to Crushlevel for chat, Crush, and AI - chat." let urlString = "\(AppConst.h5urlRoot)/@\(aiId)" guard let url = URL(string: urlString) else { return } // 分享内容(文字 + 链接) let items: [Any] = [url, content] let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil) // iPad 需要设置弹出位置,否则会崩溃 if let popover = activityVC.popoverPresentationController { popover.sourceView = view popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0) popover.permittedArrowDirections = [] } present(activityVC, animated: true, completion: nil) } @objc private func tapLikeButton(){ guard UserCore.shared.checkUserLoginIfNotPushUserToLogin() else{return} // 防止重复点击 guard !isRequesting else { return } guard let user = info, let aiId = user.aiId else { return } let isliked = user.liked.boolValue let likedCount = user.likedNum ?? 0 isRequesting = true // 禁用按钮 likeView.likeButton.isEnabled = false if isliked { // 取消点赞 let likeNewStatus = LikeOrCancelStatus.cancel let newLikedCount = max(likedCount - 1, 0) // 立即更新UI状态 info?.liked = false info?.likedNum = newLikedCount likeView.isLike = false likeView.countLabel.text = String.displayNumber(NSNumber(value: newLikedCount), scale: 1) AIRoleProvider.request(.aiUserLikeOrCancel(aiId: aiId, likedStatus: likeNewStatus), modelType: EmptyModel.self) { [weak self] result in self?.handleLikeRequestResult(result: result, aiId: aiId, isLike: false, originalLikedCount: likedCount) } } else { // 点赞 let likeNewStatus = LikeOrCancelStatus.liked let newLikedCount = likedCount + 1 // 播放点赞动画 likeView.playLotteLike { [weak self] completed in // 立即更新UI状态 self?.info?.liked = true self?.info?.likedNum = newLikedCount self?.likeView.isLike = true self?.likeView.countLabel.text = String.displayNumber(NSNumber(value: newLikedCount), scale: 1) } AIRoleProvider.request(.aiUserLikeOrCancel(aiId: aiId, likedStatus: likeNewStatus), modelType: EmptyModel.self) { [weak self] result in self?.handleLikeRequestResult(result: result, aiId: aiId, isLike: true, originalLikedCount: likedCount) } } } // 统一处理请求结果 private func handleLikeRequestResult(result: Result, aiId: Int, isLike: Bool, originalLikedCount: Int) { isRequesting = false likeView.likeButton.isEnabled = true switch result { case .success: // 请求成功,无需额外处理 break case .failure: // 请求失败,恢复状态 if let currentAiId = self.info?.aiId, currentAiId == aiId { if isLike { // 点赞失败,恢复为未点赞状态 info?.liked = false info?.likedNum = originalLikedCount likeView.isLike = false likeView.countLabel.text = String.displayNumber(NSNumber(value: originalLikedCount), scale: 1) } else { // 取消点赞失败,恢复为点赞状态 info?.liked = true info?.likedNum = originalLikedCount likeView.isLike = true likeView.countLabel.text = String.displayNumber(NSNumber(value: originalLikedCount), scale: 1) } } } // 刷新统计 container.aboutVc.loadStatistics() } private func alertDeleteRole() { let alert = Alert(title: "Delete Role", text: "删除角色不可恢复。为保障用户体验,角色删除后,已经与该角色发生过聊天的或者付费行为的用户,还可以正常与该角色互动。") let delete = AlertAction(title: "Delete", actionStyle: .destructive) { [weak self] in self?.doDeleteRole() } let cancel = AlertAction(title: "Cancel", actionStyle: .cancel) alert.addAction(delete) alert.addAction(cancel) alert.show() } @objc private func tapCreateButton() { // let sheet = Sheet() // sheet.title = "Creation Option" // let option1 = SheetAction(title: "Static Image", autoDismiss: true) {[weak self] in // self?.goGenerateMoreAlbums() // } // let option2 = SheetAction(title: "Dynamic Image", autoDismiss: true) { // Hud.comingsoon() // } // sheet.addAction(option1) // sheet.addAction(option2) // sheet.show() goGenerateMoreAlbums() } @objc private func tapEditAI(){ // Convert AIRoleInfo to AppRouter.goCreateEditAIRole(aiId: aiId) } @objc private func tapEditAvatar(){ guard UserCore.shared.isSelf(id: info?.userId) else{ return } guard let imgUrl = info?.homeImageUrl else {return} ImageDownloader.downloadImage(from: imgUrl) {[weak self] img in guard let image = img else{return} self?.cropAIAvatar(image: image) } } // MARK: - Functions private func goGenerateMoreAlbums(){ guard let aiId = info?.aiId, info != nil else {return} let vc = RolePhotoGenerateController(type: .album) vc.aiId = aiId vc.introduction = info?.introduction vc.sex = info?.sex vc.birthday = info?.birthday presentNaviRootVc(vc: vc) } private func doDeleteRole() { guard let aiInfo = info else { return } Hud.showIndicator() AIRoleProvider.request(.aiDel(id: aiInfo.aiId!)) { [weak self] result in Hud.hideIndicator() switch result { case .success: self?.alertDeleteOK() NotificationCenter.post(name: .aiRoleCreatedOrDelete) UserCore.shared.refreshUserInfo(block: nil) case let .failure(failure): dlog(failure) } } } private func alertDeleteOK() { let alert = Alert(title: "The Role has been deleted", text: "The role has been deleted and cannot be accessed and interacted") let gotit = AlertAction(title: "OK", actionStyle: .confirm) { [weak self] in self?.navigationController?.popViewController(animated: true) } alert.addAction(gotit) alert.show() } @objc private func notificationAIRoleInfoUpdated(noti: Notification) { loadAIInfo(block: nil) } private func cropAIAvatar(image:UIImage?){ guard let avatar = image else {return} let image = avatar // let image = UIImage(named: "egpic")! let cropViewController = CropViewController(image: image) { [unowned self] croppedImage in self.container.headerView.avatarIv.bindImage(img: croppedImage) // 裁剪后的图片 let photo = UploadPhotoM() photo.image = croppedImage photo.addThisItemTimeStamp = Date().timeStamp avatarModel = photo Hud.showIndicator() CloudStorage.shared.s3BatchAddPhotos([photo], bucket: .ROLE) {[weak self] result in if result{ //self?.doPublishRole() self?.doUpdateRoleAvatar() }else{ Hud.hideIndicator() } } } let navc = CLNavigationController(rootViewController: cropViewController) UIWindow.getTopViewController()?.present(navc, animated: true, completion: nil) } private func doUpdateRoleAvatar(){ guard let photo = avatarModel, let remoteFullPath = photo.remoteFullPath else{ Hud.hideIndicator() return } AIRoleProvider.request(.modifyAIAvatar(aiId: aiId, userHead: remoteFullPath), modelType: EmptyModel.self) {[weak self] result in Hud.hideIndicator() switch result { case .success: self?.info?.headImg = remoteFullPath self?.container.headerView.config(info: self?.info) self?.avatarModel = nil case .failure: break } } } }