464 lines
14 KiB
Swift
Executable File
464 lines
14 KiB
Swift
Executable File
|
||
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()
|
||
|
||
// 动态调整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()
|
||
}
|
||
}
|
||
}
|