import NIMSDK import SnapKit import TZImagePickerController import UIKit import Combine class SessionController: CLBaseViewController { var sessionNavigationView: SessionNavigationView! var bgImageView: UIImageView! var overlay: GradientView! var tableView: UITableView! // var headView: SessionAIHeadView! // MARK: BottomViews var bottomViewsStackV : UIStackView! var inputEntrance: SessionInputOperateView! var inputBar: SessionInputView! var moreView: IMMoreItemView! var giftSendView: GiftGridSendView! var textSuggestionsView : SessionInputSuggestionsView! var voiceHoldView: IMVoiceHoldView! var pureBgOperateView:SessionPureBgOperateView! // 长按菜单响应的cell var menuCell: SessionCell? // IM user info var aiInfo: IMAIUserInfo? // -- Layout var bottomConstraintForInputBar: Constraint? // -- flag var firstLoadedUser = false; var isDisappearing = false// 是否正在消失 var dealCancelEditing = false// 是否正在退出键盘输入 var kuolieReportMatchedChatOnce = false // v1.6.0 kuolie var isRequesting = false var isPullingToAIHomePage = false // --- view model lazy var giftVM = GiftViewModel() /// 此版本也是aiId var aiId: Int! /// 439257063882753@r@t var accountId:String! /// eg: 439213911113729@u@t|1|439257063882753@r@t var conversationId: String! var conversation: V2NIMConversation! var util: SessionUtil! = SessionUtil() var cancellables = Set() convenience init(accountID: String) { self.init() accountId = accountID let array = accountID.components(separatedBy: "@") if let number = Int(array.first ?? "0"){ aiId = number } self.conversationId = V2NIMConversationIdUtil.p2pConversationId(accountID) if self.conversationId != nil{ conversation = V2NIMConversation() NIMSDK.shared().v2ConversationService.getConversation(conversationId) {[weak self] conversation in self?.conversation = conversation } failure: { error in dlog("☁️❌get \(String(describing: self.conversationId)) conversation error:\(error)") } }else{ dlog("❌p2pConversationId failed") } } convenience init(conversationId: String) { self.init() self.conversationId = conversationId conversation = V2NIMConversation() let stings = conversationId.components(separatedBy: "|") guard let last = stings.last else{return} accountId = last let strings2 = last.components(separatedBy:"@") if let userIdStr = strings2.first { if let userid = Int(userIdStr) { aiId = userid } } NIMSDK.shared().v2ConversationService.getConversation(conversationId) {[weak self] conversation in self?.conversation = conversation } failure: { error in dlog("☁️❌get \(String(describing: self.conversationId)) conversation error:\(error)") } } override func viewDidLoad() { super.viewDidLoad() setupUI() setupData() setupEvent() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) (navigationController as? CLNavigationController)?.disabledFullScreenPan() IMAIViewModel.shared.updateAI(aiInfo) IMManager.shared.addCache(sessionID: conversationId) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) isDisappearing = false } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) isDisappearing = true } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) isDisappearing = false (navigationController as? CLNavigationController)?.enabledFullScreenPan() cancelEditing() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() // 动态调整tableView的contentInset adjustTableViewContentInset() } deinit { IMManager.shared.deleteCache(sessionID: conversationId) IMManager.shared.clearUnreadCountBy(ids: [conversationId]) } } // MARK: - Views extension SessionController { func setupUI() { view.clipsToBounds = true navigationView.backgroundColor = .clear sessionNavigationView = { let v = SessionNavigationView() view.addSubview(v) v.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } return v }() bgImageView = { let v = UIImageView() v.contentMode = .scaleAspectFill view.addSubview(v) v.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.top.equalToSuperview() make.bottom.equalToSuperview() // 得加,不然图片高度不够撑满 } return v }() // bgImageView.image = UIImage(named: "egpic")?.cropImageTop(with: 1 / UIScreen.aspectRatio) overlay = { let v = GradientView(colors: [UIColor.c.cbn.withAlphaComponent(1), UIColor.c.cbn.withAlphaComponent(0), UIColor.c.cbn.withAlphaComponent(0), UIColor.c.cbn.withAlphaComponent(1)], gradientType: .topToBottom) v.imBgOverlayMode = true view.addSubview(v) v.snp.makeConstraints { make in make.edges.equalToSuperview() } return v }() setupInputView() setupTableView() view.bringSubviewToFront(sessionNavigationView) view.bringSubviewToFront(bottomViewsStackV) } func setupUserInfo() { let imUserInfo = IMUserKit.imUserKitWith(accId: accountId) {[weak self] kit in if self?.conversation.conversationId == kit?.accountId{ self?.navigationView.titleLabel.text = kit?.nickname } } if let userId = imUserInfo.userId, userId > 0 { // titleView.titleLabel.text = kitInfo.showName Hud.showIndicator() requestUserInfo(userId: userId) } else if aiId > 0 { Hud.showIndicator() requestUserInfo(userId: aiId) }else{ assert(false) } } func reloadViews() { guard let user = aiInfo else { return } bgImageView.loadImage(user.backgroundImg, completionBlock: {[weak self] result in switch result { case let .success(imageResult): let image = imageResult.image self?.bgImageView.snp.makeConstraints { make in make.width.equalTo((self?.bgImageView.snp.height)!).multipliedBy(image.size.width / image.size.height) } //self?.bgImageView.image = image.cropImageTop(with: 1/UIScreen.aspectRatio) case .failure: return } }) sessionNavigationView.config(user: user) tableView.reloadData() // 刷新一些页面上的其他数据(通过UserInfo) } } // MARK: Datas & Events extension SessionController { func setupData() { setupNIM() util.conversationId = conversationId setupUserInfo() WalletCore.shared.refreshWallet() // 进入页面需要标记已读 markReadAll() } } // MARK: - Noti extension SessionController { @objc func keyboardWillChanged(noti: Notification) { // guard tableView.window != nil else { // return // } if isDisappearing || isDisplaying == false { return } guard let userInfo = noti.userInfo else { return } // 获取参数 let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! TimeInterval let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey]! as! Int let beginFrame = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue var hideKeyboardFlag = false var showKeyboardFlag = false // dlog("⌨️duration:\(duration)") if beginFrame.origin.y < UIScreen.height, endFrame.origin.y >= UIScreen.height { // 收起键盘 hideKeyboardFlag = true //dlog("⌨️close keyboard") } else if beginFrame.origin.y >= UIScreen.height, endFrame.origin.y < UIScreen.height { // 弹出键盘 showKeyboardFlag = true //dlog("⌨️show keyboard") } else if beginFrame.size.height != endFrame.size.height { // 键盘高度变更 //dlog("⌨️height of keyboard changed") } if hideKeyboardFlag{ // 将要关键盘 self.doHideKeyBordActions() }else if showKeyboardFlag{ // 将要显示键盘 self.doKeyboardShowActions() let offsetY = UIScreen.height - endFrame.origin.y// - UIWindow.safeAreaBottom self.updateInputEntrance(offsetY: offsetY, duration: duration, curve: curve) } else if !self.dealCancelEditing { let offsetY = UIScreen.height - endFrame.origin.y// - UIWindow.safeAreaBottom self.updateInputEntrance(offsetY: offsetY, duration: duration, curve: curve) } UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(rawValue: UInt(curve) << 16), animations: { self.view.layoutIfNeeded() self.scrollToBottom(self.tableView, animated: true) }) } @objc func menuHide(noti: Notification) { guard let menu = noti.object as? UIMenuController else { return } guard let menuItems = menu.menuItems else { return } guard let first = menuItems.first else { return } if first.action == Selector(("copyAction")) { UIMenuController.shared.menuItems = nil } else { } } @objc func notifyChatSettingUpdated(){ requestUserInfo(userId: aiId) } @objc func notifiyRelationHiddenUpdate(){ requestUserInfo(userId: aiId) } @objc func notifiyRelationInfoUpdate(){ requestUserInfo(userId: aiId) } } // MARK: - Action // BottomViews extension SessionController { @objc func titleTapAction() { guard let user = aiInfo, let aiId = user.aiId else { return } AppRouter.goAIRoleHome(aiId: aiId) } /// 主动退出键盘 @objc func cancelEditing() { dealCancelEditing = true DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.dealCancelEditing = false } if inputBar.textView.isFirstResponder{ view.endEditing(true) }else{ hideOperateView() } } func doKeyboardShowActions(){ self.inputBar.isHidden = false self.inputEntrance.isHidden = true // self.moreView.isHidden = true //showMoreItems(show: false) } @objc func doHideKeyBordActions() { // showHeaderView(show: true, duration: 0.3) UIView.animate(withDuration: 0.1) { self.inputBar.alpha = 0 self.inputBar.layoutIfNeeded() } completion: { finished in self.inputBar.isHidden = true self.inputBar.alpha = 1 } // self.inputEntrance.isHidden = false // self.bottomViewsStackV.setNeedsDisplay() // self.bottomViewsStackV.layoutIfNeeded() // showMoreItems(show: false) hideAllBottomViews(except: [inputEntrance]) updateInputEntrance(offsetY: 0, duration: 0, curve: UIView.AnimationCurve.easeOut.rawValue) // 0.3 } } // MARK: - Session Audio Phone call delegaet. // // extension SessionController: SessionHeadPhoneCallGuideDelegate { // func headPhoneCallGuideTapCall() { // guard AudioRecordTool.audioAuth() else { // return // } // // guard AudioPlayTool.audioChannelFreeToUse(), PhoneManager.isInPhoneChannel() == false else { // return // } // // hideCallGuide() // phoneCallHandler.onSelectVoice(user: userInfo, session: session) // } // } // MARK: - Animation extension SessionController { // func showMoreItem(show: Bool) { // if show { // UIView.animate(withDuration: 0.3) { // self.moreView.alpha = 1 // self.moreView.snp.updateConstraints { make in // make.top.equalTo(self.inputBar.snp.bottom).offset(-UIWindow.safeAreaBottom) // } // self.view.layoutIfNeeded() // } completion: { _ in // } // } else { // UIView.animate(withDuration: 0.3) { // self.moreView.alpha = 0 // self.moreView.snp.updateConstraints { make in // make.top.equalTo(self.inputBar.snp.bottom).offset(10) // } // self.view.layoutIfNeeded() // } completion: { _ in // } // } // } func updateInputEntrance(offsetY: CGFloat, duration: TimeInterval, curve: Int) { let offsetY = offsetY > 0 ? offsetY : (UIWindow.safeAreaBottom) self.bottomViewsStackV.snp.updateConstraints { make in make.bottom.equalTo(self.view).offset(-offsetY) } // self.bottomViewsStackV.setNeedsDisplay() // self.bottomViewsStackV.layoutIfNeeded() // // // 添加这行来动态调整tableView的contentInset // DispatchQueue.main.async { [weak self] in // self?.adjustTableViewContentInset() // } } func updateTableContentOffset(duration: TimeInterval){ UIView.animate(withDuration: duration) { self.updateTableViewContentLayout() self.view.layoutIfNeeded() } } }