Browse Source

对 TSChatViewController 进行整理拆分

100Years 2 months ago
parent
commit
b213a0cee4

+ 26 - 14
AIEmoji.xcodeproj/project.pbxproj

@@ -49,10 +49,8 @@
 		A89EA66B2D59AA31000EB181 /* TSTextMessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6662D59AA31000EB181 /* TSTextMessageContentCell.swift */; };
 		A89EA66C2D59AA31000EB181 /* TSMessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6652D59AA31000EB181 /* TSMessageContentCell.swift */; };
 		A89EA66D2D59AA31000EB181 /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6672D59AA31000EB181 /* TableViewCells.swift */; };
-		A89EA6762D59C93E000EB181 /* TSAIChatContainerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6752D59C932000EB181 /* TSAIChatContainerVC.swift */; };
 		A89EA67A2D59D25F000EB181 /* TSAIChatVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6792D59D248000EB181 /* TSAIChatVM.swift */; };
 		A89EA67D2D59F1AF000EB181 /* StreamPostRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA67C2D59F1AC000EB181 /* StreamPostRequest.swift */; };
-		A89EA6812D59F44D000EB181 /* TSAIChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6802D59F440000EB181 /* TSAIChatVC.swift */; };
 		A89EA6832D59F4F9000EB181 /* TSChatViewController+ChatDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6822D59F4F1000EB181 /* TSChatViewController+ChatDelegate.swift */; };
 		A89EA6A32D5B26E3000EB181 /* TSDBAIChatList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6A22D5B26E3000EB181 /* TSDBAIChatList.swift */; };
 		A89EA6AC2D5B3EFB000EB181 /* TSRealmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6AB2D5B3EFA000EB181 /* TSRealmManager.swift */; };
@@ -65,7 +63,10 @@
 		A89EA6C12D5ED289000EB181 /* TSChatCellConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6C02D5ED278000EB181 /* TSChatCellConfig.swift */; };
 		A89EA6C42D5F40CC000EB181 /* TSChatInputBarVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6C32D5F40CB000EB181 /* TSChatInputBarVC.swift */; };
 		A89EA6C62D5F5C22000EB181 /* TSChatInputFullScreenVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6C52D5F5C21000EB181 /* TSChatInputFullScreenVC.swift */; };
-		A89EA6C82D6359ED000EB181 /* TSChatViewController+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6C72D6359EA000EB181 /* TSChatViewController+View.swift */; };
+		A89EA6C82D6359ED000EB181 /* TSChatViewController+VipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6C72D6359EA000EB181 /* TSChatViewController+VipView.swift */; };
+		A89EA6CA2D642C0A000EB181 /* TSChatViewController+SendMsg.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6C92D642C03000EB181 /* TSChatViewController+SendMsg.swift */; };
+		A89EA6CC2D642CE2000EB181 /* TSChatViewController+NaviBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6CB2D642CD4000EB181 /* TSChatViewController+NaviBar.swift */; };
+		A89EA6CF2D6430F3000EB181 /* TSChatViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6CE2D6430EE000EB181 /* TSChatViewController+Keyboard.swift */; };
 		A8EEADD42D3E6C660032C5A0 /* Flower💐.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD32D3E6C610032C5A0 /* Flower💐.json */; };
 		A8EEADD62D3E6CD80032C5A0 /* Fish🐠.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */; };
 		A8EEADD82D3E74D20032C5A0 /* Pink🩷.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */; };
@@ -167,10 +168,8 @@
 		A89EA6652D59AA31000EB181 /* TSMessageContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSMessageContentCell.swift; sourceTree = "<group>"; };
 		A89EA6662D59AA31000EB181 /* TSTextMessageContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTextMessageContentCell.swift; sourceTree = "<group>"; };
 		A89EA6672D59AA31000EB181 /* TableViewCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
-		A89EA6752D59C932000EB181 /* TSAIChatContainerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIChatContainerVC.swift; sourceTree = "<group>"; };
 		A89EA6792D59D248000EB181 /* TSAIChatVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIChatVM.swift; sourceTree = "<group>"; };
 		A89EA67C2D59F1AC000EB181 /* StreamPostRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamPostRequest.swift; sourceTree = "<group>"; };
-		A89EA6802D59F440000EB181 /* TSAIChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIChatVC.swift; sourceTree = "<group>"; };
 		A89EA6822D59F4F1000EB181 /* TSChatViewController+ChatDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSChatViewController+ChatDelegate.swift"; sourceTree = "<group>"; };
 		A89EA6A22D5B26E3000EB181 /* TSDBAIChatList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDBAIChatList.swift; sourceTree = "<group>"; };
 		A89EA6AB2D5B3EFA000EB181 /* TSRealmManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRealmManager.swift; sourceTree = "<group>"; };
@@ -183,7 +182,10 @@
 		A89EA6C02D5ED278000EB181 /* TSChatCellConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSChatCellConfig.swift; sourceTree = "<group>"; };
 		A89EA6C32D5F40CB000EB181 /* TSChatInputBarVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSChatInputBarVC.swift; sourceTree = "<group>"; };
 		A89EA6C52D5F5C21000EB181 /* TSChatInputFullScreenVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSChatInputFullScreenVC.swift; sourceTree = "<group>"; };
-		A89EA6C72D6359EA000EB181 /* TSChatViewController+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSChatViewController+View.swift"; sourceTree = "<group>"; };
+		A89EA6C72D6359EA000EB181 /* TSChatViewController+VipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSChatViewController+VipView.swift"; sourceTree = "<group>"; };
+		A89EA6C92D642C03000EB181 /* TSChatViewController+SendMsg.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSChatViewController+SendMsg.swift"; sourceTree = "<group>"; };
+		A89EA6CB2D642CD4000EB181 /* TSChatViewController+NaviBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSChatViewController+NaviBar.swift"; sourceTree = "<group>"; };
+		A89EA6CE2D6430EE000EB181 /* TSChatViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSChatViewController+Keyboard.swift"; sourceTree = "<group>"; };
 		A8EEADD32D3E6C610032C5A0 /* Flower💐.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Flower💐.json"; sourceTree = "<group>"; };
 		A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Fish🐠.json"; sourceTree = "<group>"; };
 		A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Pink🩷.json"; sourceTree = "<group>"; };
@@ -425,7 +427,6 @@
 			children = (
 				A89EA6AF2D5C9861000EB181 /* TSAIChatHistoryVC */,
 				A89EA6772D59D224000EB181 /* TSChatViewController */,
-				A89EA6752D59C932000EB181 /* TSAIChatContainerVC.swift */,
 			);
 			path = AIChat;
 			sourceTree = "<group>";
@@ -465,13 +466,10 @@
 		A89EA6772D59D224000EB181 /* TSChatViewController */ = {
 			isa = PBXGroup;
 			children = (
+				A89EA6D02D64333D000EB181 /* TSChatViewController */,
 				A89EA6C22D5F4094000EB181 /* TSChatInputBarVC */,
 				A89EA6782D59D238000EB181 /* ViewModel */,
 				A89EA6682D59AA31000EB181 /* Views */,
-				A89EA65E2D59AA11000EB181 /* TSChatViewController.swift */,
-				A89EA6822D59F4F1000EB181 /* TSChatViewController+ChatDelegate.swift */,
-				A89EA6C72D6359EA000EB181 /* TSChatViewController+View.swift */,
-				A89EA6802D59F440000EB181 /* TSAIChatVC.swift */,
 				A89EA64D2D59A9F4000EB181 /* Layout */,
 				A89EA6532D59A9F4000EB181 /* Models */,
 			);
@@ -538,6 +536,19 @@
 			path = TSChatInputBarVC;
 			sourceTree = "<group>";
 		};
+		A89EA6D02D64333D000EB181 /* TSChatViewController */ = {
+			isa = PBXGroup;
+			children = (
+				A89EA65E2D59AA11000EB181 /* TSChatViewController.swift */,
+				A89EA6822D59F4F1000EB181 /* TSChatViewController+ChatDelegate.swift */,
+				A89EA6C72D6359EA000EB181 /* TSChatViewController+VipView.swift */,
+				A89EA6C92D642C03000EB181 /* TSChatViewController+SendMsg.swift */,
+				A89EA6CB2D642CD4000EB181 /* TSChatViewController+NaviBar.swift */,
+				A89EA6CE2D6430EE000EB181 /* TSChatViewController+Keyboard.swift */,
+			);
+			path = TSChatViewController;
+			sourceTree = "<group>";
+		};
 		A8F774602D38E8B000AA6E93 = {
 			isa = PBXGroup;
 			children = (
@@ -1075,10 +1086,11 @@
 				A80E72792D42285500C64288 /* TSBootPageVC.swift in Sources */,
 				A80E726A2D409E5400C64288 /* TSDiyTLYFlowersView.swift in Sources */,
 				A8F7764E2D3E00A800AA6E93 /* TSEmojisColViewModel.swift in Sources */,
-				A89EA6C82D6359ED000EB181 /* TSChatViewController+View.swift in Sources */,
+				A89EA6C82D6359ED000EB181 /* TSChatViewController+VipView.swift in Sources */,
 				A8F776422D3B75FC00AA6E93 /* TSBottomAlertVC.swift in Sources */,
 				A8F775192D38EC6800AA6E93 /* TSEmojisVC.swift in Sources */,
 				A80E725C2D3FB09400C64288 /* TSKeyboardView.swift in Sources */,
+				A89EA6CC2D642CE2000EB181 /* TSChatViewController+NaviBar.swift in Sources */,
 				A8F7753B2D3918DE00AA6E93 /* TSNetworkManager+Loading.swift in Sources */,
 				A80E721A2D3F393A00C64288 /* DiyStickerModel.swift in Sources */,
 				A80E726F2D40DE2B00C64288 /* TSWallpaperPreviewVC.swift in Sources */,
@@ -1090,6 +1102,7 @@
 				A80E73E42D533EB000C64288 /* TSPurchaseManager.swift in Sources */,
 				A8F7762F2D3A765400AA6E93 /* TSGenmojiViewModel.swift in Sources */,
 				A8F7751B2D38EC9800AA6E93 /* TSGenmojiVC.swift in Sources */,
+				A89EA6CF2D6430F3000EB181 /* TSChatViewController+Keyboard.swift in Sources */,
 				A8F7754E2D39E59100AA6E93 /* TSGenmojiModel.swift in Sources */,
 				A8F776452D3DE8A800AA6E93 /* TSSmallIconBrowseVC.swift in Sources */,
 				A80E72632D40925000C64288 /* TSDiyKeyboardVM.swift in Sources */,
@@ -1121,8 +1134,6 @@
 				A80E72222D3F3A9200C64288 /* DiyStickerElement.swift in Sources */,
 				A8F775002D38EA8C00AA6E93 /* WindowHelper.swift in Sources */,
 				A8F7764B2D3E008500AA6E93 /* TSEmojisChildColViewModel.swift in Sources */,
-				A89EA6762D59C93E000EB181 /* TSAIChatContainerVC.swift in Sources */,
-				A89EA6812D59F44D000EB181 /* TSAIChatVC.swift in Sources */,
 				A80E72772D41EFF900C64288 /* TSEmojisTutorialsVC.swift in Sources */,
 				A8F776482D3DE9F600AA6E93 /* TSSmallIconBrowseCell.swift in Sources */,
 				A8F7753D2D3918F800AA6E93 /* TSNetWork+Business.swift in Sources */,
@@ -1152,6 +1163,7 @@
 				A8F775382D390C3C00AA6E93 /* TSNetworkManager.swift in Sources */,
 				A89EA65F2D59AA11000EB181 /* TSChatViewController.swift in Sources */,
 				A89EA6C62D5F5C22000EB181 /* TSChatInputFullScreenVC.swift in Sources */,
+				A89EA6CA2D642C0A000EB181 /* TSChatViewController+SendMsg.swift in Sources */,
 				A89EA6B12D5C9D0C000EB181 /* TSAIChatHistoryVC.swift in Sources */,
 				A8FB02B72D3E3A3D0031A396 /* TSEmojisChildViewModel.swift in Sources */,
 				A8F7754B2D39376800AA6E93 /* TSSettingListView.swift in Sources */,

+ 7 - 4
AIEmoji/Business/AIChat/TSChatViewController/Models/TSTextLayoutSizeCalculator.swift

@@ -56,8 +56,13 @@ class TSTextLayoutSizeCalculator: TSLayoutSizeCalculator {
         }
         let maxWidth = fromCurrentSender ? cellMessagelabelTraingMaxWidth: cellMessagelabelLeadingMaxWidth
 //        let textSize = sendText.height(ofFont: Self.messageLabelFont, maxWidth: maxWidth)
-        var size = attributedText.size(consideringWidth: maxWidth)
-        size.width = size.width + 10
+
+        //用 label 计算更加精准
+        var size = UILabel.getAttributedTextSize(attributedText: attributedText, maxWidth: maxWidth)
+
+//        var size = attributedText.size(consideringWidth: maxWidth)
+//        size.width = size.width + 10
+
         let minSize = Self.cellMessagelabelMinSize
         if size.width < minSize.width {
             size.width = minSize.width
@@ -65,8 +70,6 @@ class TSTextLayoutSizeCalculator: TSLayoutSizeCalculator {
         if size.height < minSize.height {
             size.height = minSize.height
         }
-        
         return size
     }
-    
 }

+ 0 - 15
AIEmoji/Business/AIChat/TSChatViewController/TSAIChatVC.swift

@@ -1,15 +0,0 @@
-//
-//  TSAIChatVC.swift
-//  AIEmoji
-//
-//  Created by 100Years on 2025/2/10.
-//
-
-class TSAIChatVC: TSChatViewController {
-    
-    
-    
-    override func viewDidLoad() {
-        
-    }
-}

+ 0 - 159
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController+View.swift

@@ -1,159 +0,0 @@
-//
-//  TSChatViewController+View.swift
-//  AIEmoji
-//
-//  Created by 100Years on 2025/2/17.
-//
-
-extension TSChatViewController {
-    
-    
-     var creatVipBtn: UIButton{
-        let vipBtn = UIButton.createButton(image: UIImage(named: "nav_vip")) { [weak self]  in
-            guard let self = self else { return }
-            TSPurchaseVC.show(target: self) {
-      
-            }
-        }
-        return vipBtn
-    }
-    
-     var creatNavBarView: TSBaseNavContentBarView{
-        let navBarView = TSBaseNavContentBarView()
-        
-        let titleImageView = UIImageView.createImageView(imageName: "nav_title_aichat",contentMode: .scaleToFill)
-        navBarView.barView.addSubview(titleImageView)
-        titleImageView.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.left.equalTo(16)
-        }
-        
-        let setBtn = UIButton.createButton(image: UIImage(named: "setting")) { [weak self]  in
-            guard let self = self else { return }
-            let setingVC = TSSetingVC()
-            setingVC.hidesBottomBarWhenPushed = true
-            navigationController?.pushViewController(setingVC, animated: true)
-        }
-        navBarView.barView.addSubview(setBtn)
-        setBtn.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.trailing.equalTo(-16)
-            make.width.height.equalTo(24)
-        }
-        
-        let historyBtn = UIButton.createButton(image: UIImage(named: "aichat_history")) { [weak self]  in
-            guard let self = self else { return }
-            let historyVC = TSAIChatHistoryVC()
-            historyVC.hidesBottomBarWhenPushed = true
-            navigationController?.pushViewController(historyVC, animated: true)
-        }
-        navBarView.barView.addSubview(historyBtn)
-        historyBtn.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.trailing.equalTo(-60)
-            make.width.height.equalTo(24)
-        }
-        
-        navBarView.barView.addSubview(vipBtn)
-        vipBtn.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.trailing.equalTo(-104)
-            make.width.height.equalTo(24)
-        }
-        
-        return navBarView
-    }
-    
-    
-    var creatInputBarBgView:UIView{
-        let inputBarBgView = UIView()
-        inputBarBgView.addShadow(shadowColor: "#111111".uiColor.cgColor, shadowOffset: CGSize(width: 0, height: -10), shadowRadius: 10, shadowOpacity: 1.0)
-        return inputBarBgView
-    }
-    
-    
-    var creatFreeText: UILabel{
-        let textLabel = UILabel.createLabel(
-            text: "Remaining \(kPurchaseDefault.freeNum(type: .aichat)) free times",
-            font: .font(size: 12),
-            textColor: "#E83E3E".uiColor,
-            textAlignment: .center,
-            numberOfLines: 0
-        )
-        textLabel.isHidden = false
-        return textLabel
-    }
-    
-    
-    var creatUpgradeVipBg: UIView{
-        let upgradeVipBg = UIView()
-        
-        let imageView = UIImageView.createImageView(imageName: "vip_upgrade_bg",contentMode: .scaleToFill)
-        upgradeVipBg.addSubview(imageView)
-        imageView.snp.makeConstraints { make in
-            make.edges.equalToSuperview()
-        }
-        
-        let label = UILabel.createLabel(
-            text:"Free usage limit reached. Upgrade for unlimited chats.".localized,
-            font: .font(size: 14,weight: .bold),
-            textColor: "#111111".uiColor,
-            numberOfLines: 0
-        )
-        upgradeVipBg.addSubview(label)
-        label.snp.makeConstraints { make in
-            make.leading.equalTo(16)
-            make.top.equalTo(8)
-            make.bottom.equalTo(-8)
-            make.trailing.equalTo(-95)
-        }
-        
-        let upgradeBtn = UIButton.createButton(
-            title: "Upgrade".localized,
-            backgroundColor: "#111111".uiColor,
-            font:.font(size: 12,weight: .medium),
-            titleColor:.white,
-            corner: 14) { [weak self]  in
-                guard let self = self else { return }
-                TSPurchaseVC.show(target: self) { [weak self]  in
-//                    guard let self = self else { return }
-//                    updateVipView()
-                }
-            }
-        upgradeVipBg.addSubview(upgradeBtn)
-        upgradeBtn.snp.makeConstraints { make in
-            make.trailing.equalTo(-12)
-            make.centerY.equalToSuperview()
-            make.width.equalTo(70)
-            make.height.equalTo(28)
-        }
-        return upgradeVipBg
-    }
-    
-    
-    var creatScrollToBottomButton: UIButton{
-        let backBottomBtn = UIButton.createButton(image: UIImage(named: "down_arrow_line")) { [weak self]  in
-            guard let self = self else { return }
-            messagesCollectionView.scrollToLastItem(animated: false)
-        }
-        backBottomBtn.isHidden = true
-        backBottomBtn.backgroundColor = .popupColor
-        backBottomBtn.cornerRadius = 16.0
-        return backBottomBtn
-    }
-    
-    
-    
-   var creatNavBarContentView: UIView{
-        let view = UIView()
-        view.backgroundColor = .clear
-        return view
-    }
-    
-    var creatNormalNavBarView: TSNormalNavigationBarView{
-        let view = TSNormalNavigationBarView()
-        return view
-    }
-    
-    
-}

+ 0 - 609
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController.swift

@@ -1,609 +0,0 @@
-//
-// MIT License
-//
-// Copyright (c) 2017-2020 MessageKit
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-
-import InputBarAccessoryView
-import MessageKit
-import UIKit
-
-class TSChatViewController: MessagesViewController, MessagesDataSource {
-
-    var viewModel:TSAIChatVM = TSAIChatVM()
-    
-    
-    public lazy var navBarContentView: UIView = creatNavBarContentView
-    
-    public lazy var normalNavBarView: TSNormalNavigationBarView = creatNormalNavBarView
-    
-    
-    // MARK: - Public properties
-    lazy var messageList: [TSChatMessage] = []
-    
-    private(set) lazy var refreshControl: UIRefreshControl = {
-        let control = UIRefreshControl()
-        control.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
-        return control
-    }()
-    
-    // MARK: Private
-    lazy var textMessageSizeCalculator = TSTextLayoutSizeCalculator(layout: self.messagesCollectionView.messagesCollectionViewFlowLayout)
-    
-    lazy var inputBarVC: TSChatInputBarVC = {
-        let inputBarVC = TSChatInputBarVC()
-        inputBarVC.sendComplete = { [weak self] components in
-            guard let self = self else { return }
-            inputSendMsg(components)
-        }
-        return inputBarVC
-    }()
-    
-    
-    lazy var vipBtn: UIButton = creatVipBtn
-    lazy var navBarView: TSBaseNavContentBarView = creatNavBarView
-    
-    var deleteBlock:(()->Void)?
-
-    lazy var inputBarBgView:UIView = creatInputBarBgView
-    let inputBarTopView:UIView = UIView()
-
-    //免费次数label
-    lazy var freeText: UILabel = creatFreeText
-    lazy var upgradeVipBg: UIView = creatUpgradeVipBg
-    lazy var scrollToBottomButton: UIButton = creatScrollToBottomButton
-    
-    
-    
-    
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        navigationItem.title = "MessageKit"
-        edgesForExtendedLayout = [.top]
- 
-        // 创建轻击手势识别器
-        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickView))
-      // 设置轻击手势识别器取消默认的视图触摸响应,避免影响子视图交互
-       tapGesture.cancelsTouchesInView = false
-      // 将轻击手势识别器添加到视图上
-        messagesCollectionView.addGestureRecognizer(tapGesture)
-        configureNaviBarView()
-        configureMessageCollectionView()
-        configureMessageInputBar()
-        configureOtherUI()
-        loadFirstMessages()
-        
-        vipBtn.isHidden = PurchaseManager.default.isVip
-        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
-        if viewModel.uiStyle == .chat {
-            // 注册通知监听,App死的时候,保存本次聊天记录到本地
-            NotificationCenter.default.addObserver(self, selector: #selector(saveChatList), name: .kApplicationWillTerminate, object: nil)
-            // 监听键盘事件
-            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
-            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
-        }
-    }
-    
-    @objc func vipInfoChanged() {
-        kExecuteOnMainThread {
-            self.vipBtn.isHidden = PurchaseManager.default.isVip
-            self.updateVipView()
-        }
-    }
-    
-    @objc func clickView(){
-        view.endEditing(true)
-    }
-    
-    @objc func keyboardWillShow(_ notification: Notification) {
-
-        guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
-              let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else {
-            return
-        }
-
-        let keyboardHeight = keyboardFrame.height
-        let contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
-
-        UIView.animate(withDuration: animationDuration) {
-            self.messagesCollectionView.contentInset = contentInset
-            self.messagesCollectionView.scrollIndicatorInsets = contentInset
-        }
-        
-        kDelayMainShort {
-            self.messagesCollectionView.scrollToLastItem(animated: false)
-        }
-    }
-
-    @objc func keyboardWillHide(_ notification: Notification) {
-        guard let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else {
-            return
-        }
-
-        let contentInset = UIEdgeInsets.zero
-
-        UIView.animate(withDuration: animationDuration) {
-            self.messagesCollectionView.contentInset = contentInset
-            self.messagesCollectionView.scrollIndicatorInsets = contentInset
-        }
-    }
-    
-    
-    @objc func saveChatList() {
-        messageList.remove(at: 0)
-        viewModel.updateMessages(msgModels: messageList)
-    }
-    
-    override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-    }
-    
-    override func viewDidDisappear(_ animated: Bool) {
-        super.viewDidDisappear(animated)
-    }
-    
-    func loadFirstMessages() {
-        //获取消息数量
-        self.messageList = viewModel.getHistoryChatMessage()
-        self.messagesCollectionView.reloadData()
-        self.messagesCollectionView.scrollToLastItem(animated: false)
-    }
-    
-    @objc
-    func loadMoreMessages() {
-        //获取更多消息数量
-        
-    }
-    
-    public func addNormalNavBarView(){
-        navBarContentView.addSubview(normalNavBarView)
-        normalNavBarView.snp.makeConstraints { make in
-            make.edges.equalToSuperview()
-        }
-    }
-    
-    public func setTitleText(_ title: String) {
-       _ = normalNavBarView.setTitleName(NSLocalizedString(title, comment: ""))
-    }
-
-    public func setPageTitle(_ title: String) {
-        let pageTitle = title
-        let backTitle = " "
-        setTitleText(pageTitle)
-        _ = setNavigationItem(backTitle, imageName: "navi_back_white", direction: .left, action: #selector(navBarClickLeftAction))
-    }
-    @objc public func navBarClickLeftAction() {
-        debugPrint("navBarClickLeftAction -> \(type(of: self))")
-        pop()
-    }
-    
-    public func pop() {
-        if navigationController == nil {
-            dismiss(animated: true, completion: nil)
-        } else if navigationController?.presentingViewController != nil, navigationController?.viewControllers.count == 1 {
-            navigationController?.dismiss(animated: true, completion: nil)
-        } else {
-            navigationController?.popViewController(animated: true)
-        }
-    }
-    
-    public func setNavigationItem(_ name: String, imageName: String, direction: NSTextAlignment, action: Selector) -> UIButton {
-        if direction == .left {
-            return normalNavBarView.setLeftNavigationItem(name: name, imageName: imageName, target: self, action: action)
-        } else {
-            return normalNavBarView.setRightNavigationItem(name: name, imageName: imageName, target: self, action: action)
-        }
-    }
-    @objc func clickDelete(){
-        showCustomAlert(message: "Are you sure to delete".localized, deleteHandler:  { [weak self]  in
-            guard let self = self else { return }
-            viewModel.dbAIChatList.delete()
-        
-            deleteBlock?()
-            pop()
-        })
-        
-        
-    }
-    func configureNaviBarView() {
-        view.addSubview(navBarContentView)
-        navBarContentView.snp.makeConstraints { make in
-            make.leading.top.trailing.equalToSuperview()
-            make.height.equalTo(k_Nav_Height)
-        }
-        
-        if viewModel.uiStyle == .history {
-            addNormalNavBarView()
-            setPageTitle("History".localized)
-            _ = setNavigationItem("", imageName: "delete_white", direction: .right, action: #selector(clickDelete))
-        }else{
-            navBarContentView.addSubview(navBarView)
-            navBarView.snp.makeConstraints { make in
-                make.edges.equalToSuperview()
-            }
-        }
-    }
-    
-
-    
-    func configureMessageCollectionView() {
-        clearAndResetConstraints()
-        view.backgroundColor = .mainBg
-        //设置自定义FlowLayout,itemsize等,都在这里控制
-        let flowLayout = CustomMessagesFlowLayout()
-        flowLayout.sectionInset = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)
-    
-        messagesCollectionView.collectionViewLayout = flowLayout
-        messagesCollectionView.backgroundColor = .clear
-        messagesCollectionView.register(TSTextMessageContentCell.self)
-        messagesCollectionView.messagesLayoutDelegate = self
-        messagesCollectionView.messagesDisplayDelegate = self
-        messagesCollectionView.messagesDataSource = self
-        messagesCollectionView.messageCellDelegate = self
-        messagesCollectionView.clipsToBounds = true
-        scrollsToLastItemOnKeyboardBeginsEditing = true // default false
-        maintainPositionOnInputBarHeightChanged = true // default false
-        showMessageTimestampOnSwipeLeft = false // default false
-        //        messagesCollectionView.refreshControl = refreshControl
-        messagesCollectionView.reloadData()
-    }
-    
-    
-    func clearAndResetConstraints() {
-        // 筛选出与 messagesCollectionView 相关的约束
-        let constraintsToRemove = view.constraints.filter { constraint in
-            return (constraint.firstItem as? UIView == messagesCollectionView) || (constraint.secondItem as? UIView == messagesCollectionView)
-        }
-        // 停用并移除这些约束
-        NSLayoutConstraint.deactivate(constraintsToRemove)
-        for constraint in constraintsToRemove {
-            view.removeConstraint(constraint)
-        }
-
-        
-        messagesCollectionView.snp.remakeConstraints { make in
-            make.leading.trailing.bottom.equalTo(0)
-            make.top.equalTo(k_Nav_Height)
-        }
-    }
-    
-    func configureMessageInputBar() {
-        
-        inputBarBgView.addSubview(inputBarTopView)
-        inputBarTopView.snp.makeConstraints { make in
-            make.leading.equalTo(0)
-            make.trailing.equalTo(0)
-            make.top.equalTo(0)
-        }
-        
-        if viewModel.uiStyle == .chat {
-            inputBarBgView.addSubview(inputBarVC.view)
-            inputBarVC.view.snp.makeConstraints { make in
-                make.leading.equalTo(0)
-                make.trailing.equalTo(0)
-                make.top.equalTo(inputBarTopView.snp.bottom)
-                make.bottom.equalTo(0)
-            }
-        }
-        inputBarType = .custom(inputBarBgView)
-    }
-    
-    
-    func configureOtherUI() {
-        view.addSubview(scrollToBottomButton)
-        scrollToBottomButton.snp.makeConstraints { make in
-            make.centerX.equalToSuperview()
-            make.bottom.equalTo(inputContainerView.snp.top).offset(-14)
-            make.width.height.equalTo(32)
-        }
-        self.scrollViewDidScroll(self.messagesCollectionView)
-        
-        updateVipView()
-    }
-    
-    func updateVipView() {
-        inputBarTopView.subviews.forEach { $0.removeFromSuperview()}
-        
-        if viewModel.uiStyle == .chat ,
-           kPurchaseDefault.isVip == false
-        {
-            let freeNum = kPurchaseDefault.freeNum(type: .aichat)
-            if freeNum > 0 {
-                freeText.text = "Remaining \(freeNum) free times"
-                inputBarTopView.addSubview(freeText)
-                freeText.snp.makeConstraints { make in
-                    make.leading.equalTo(20)
-                    make.trailing.equalTo(-20)
-                    make.bottom.equalTo(-8)
-                    make.top.equalTo(8)
-                }
-            }else{
-                inputBarTopView.addSubview(upgradeVipBg)
-                upgradeVipBg.snp.makeConstraints { make in
-                    make.leading.equalTo(16)
-                    make.trailing.equalTo(-16)
-                    make.bottom.equalTo(-2)
-                    make.top.equalTo(14)
-                }
-            }
-        }
-    }
-    
-    
-    
-    // MARK: - Helpers
-    var lastIndexPath:IndexPath{
-        if messageList.count == 0 {
-            return IndexPath(item: 0, section: 0)
-        }
-        return IndexPath(item: messageList.count - 1, section: 0)
-    }
-    
-    func insertMessage(_ message: TSChatMessage) {
-        messageList.append(message)
-        messagesCollectionView.performBatchUpdates({
-            messagesCollectionView.insertItems(at: [lastIndexPath])
-            if messageList.count >= 2 {
-                messagesCollectionView.reloadItems(at: [lastIndexPath])
-            }
-        }, completion: { [weak self] _ in
-            if self?.isLastSectionVisible() == true {
-                self?.messagesCollectionView.scrollToLastItem(animated: true)
-            }
-        })
-    }
-    
-    func isLastSectionVisible() -> Bool {
-        guard !messageList.isEmpty else { return false }
-        return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
-    }
-    
-    private let formatter: DateFormatter = {
-        let formatter = DateFormatter()
-        formatter.dateStyle = .medium
-        return formatter
-    }()
-}
-
-
-// MARK: MessagesDataSource
-extension TSChatViewController {
-    
-    var currentSender: SenderType {
-        return viewModel.kUserSender
-    }
-    
-    func numberOfSections(in _: MessagesCollectionView) -> Int {
-        return 1
-    }
-    
-    func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int{
-        messageList.count
-    }
-    
-    func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
-        messageList[indexPath.item]
-    }
-    
-    func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-//        if indexPath.item % 3 == 0 {
-//            return NSAttributedString(
-//                string: MessageKitDateFormatter.shared.string(from: message.sentDate),
-//                attributes: [
-//                    NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
-//                    NSAttributedString.Key.foregroundColor: UIColor.darkGray,
-//                ])
-//        }
-        return nil
-    }
-    
-    func cellBottomLabelAttributedText(for _: MessageType, at _: IndexPath) -> NSAttributedString? {
-        NSAttributedString(
-            string: "Read",
-            attributes: [
-                NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
-                NSAttributedString.Key.foregroundColor: UIColor.darkGray,
-            ])
-    }
-    
-    func messageTopLabelAttributedText(for message: MessageType, at _: IndexPath) -> NSAttributedString? {
-        let name = message.sender.displayName
-        return NSAttributedString(
-            string: name,
-            attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
-    }
-    
-    func messageBottomLabelAttributedText(for message: MessageType, at _: IndexPath) -> NSAttributedString? {
-        let dateString = formatter.string(from: message.sentDate)
-        return NSAttributedString(
-            string: dateString,
-            attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption2)])
-    }
-    
-    func textCell(
-        for message: MessageType,
-        at indexPath: IndexPath,
-        in messagesCollectionView: MessagesCollectionView)
-    -> UICollectionViewCell?
-    {
-        let cell = messagesCollectionView.dequeueReusableCell(
-            TSTextMessageContentCell.self,
-            for: indexPath)
-        cell.configure(
-            with: message,
-            at: indexPath,
-            in: messagesCollectionView,
-            dataSource: self,
-            and: textMessageSizeCalculator)
-        
-        return cell
-    }
-}
-
-
-// MARK: InputBarAccessoryViewDelegate
-
-extension TSChatViewController: InputBarAccessoryViewDelegate {
-    // MARK: Internal
-    
-    @objc
-    func inputBar(_: InputBarAccessoryView, didPressSendButtonWith _: String) {
-        processInputBar(messageInputBar)
-    }
-    
-    //聊天发送内容
-    func processInputBar(_ inputBar: InputBarAccessoryView) {
-        // Here we can parse for which substrings were autocompleted
-        let attributedText = inputBar.inputTextView.attributedText!
-        let range = NSRange(location: 0, length: attributedText.length)
-        attributedText.enumerateAttribute(.autocompleted, in: range, options: []) { _, range, _ in
-            
-            let substring = attributedText.attributedSubstring(from: range)
-            let context = substring.attribute(.autocompletedContext, at: 0, effectiveRange: nil)
-            print("Autocompleted: `", substring, "` with context: ", context ?? "-")
-        }
-        
-        let components = inputBar.inputTextView.components
-        inputBar.inputTextView.text = String()
-        inputBar.invalidatePlugins()
-        
-        inputBar.inputTextView.placeholder = "Aa"
-        sendMessages(components)
-        messagesCollectionView.scrollToLastItem(animated: true)
-    }
-    
-    // MARK: Private
-    
-    private func sendMessages(_ data: [Any]) {
-        let user = viewModel.kUserSender
-        for component in data {
-            if let str = component as? String {
-                let message = TSChatMessage(text: str, user: user, messageId: UUID().uuidString, date: Date())
-                insertMessage(message)
-                //保存这条消息到本地数据库
-                //发送消息后,进行AI 对话生成
-                generativeAIChat(message: message)
-            }
-        }
-    }
-    
-    func generativeAIChat(message:TSChatMessage) {
-        var messageString = ""
-        switch message.kind {
-        case .text(let message):
-            messageString = message
-        default:
-            break
-        }
-        
-        if messageString.count == 0 {
-            return
-        }
-        
-        let message = TSChatMessage(attributedText: kMDAttributedString(text: ""), user: viewModel.kAIUser, messageId: UUID().uuidString, date: Date())
-        message.sendState = .start
-        insertMessage(message)
-        
-        inputBarVC.sendEnabled(enabled: false)
-        viewModel.sendChatMessage(message: messageString) {[weak self] string in
-            guard let self = self else { return }
-            debugPrint("viewModel.AiMDString=\(viewModel.AiMDString)")
-            message.kind = .attributedText(kMDAttributedString(text: viewModel.AiMDString))
-            message.sendState = .progress(0.5)
-            updataAIChatCellUI()
-            
-        } completion: {[weak self] data, error in
-            guard let self = self else { return }
-            if let netData = data {
-                message.sendState = .success("netData")
-                //保存这条消息到本地数据库
-                //消耗一次 AI 次数
-                kPurchaseDefault.useOnceForFree(type: .aichat)
-                
-            }else {
-                message.kind = .attributedText(kMDAttributedString(text: kAIErrorString))
-                message.sendState = .failed(kAIErrorString)
-                //保存这条消息到本地数据库
-            }
-            updataAIChatCellUI()
-            
-            kExecuteOnMainThread {
-                self.inputBarVC.sendEnabled(enabled: true)
-            }
-        }
-    }
-    
-    func updataAIChatCellUI(){
-        kExecuteOnMainThread {
-            if self.messageList.count >= 2 {
-                UIView.performWithoutAnimation {
-                    self.messagesCollectionView.reloadItems(at: [self.lastIndexPath])
-                }
-            }else{
-                self.messagesCollectionView.reloadData()
-            }
-
-            //更新 Vip
-            if kPurchaseDefault.isVip == false{
-                self.updateVipView()
-            }
-            
-            self.messagesCollectionView.scrollToLastItem(animated: false)
-            
-        }
-    }
-    
-}
-
-extension TSChatViewController{
-    
-    func inputSendMsg(_ data: [Any]) {
-        
-        //判断 vip
-        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .aichat) == false, vc: self, closePageBlock: {[weak self] in
-            guard let self = self else { return }
-        }){ return }
-        
-        sendMessages(data)
-        messagesCollectionView.scrollToLastItem(animated: true)
-        view.endEditing(true)
-    }
-    
-    
-}
-
-extension TSChatViewController{
-    
-    // UICollectionViewDelegate 方法
-    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
-        let offsetY = scrollView.contentOffset.y
-        let contentHeight = scrollView.contentSize.height
-        let frameHeight = scrollView.frame.size.height
-        
-        // 判断是否需要显示滚动到底部的按钮
-        if offsetY > contentHeight - frameHeight - 400 {
-            scrollToBottomButton.isHidden = true
-        } else {
-            scrollToBottomButton.isHidden = false
-        }
-    }
-    
-    
-}

+ 81 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController+ChatDelegate.swift → AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController+ChatDelegate.swift

@@ -9,6 +9,87 @@ import MessageKit
 import UIKit
 import MapKit
 
+
+
+
+
+
+// MARK: MessagesDataSource
+extension TSChatViewController {
+    
+    var currentSender: SenderType {
+        return viewModel.kUserSender
+    }
+    
+    func numberOfSections(in _: MessagesCollectionView) -> Int {
+        return 1
+    }
+    
+    func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int{
+        messageList.count
+    }
+    
+    func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
+        messageList[indexPath.item]
+    }
+    
+    func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+//        if indexPath.item % 3 == 0 {
+//            return NSAttributedString(
+//                string: MessageKitDateFormatter.shared.string(from: message.sentDate),
+//                attributes: [
+//                    NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
+//                    NSAttributedString.Key.foregroundColor: UIColor.darkGray,
+//                ])
+//        }
+        return nil
+    }
+    
+    func cellBottomLabelAttributedText(for _: MessageType, at _: IndexPath) -> NSAttributedString? {
+        NSAttributedString(
+            string: "Read",
+            attributes: [
+                NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
+                NSAttributedString.Key.foregroundColor: UIColor.darkGray,
+            ])
+    }
+    
+    func messageTopLabelAttributedText(for message: MessageType, at _: IndexPath) -> NSAttributedString? {
+        let name = message.sender.displayName
+        return NSAttributedString(
+            string: name,
+            attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
+    }
+    
+    func messageBottomLabelAttributedText(for message: MessageType, at _: IndexPath) -> NSAttributedString? {
+        let dateString = formatter.string(from: message.sentDate)
+        return NSAttributedString(
+            string: dateString,
+            attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption2)])
+    }
+    
+    func textCell(
+        for message: MessageType,
+        at indexPath: IndexPath,
+        in messagesCollectionView: MessagesCollectionView)
+    -> UICollectionViewCell?
+    {
+        let cell = messagesCollectionView.dequeueReusableCell(
+            TSTextMessageContentCell.self,
+            for: indexPath)
+        cell.configure(
+            with: message,
+            at: indexPath,
+            in: messagesCollectionView,
+            dataSource: self,
+            and: textMessageSizeCalculator)
+        
+        return cell
+    }
+}
+
+
+
 // MARK: MessagesLayoutDelegate
 
 extension TSChatViewController: MessagesLayoutDelegate {

+ 153 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController+Keyboard.swift

@@ -0,0 +1,153 @@
+//
+//  Keyboard.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/17.
+//
+
+extension TSChatViewController {
+    
+    var creatInputBarVC: TSChatInputBarVC{
+        let inputBarVC = TSChatInputBarVC()
+        inputBarVC.sendComplete = { [weak self] components in
+            guard let self = self else { return }
+            inputSendMsg(components)
+        }
+        return inputBarVC
+    }
+    
+    
+    var creatInputBarBgView:UIView{
+        let inputBarBgView = UIView()
+        inputBarBgView.addShadow(shadowColor: "#111111".uiColor.cgColor, shadowOffset: CGSize(width: 0, height: -10), shadowRadius: 10, shadowOpacity: 1.0)
+        return inputBarBgView
+    }
+    
+    
+    
+    var creatScrollToBottomButton: UIButton{
+        let backBottomBtn = UIButton.createButton(image: UIImage(named: "down_arrow_line")) { [weak self]  in
+            guard let self = self else { return }
+            messagesCollectionView.scrollToLastItem(animated: false)
+        }
+        backBottomBtn.isHidden = true
+        backBottomBtn.backgroundColor = .popupColor
+        backBottomBtn.cornerRadius = 16.0
+        return backBottomBtn
+    }
+    
+    
+    
+}
+extension TSChatViewController{
+    
+    
+    // UICollectionViewDelegate 方法
+    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        let offsetY = scrollView.contentOffset.y
+        let contentHeight = scrollView.contentSize.height
+        let frameHeight = scrollView.frame.size.height
+        
+        // 判断是否需要显示滚动到底部的按钮
+        if offsetY > contentHeight - frameHeight - 400 {
+            scrollToBottomButton.isHidden = true
+        } else {
+            scrollToBottomButton.isHidden = false
+        }
+    }
+    
+    
+}
+
+
+
+extension TSChatViewController{
+    
+    
+    func configureMessageInputBar() {
+        inputBarBgView.addSubview(inputBarTopView)
+        inputBarTopView.snp.makeConstraints { make in
+            make.leading.equalTo(0)
+            make.trailing.equalTo(0)
+            make.top.equalTo(0)
+        }
+        
+        if viewModel.uiStyle == .chat {
+            inputBarBgView.addSubview(inputBarVC.view)
+            inputBarVC.view.snp.makeConstraints { make in
+                make.leading.equalTo(0)
+                make.trailing.equalTo(0)
+                make.top.equalTo(inputBarTopView.snp.bottom)
+                make.bottom.equalTo(0)
+            }
+        }
+        inputBarType = .custom(inputBarBgView)
+        
+        handleKeyBoard()
+        configureScrollToBottomButton()
+    }
+    
+    
+    func handleKeyBoard(){
+        if viewModel.uiStyle == .chat {
+            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickView))
+            tapGesture.cancelsTouchesInView = false
+            messagesCollectionView.addGestureRecognizer(tapGesture)
+            
+            // 监听键盘事件
+            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
+            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
+        }
+    }
+    
+    
+    func configureScrollToBottomButton() {
+        view.addSubview(scrollToBottomButton)
+        scrollToBottomButton.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.bottom.equalTo(inputContainerView.snp.top).offset(-14)
+            make.width.height.equalTo(32)
+        }
+        self.scrollViewDidScroll(self.messagesCollectionView)
+        
+
+    }
+    
+    @objc func clickView(){
+        view.endEditing(true)
+    }
+    
+
+    @objc func keyboardWillShow(_ notification: Notification) {
+
+        guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
+              let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else {
+            return
+        }
+
+        let keyboardHeight = keyboardFrame.height
+        let contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
+
+        UIView.animate(withDuration: animationDuration) {
+            self.messagesCollectionView.contentInset = contentInset
+            self.messagesCollectionView.scrollIndicatorInsets = contentInset
+        }
+        
+        kDelayMainShort {
+            self.messagesCollectionView.scrollToLastItem(animated: false)
+        }
+    }
+
+    @objc func keyboardWillHide(_ notification: Notification) {
+        guard let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else {
+            return
+        }
+
+        let contentInset = UIEdgeInsets.zero
+        UIView.animate(withDuration: animationDuration) {
+            self.messagesCollectionView.contentInset = contentInset
+            self.messagesCollectionView.scrollIndicatorInsets = contentInset
+        }
+    }
+    
+}

+ 70 - 34
AIEmoji/Business/AIChat/TSAIChatContainerVC.swift → AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController+NaviBar.swift

@@ -1,18 +1,24 @@
 //
-//  TSAIChatVC.swift
+//  TSChatViewController+Nav.swift
 //  AIEmoji
 //
-//  Created by 100Years on 2025/2/9.
+//  Created by 100Years on 2025/2/17.
 //
 
-class TSAIChatContainer111VC111: TSBaseVC {
-    
+extension TSChatViewController {
     
+   var creatNavBarContentView: UIView{
+        let view = UIView()
+        view.backgroundColor = .clear
+        return view
+    }
     
-    var deleteBlock:(()->Void)?
+    var creatNormalNavBarView: TSNormalNavigationBarView{
+        let view = TSNormalNavigationBarView()
+        return view
+    }
     
-
-    lazy var vipBtn: UIButton = {
+     var creatVipBtn: UIButton{
         let vipBtn = UIButton.createButton(image: UIImage(named: "nav_vip")) { [weak self]  in
             guard let self = self else { return }
             TSPurchaseVC.show(target: self) {
@@ -20,9 +26,9 @@ class TSAIChatContainer111VC111: TSBaseVC {
             }
         }
         return vipBtn
-    }()
+    }
     
-    lazy var navBarView: TSBaseNavContentBarView = {
+     var creatNavBarView: TSBaseNavContentBarView{
         let navBarView = TSBaseNavContentBarView()
         
         let titleImageView = UIImageView.createImageView(imageName: "nav_title_aichat",contentMode: .scaleToFill)
@@ -66,25 +72,65 @@ class TSAIChatContainer111VC111: TSBaseVC {
         }
         
         return navBarView
-    }()
+    }
     
+    public func addNormalNavBarView(){
+        navBarContentView.addSubview(normalNavBarView)
+        normalNavBarView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+    }
     
-    lazy var viewModel : TSAIChatVM = {
-        let viewModel = TSAIChatVM()
-        return viewModel
-    }()
+    public func setTitleText(_ title: String) {
+       _ = normalNavBarView.setTitleName(NSLocalizedString(title, comment: ""))
+    }
+
+    public func setPageTitle(_ title: String) {
+        let pageTitle = title
+        let backTitle = " "
+        setTitleText(pageTitle)
+        _ = setNavigationItem(backTitle, imageName: "navi_back_white", direction: .left, action: #selector(navBarClickLeftAction))
+    }
+    @objc public func navBarClickLeftAction() {
+        debugPrint("navBarClickLeftAction -> \(type(of: self))")
+        pop()
+    }
     
+    public func pop() {
+        if navigationController == nil {
+            dismiss(animated: true, completion: nil)
+        } else if navigationController?.presentingViewController != nil, navigationController?.viewControllers.count == 1 {
+            navigationController?.dismiss(animated: true, completion: nil)
+        } else {
+            navigationController?.popViewController(animated: true)
+        }
+    }
     
-    lazy var chatVC: TSChatViewController = {
-        let chatVC = TSChatViewController()
-        chatVC.viewModel = viewModel
-        return chatVC
-    }()
+    public func setNavigationItem(_ name: String, imageName: String, direction: NSTextAlignment, action: Selector) -> UIButton {
+        if direction == .left {
+            return normalNavBarView.setLeftNavigationItem(name: name, imageName: imageName, target: self, action: action)
+        } else {
+            return normalNavBarView.setRightNavigationItem(name: name, imageName: imageName, target: self, action: action)
+        }
+    }
     
     
-    override func createView() {
-//        edgesForExtendedLayout = []
+}
+
+ 
+extension TSChatViewController {
     
+    func configureNaviBarView(){
+        
+        navigationItem.title = "MessageKit"
+        edgesForExtendedLayout = [.top]
+        
+        view.addSubview(navBarContentView)
+        navBarContentView.snp.makeConstraints { make in
+            make.leading.top.trailing.equalToSuperview()
+            make.height.equalTo(k_Nav_Height)
+        }
+        
         if viewModel.uiStyle == .history {
             addNormalNavBarView()
             setPageTitle("History".localized)
@@ -95,19 +141,9 @@ class TSAIChatContainer111VC111: TSBaseVC {
                 make.edges.equalToSuperview()
             }
         }
-//        setNavBarViewHidden(true)
-        addChild(chatVC)
-        contentView.addSubview(chatVC.view)
-        
-        chatVC.view.snp.makeConstraints { make in
-            make.bottom.leading.trailing.equalTo(0)
-            make.top.equalTo(-k_Nav_Height)
-//            make.top.equalTo(0)
-        }
-        
+
     }
     
-    
     @objc func clickDelete(){
         showCustomAlert(message: "Are you sure to delete".localized, deleteHandler:  { [weak self]  in
             guard let self = self else { return }
@@ -116,7 +152,7 @@ class TSAIChatContainer111VC111: TSBaseVC {
             deleteBlock?()
             pop()
         })
-        
-        
     }
+
+    
 }

+ 102 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController+SendMsg.swift

@@ -0,0 +1,102 @@
+//
+//  TSChatViewController+SendMsg.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/17.
+//
+
+extension TSChatViewController {
+    
+    func inputSendMsg(_ data: [Any]) {
+        
+        //判断 vip
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .aichat) == false, vc: self, closePageBlock: {[weak self] in
+            guard let self = self else { return }
+        }){ return }
+        
+        sendMessages(data)
+        messagesCollectionView.scrollToLastItem(animated: true)
+        view.endEditing(true)
+    }
+    
+    
+    private func sendMessages(_ data: [Any]) {
+        let user = viewModel.kUserSender
+        for component in data {
+            if let str = component as? String {
+                let message = TSChatMessage(text: str, user: user, messageId: UUID().uuidString, date: Date())
+                insertMessage(message)
+                //保存这条消息到本地数据库
+                //发送消息后,进行AI 对话生成
+                generativeAIChat(message: message)
+            }
+        }
+    }
+    
+    func generativeAIChat(message:TSChatMessage) {
+        var messageString = ""
+        switch message.kind {
+        case .text(let message):
+            messageString = message
+        default:
+            break
+        }
+        
+        if messageString.count == 0 {
+            return
+        }
+        
+        let message = TSChatMessage(attributedText: kMDAttributedString(text: ""), user: viewModel.kAIUser, messageId: UUID().uuidString, date: Date())
+        message.sendState = .start
+        insertMessage(message)
+        
+        inputBarVC.sendEnabled(enabled: false)
+        viewModel.sendChatMessage(message: messageString) {[weak self] string in
+            guard let self = self else { return }
+            debugPrint("viewModel.AiMDString=\(viewModel.AiMDString)")
+            message.kind = .attributedText(kMDAttributedString(text: viewModel.AiMDString))
+            message.sendState = .progress(0.5)
+            updataAIChatCellUI()
+            
+        } completion: {[weak self] data, error in
+            guard let self = self else { return }
+            if let netData = data {
+                message.sendState = .success("netData")
+                //保存这条消息到本地数据库
+                //消耗一次 AI 次数
+                kPurchaseDefault.useOnceForFree(type: .aichat)
+                
+            }else {
+                message.kind = .attributedText(kMDAttributedString(text: kAIErrorString))
+                message.sendState = .failed(kAIErrorString)
+                //保存这条消息到本地数据库
+            }
+            updataAIChatCellUI()
+            
+            kExecuteOnMainThread {
+                self.inputBarVC.sendEnabled(enabled: true)
+            }
+        }
+    }
+    
+    func updataAIChatCellUI(){
+        kExecuteOnMainThread {
+            if self.messageList.count >= 2 {
+                UIView.performWithoutAnimation {
+                    self.messagesCollectionView.reloadItems(at: [self.lastIndexPath])
+                }
+            }else{
+                self.messagesCollectionView.reloadData()
+            }
+
+            //更新 Vip
+            if kPurchaseDefault.isVip == false{
+                self.updateVipView()
+            }
+            
+            self.messagesCollectionView.scrollToLastItem(animated: false)
+            
+        }
+    }
+    
+}

+ 100 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController+VipView.swift

@@ -0,0 +1,100 @@
+//
+//  TSChatViewController+View.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/17.
+//
+
+extension TSChatViewController {
+
+    var creatFreeText: UILabel{
+        let textLabel = UILabel.createLabel(
+            text: "Remaining \(kPurchaseDefault.freeNum(type: .aichat)) free times",
+            font: .font(size: 12),
+            textColor: "#E83E3E".uiColor,
+            textAlignment: .center,
+            numberOfLines: 0
+        )
+        textLabel.isHidden = false
+        return textLabel
+    }
+    
+    
+    var creatUpgradeVipBg: UIView{
+        let upgradeVipBg = UIView()
+        
+        let imageView = UIImageView.createImageView(imageName: "vip_upgrade_bg",contentMode: .scaleToFill)
+        upgradeVipBg.addSubview(imageView)
+        imageView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        let label = UILabel.createLabel(
+            text:"Free usage limit reached. Upgrade for unlimited chats.".localized,
+            font: .font(size: 14,weight: .bold),
+            textColor: "#111111".uiColor,
+            numberOfLines: 0
+        )
+        upgradeVipBg.addSubview(label)
+        label.snp.makeConstraints { make in
+            make.leading.equalTo(16)
+            make.top.equalTo(8)
+            make.bottom.equalTo(-8)
+            make.trailing.equalTo(-95)
+        }
+        
+        let upgradeBtn = UIButton.createButton(
+            title: "Upgrade".localized,
+            backgroundColor: "#111111".uiColor,
+            font:.font(size: 12,weight: .medium),
+            titleColor:.white,
+            corner: 14) { [weak self]  in
+                guard let self = self else { return }
+                TSPurchaseVC.show(target: self) { [weak self]  in
+//                    guard let self = self else { return }
+//                    updateVipView()
+                }
+            }
+        upgradeVipBg.addSubview(upgradeBtn)
+        upgradeBtn.snp.makeConstraints { make in
+            make.trailing.equalTo(-12)
+            make.centerY.equalToSuperview()
+            make.width.equalTo(70)
+            make.height.equalTo(28)
+        }
+        return upgradeVipBg
+    }
+    
+}
+
+extension TSChatViewController{
+    
+    func updateVipView() {
+        inputBarTopView.subviews.forEach { $0.removeFromSuperview()}
+        
+        if viewModel.uiStyle == .chat ,
+           kPurchaseDefault.isVip == false
+        {
+            let freeNum = kPurchaseDefault.freeNum(type: .aichat)
+            if freeNum > 0 {
+                freeText.text = "Remaining \(freeNum) free times"
+                inputBarTopView.addSubview(freeText)
+                freeText.snp.makeConstraints { make in
+                    make.leading.equalTo(20)
+                    make.trailing.equalTo(-20)
+                    make.bottom.equalTo(-8)
+                    make.top.equalTo(8)
+                }
+            }else{
+                inputBarTopView.addSubview(upgradeVipBg)
+                upgradeVipBg.snp.makeConstraints { make in
+                    make.leading.equalTo(16)
+                    make.trailing.equalTo(-16)
+                    make.bottom.equalTo(-2)
+                    make.top.equalTo(14)
+                }
+            }
+        }
+    }
+
+}

+ 177 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController.swift

@@ -0,0 +1,177 @@
+//
+// MIT License
+//
+// Copyright (c) 2017-2020 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import MessageKit
+import UIKit
+
+class TSChatViewController: MessagesViewController, MessagesDataSource {
+
+    var deleteBlock:(()->Void)?
+    //数据
+    var viewModel:TSAIChatVM = TSAIChatVM()
+    lazy var messageList: [TSChatMessage] = []
+    
+    //导航栏
+    lazy var vipBtn: UIButton = creatVipBtn
+    lazy var navBarView: TSBaseNavContentBarView = creatNavBarView
+    lazy var navBarContentView: UIView = creatNavBarContentView
+    lazy var normalNavBarView: TSNormalNavigationBarView = creatNormalNavBarView
+
+    //collectionView 布局
+    lazy var textMessageSizeCalculator = TSTextLayoutSizeCalculator(layout:self.messagesCollectionView.messagesCollectionViewFlowLayout)
+    
+    //键盘
+    lazy var inputBarVC: TSChatInputBarVC = creatInputBarVC
+    lazy var inputBarBgView:UIView = creatInputBarBgView
+    let inputBarTopView:UIView = UIView()
+    lazy var scrollToBottomButton: UIButton = creatScrollToBottomButton
+
+    // vip 相关
+    lazy var freeText: UILabel = creatFreeText
+    lazy var upgradeVipBg: UIView = creatUpgradeVipBg
+
+    
+    let formatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateStyle = .medium
+        return formatter
+    }()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+       
+        configureNaviBarView()
+        configureMessageCollectionView()
+        configureMessageInputBar()
+        loadFirstMessages()
+        updateVipView()
+        dealThings()
+    }
+    
+    
+    func dealThings(){
+        
+        vipBtn.isHidden = PurchaseManager.default.isVip
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+        
+        if viewModel.uiStyle == .chat {
+            // 注册通知监听,App死的时候,保存本次聊天记录到本地
+            NotificationCenter.default.addObserver(self, selector: #selector(saveChatList), name: .kApplicationWillTerminate, object: nil)
+        }
+    }
+    
+    @objc func vipInfoChanged() {
+        kExecuteOnMainThread {
+            self.vipBtn.isHidden = PurchaseManager.default.isVip
+            self.updateVipView()
+        }
+    }
+
+    @objc func saveChatList() {
+        messageList.remove(at: 0)
+        viewModel.updateMessages(msgModels: messageList)
+    }
+    
+    func loadFirstMessages() {
+        self.messageList = viewModel.getHistoryChatMessage()
+        self.messagesCollectionView.reloadData()
+        self.messagesCollectionView.scrollToLastItem(animated: false)
+    }
+
+    func configureMessageCollectionView() {
+        clearAndResetConstraints()
+        view.backgroundColor = .mainBg
+        //设置自定义FlowLayout,itemsize等,都在这里控制
+        let flowLayout = CustomMessagesFlowLayout()
+        flowLayout.sectionInset = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)
+    
+        messagesCollectionView.collectionViewLayout = flowLayout
+        messagesCollectionView.backgroundColor = .clear
+        messagesCollectionView.register(TSTextMessageContentCell.self)
+        messagesCollectionView.messagesLayoutDelegate = self
+        messagesCollectionView.messagesDisplayDelegate = self
+        messagesCollectionView.messagesDataSource = self
+        messagesCollectionView.messageCellDelegate = self
+        messagesCollectionView.clipsToBounds = true
+        scrollsToLastItemOnKeyboardBeginsEditing = true // default false
+        maintainPositionOnInputBarHeightChanged = true // default false
+        showMessageTimestampOnSwipeLeft = false // default false
+        //        messagesCollectionView.refreshControl = refreshControl
+        messagesCollectionView.reloadData()
+    }
+    
+    
+    func clearAndResetConstraints() {
+        // 筛选出与 messagesCollectionView 相关的约束
+        let constraintsToRemove = view.constraints.filter { constraint in
+            return (constraint.firstItem as? UIView == messagesCollectionView) || (constraint.secondItem as? UIView == messagesCollectionView)
+        }
+        // 停用并移除这些约束
+        NSLayoutConstraint.deactivate(constraintsToRemove)
+        for constraint in constraintsToRemove {
+            view.removeConstraint(constraint)
+        }
+
+        messagesCollectionView.snp.remakeConstraints { make in
+            make.leading.trailing.bottom.equalTo(0)
+            make.top.equalTo(k_Nav_Height)
+        }
+    }
+
+
+    // MARK: - Helpers
+    var lastIndexPath:IndexPath{
+        if messageList.count == 0 {
+            return IndexPath(item: 0, section: 0)
+        }
+        return IndexPath(item: messageList.count - 1, section: 0)
+    }
+    
+    func insertMessage(_ message: TSChatMessage) {
+        messageList.append(message)
+        messagesCollectionView.performBatchUpdates({
+            messagesCollectionView.insertItems(at: [lastIndexPath])
+            if messageList.count >= 2 {
+                messagesCollectionView.reloadItems(at: [lastIndexPath])
+            }
+        }, completion: { [weak self] _ in
+            if self?.isLastSectionVisible() == true {
+                self?.messagesCollectionView.scrollToLastItem(animated: true)
+            }
+        })
+    }
+    
+    
+    func isLastSectionVisible() -> Bool {
+        guard !messageList.isEmpty else { return false }
+        return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
+    }
+    
+    
+    override func viewWillAppear(_ animated: Bool) {
+        
+    }
+
+}
+
+