447 lines
16 KiB
Swift
447 lines
16 KiB
Swift
//
|
||
// RoleHomePagerController.swift
|
||
// Crush
|
||
//
|
||
// Created by Leon on 2025/7/24.
|
||
//
|
||
|
||
import Combine
|
||
import UIKit
|
||
class RoleHomePagerController: CLViewController<RoleHomePagerView> {
|
||
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<AnyCancellable>()
|
||
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<EmptyModel?, ResponseError>, 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
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
}
|