Visual_Novel_iOS/crush/Crush/Src/Modules/Chat/Session/SessionController.swift

464 lines
14 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
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<AnyCancellable>()
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()
// tableViewcontentInset
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()
//
// // tableViewcontentInset
// DispatchQueue.main.async { [weak self] in
// self?.adjustTableViewContentInset()
// }
}
func updateTableContentOffset(duration: TimeInterval){
UIView.animate(withDuration: duration) {
self.updateTableViewContentLayout()
self.view.layoutIfNeeded()
}
}
}