فهرست منبع

feat: 接入腾讯表情包

陈文艺 3 ماه پیش
والد
کامیت
1f69a0d098
100فایلهای تغییر یافته به همراه1725 افزوده شده و 921 حذف شده
  1. 17 4
      Lanu.xcodeproj/project.pbxproj
  2. 21 0
      Lanu/Assets.xcassets/IM/ic_im_chat_emoji_delete.imageset/Contents.json
  3. BIN
      Lanu/Assets.xcassets/IM/ic_im_chat_emoji_delete.imageset/ic_im_chat_emoji_delete@2x.png
  4. 22 0
      Lanu/Assets.xcassets/IM/ic_im_chat_keyboard.imageset/Contents.json
  5. BIN
      Lanu/Assets.xcassets/IM/ic_im_chat_keyboard.imageset/ic_im_chat_keyboard@2x.png
  6. BIN
      Lanu/Assets.xcassets/IM/ic_im_chat_keyboard.imageset/ic_im_chat_keyboard@3x.png
  7. 12 2
      Lanu/Common/Config/LNAppConfig.swift
  8. 1 0
      Lanu/Common/Extension/String+Extension.swift
  9. 2 0
      Lanu/Common/Storage/LNUserDefaultsKey.swift
  10. 1 1
      Lanu/Common/Views/Base/LNNavigationController.swift
  11. 6 2
      Lanu/Common/Views/LNPopupView.swift
  12. 19 7
      Lanu/Common/Voice/LNVoiceRecorder.swift
  13. 2 2
      Lanu/Lanu-Bridging-Header.h
  14. 31 0
      Lanu/Manager/IM/Emoji/LNEmojiData.swift
  15. 118 0
      Lanu/Manager/IM/Emoji/LNIMEmojiManager.swift
  16. 117 0
      Lanu/Manager/IM/Emoji/String+TUIEmoji.swift
  17. 17 4
      Lanu/Manager/IM/LNIMManager.swift
  18. 3 0
      Lanu/Manager/IM/LNIMMessageData.swift
  19. 47 0
      Lanu/Manager/IM/TUIUtils/String+TUILocalized.swift
  20. 6 10
      Lanu/Manager/IM/TUIUtils/V2TIMConversation+Extension.swift
  21. 43 43
      Lanu/Manager/IM/TUIUtils/V2TIMMessage+Extension.swift
  22. 2 0
      Lanu/Manager/LNEventDeliver.swift
  23. 6 10
      Lanu/SceneDelegate.swift
  24. 1 1
      Lanu/Views/IM/Chat/Cells/LNIMChatTextMessageCell.swift
  25. 22 0
      Lanu/Views/IM/Chat/Emoji/LNIMChatEmojiAttachment.swift
  26. 50 0
      Lanu/Views/IM/Chat/Emoji/LNIMChatEmojiCell.swift
  27. 179 0
      Lanu/Views/IM/Chat/Emoji/LNIMChatEmojiPanel.swift
  28. 4 0
      Lanu/Views/IM/Chat/InputMenu/LNIMChatInputMenuView.swift
  29. 135 32
      Lanu/Views/IM/Chat/InputMenu/LNIMChatTextInputView.swift
  30. 14 7
      Lanu/Views/IM/Chat/InputMenu/LNIMChatVoiceInputView.swift
  31. 2 2
      Lanu/Views/IM/Chat/LNIMChatViewController.swift
  32. 2 2
      Lanu/Views/Login/LNLoginPanel.swift
  33. 1 1
      Lanu/Views/Main/LNMainViewController.swift
  34. 1 1
      Podfile
  35. 11 13
      Podfile.lock
  36. 0 30
      ThirdParty/TUIKit/TIMCommon/BaseCellData/NSString+TUIEmoji.h
  37. 0 55
      ThirdParty/TUIKit/TIMCommon/BaseCellData/NSString+TUIEmoji.m
  38. 0 27
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonMediator.h
  39. 0 41
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonMediator.m
  40. 0 102
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonModel.h
  41. 0 39
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonModel.m
  42. 0 42
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMConfig.h
  43. 0 55
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMConfig.m
  44. 0 16
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMDefine.h
  45. 0 51
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMRTLUtil.h
  46. 0 291
      ThirdParty/TUIKit/TIMCommon/CommonModel/TIMRTLUtil.m
  47. 0 27
      ThirdParty/TUIKit/TIMCommon/CommonModel/TUIEmojiMeditorProtocol.h
  48. 6 1
      ThirdParty/TUIKit/TIMCommon/TIMCommon.podspec
  49. 24 0
      ThirdParty/TUIKit/TUIChat/Resources/PrivacyInfo.xcprivacy
  50. 62 0
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/ar.lproj/Localizable.strings
  51. 62 0
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/en.lproj/Localizable.strings
  52. 62 0
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/zh-Hans.lproj/Localizable.strings
  53. 62 0
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/zh-Hant.lproj/Localizable.strings
  54. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/del_normal@2x.png
  55. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/del_pressed@2x.png
  56. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoj_normal@2x.png
  57. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoj_pressed@2x.png
  58. 462 0
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji.plist
  59. 70 0
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emojiRecentDefaultList.plist
  60. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_0@2x.png
  61. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_10@2x.png
  62. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_11@2x.png
  63. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_12@2x.png
  64. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_13@2x.png
  65. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_14@2x.png
  66. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_15@2x.png
  67. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_16@2x.png
  68. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_17@2x.png
  69. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_18@2x.png
  70. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_19@2x.png
  71. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_1@2x.png
  72. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_20@2x.png
  73. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_21@2x.png
  74. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_22@2x.png
  75. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_23@2x.png
  76. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_24@2x.png
  77. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_25@2x.png
  78. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_26@2x.png
  79. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_27@2x.png
  80. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_28@2x.png
  81. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_29@2x.png
  82. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_2@2x.png
  83. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_30@2x.png
  84. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_31@2x.png
  85. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_32@2x.png
  86. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_33@2x.png
  87. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_34@2x.png
  88. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_35@2x.png
  89. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_36@2x.png
  90. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_37@2x.png
  91. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_38@2x.png
  92. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_39@2x.png
  93. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_3@2x.png
  94. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_40@2x.png
  95. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_41@2x.png
  96. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_42@2x.png
  97. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_43@2x.png
  98. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_44@2x.png
  99. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_45@2x.png
  100. BIN
      ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_46@2x.png

+ 17 - 4
Lanu.xcodeproj/project.pbxproj

@@ -93,11 +93,15 @@
 				Manager/GameMate/LNGameMateManager.swift,
 				Manager/GameMate/Network/LNGameMateResponse.swift,
 				"Manager/GameMate/Network/LNHttpManager+GameMate.swift",
+				Manager/IM/Emoji/LNEmojiData.swift,
+				Manager/IM/Emoji/LNIMEmojiManager.swift,
+				"Manager/IM/Emoji/String+TUIEmoji.swift",
 				Manager/IM/GenerateTestUserSig.m,
 				Manager/IM/LNIMManager.swift,
 				Manager/IM/LNIMMessageData.swift,
-				"Manager/IM/V2TIMConversation+Extension.swift",
-				"Manager/IM/V2TIMMessage+Extension.swift",
+				"Manager/IM/TUIUtils/String+TUILocalized.swift",
+				"Manager/IM/TUIUtils/V2TIMConversation+Extension.swift",
+				"Manager/IM/TUIUtils/V2TIMMessage+Extension.swift",
 				Manager/LNDelayTask.swift,
 				Manager/LNEventDeliver.swift,
 				Manager/Network/Download/LNFileDownloader.swift,
@@ -153,6 +157,9 @@
 				Views/IM/Chat/Cells/LNIMChatTextMessageCell.swift,
 				Views/IM/Chat/Cells/LNIMChatUnknownMessageCell.swift,
 				Views/IM/Chat/Cells/LNIMChatVoiceMessageCell.swift,
+				Views/IM/Chat/Emoji/LNIMChatEmojiAttachment.swift,
+				Views/IM/Chat/Emoji/LNIMChatEmojiCell.swift,
+				Views/IM/Chat/Emoji/LNIMChatEmojiPanel.swift,
 				Views/IM/Chat/GameMate/LNIMChatGameMateSkillCell.swift,
 				Views/IM/Chat/GameMate/LNIMChatGameMateSkillView.swift,
 				Views/IM/Chat/InputMenu/LNIMChatInputMenuView.swift,
@@ -224,8 +231,6 @@
 		};
 		FBB67E232EC48B440070E686 /* ThirdParty */ = {
 			isa = PBXFileSystemSynchronizedRootGroup;
-			exceptions = (
-			);
 			path = ThirdParty;
 			sourceTree = "<group>";
 		};
@@ -376,10 +381,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-resources-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Copy Pods Resources";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-resources-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-resources.sh\"\n";
@@ -415,10 +424,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-frameworks.sh\"\n";

+ 21 - 0
Lanu/Assets.xcassets/IM/ic_im_chat_emoji_delete.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "ic_im_chat_emoji_delete@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Lanu/Assets.xcassets/IM/ic_im_chat_emoji_delete.imageset/ic_im_chat_emoji_delete@2x.png


+ 22 - 0
Lanu/Assets.xcassets/IM/ic_im_chat_keyboard.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "ic_im_chat_keyboard@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "ic_im_chat_keyboard@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Lanu/Assets.xcassets/IM/ic_im_chat_keyboard.imageset/ic_im_chat_keyboard@2x.png


BIN
Lanu/Assets.xcassets/IM/ic_im_chat_keyboard.imageset/ic_im_chat_keyboard@3x.png


+ 12 - 2
Lanu/Common/Config/LNAppConfig.swift

@@ -46,17 +46,18 @@ enum LNAppLanguage: Codable {
 class LNAppConfig {
     static var shared = LNAppConfig()
     
-    @Published
     var curEnv: LNAppEnvType = LNUserDefaults[.appEnv, .official] {
         didSet {
             LNUserDefaults[.appEnv] = curEnv
         }
     }
     
-    @Published
     var curLang: LNAppLanguage = LNUserDefaults[.appLanguage, .english] {
         didSet {
             LNUserDefaults[.appLanguage] = curLang
+            if oldValue != curLang {
+                notifyLanguageChanged()
+            }
         }
     }
     
@@ -75,3 +76,12 @@ class LNAppConfig {
         languageBundleMap[curLang] ?? Bundle.main
     }
 }
+
+extension LNAppConfig {
+    private func notifyLanguageChanged() {
+        let curLang = self.curLang
+        LNEventDeliver.notifyEvent {
+            ($0 as? LNAppMainEvent)?.onAppLanguageChanged(newLanguage: curLang)
+        }
+    }
+}

+ 1 - 0
Lanu/Common/Extension/String+Extension.swift

@@ -66,6 +66,7 @@ extension String {
 }
 
 extension String {
+    // 修复 UUID 变化后的路径
     var fixedFilePath: String? {
         let currentAppRootPath = URL.rootDir.path
         

+ 2 - 0
Lanu/Common/Storage/LNUserDefaultsKey.swift

@@ -17,4 +17,6 @@ enum LNUserDefaultsKey: String {
     case voiceCache
     
     case userSearchHistory
+    
+    case imRecentEmojis
 }

+ 1 - 1
Lanu/Common/Views/Base/LNNavigationController.swift

@@ -61,7 +61,7 @@ extension LNNavigationController {
     }
     
     private func showLoginPanel() {
-        LNLoginPanel.show()
+        LNLoginPanel.show(container: view)
     }
 }
 

+ 6 - 2
Lanu/Common/Views/LNPopupView.swift

@@ -28,10 +28,14 @@ class LNPopupView: UIView {
     func onWillPopup() {}
     
     func showIn(_ targetView: UIView? = nil) {
-        if let view = targetView?.viewController?.view {
+        if let window = targetView as? UIWindow {
+            window.addSubview(self)
+        } else if let view = targetView?.viewController?.view {
             view.addSubview(self)
+        } else if let window = UIView.appKeyWindow {
+            window.addSubview(self)
         } else {
-            UIView.appKeyWindow?.addSubview(self)
+            return
         }
         snp.makeConstraints { make in
             make.directionalEdges.equalToSuperview()

+ 19 - 7
Lanu/Common/Voice/LNVoiceRecorder.swift

@@ -21,7 +21,8 @@ protocol LNVoiceRecorderNotify {
     func onRecordTaskDurationChanged(taskId: String, duration: Double, volumeRatio: Double)
     func onRecordTaskRecording(taskId: String)
     func onRecordTaskPause(taskId: String)
-    func onRecordTaskStop(taskId: String, fileUrl: URL?, duration: Double)
+    func onRecordTaskStop(taskId: String)
+    func onRecordTaskReachMaxDuration(taskId: String, fileUrl: URL?, duration: Double)
 }
 
 
@@ -119,7 +120,8 @@ class LNVoiceRecorder {
         notifyTaskStart()
     }
     
-    func stopRecord() {
+    @discardableResult
+    func stopRecord() -> (URL?, Double) {
         recorder?.stop()
         try? AVAudioSession.sharedInstance().setActive(false)
         stopDurationTimer()
@@ -133,9 +135,11 @@ class LNVoiceRecorder {
                 filePath = outputPath
             } catch { }
         }
-        notifyTaskStop(filePath: filePath, duration: curDuration)
+        notifyTaskStop()
         
         curTaskId = ""
+        
+        return (filePath, curDuration)
     }
 }
 
@@ -192,8 +196,9 @@ extension LNVoiceRecorder {
             notifyTaskProgress(duration: curDuration, volumeRatio: volumeRatio)
             
             if maxDuration != -1,
-               maxDuration >= curDuration {
-                stopRecord()
+               maxDuration <= curDuration {
+                let result = stopRecord()
+                notifyTaskReachMaxDuration(file: result.0, duration: result.1)
             }
         })
     }
@@ -212,10 +217,10 @@ extension LNVoiceRecorder {
         }
     }
     
-    private func notifyTaskStop(filePath: URL?, duration: Double) {
+    private func notifyTaskStop() {
         let taskId = curTaskId
         LNEventDeliver.notifyEvent {
-            ($0 as? LNVoiceRecorderNotify)?.onRecordTaskStop(taskId: taskId, fileUrl: filePath, duration: duration)
+            ($0 as? LNVoiceRecorderNotify)?.onRecordTaskStop(taskId: taskId)
         }
     }
     
@@ -232,4 +237,11 @@ extension LNVoiceRecorder {
             ($0 as? LNVoiceRecorderNotify)?.onRecordTaskPause(taskId: taskId)
         }
     }
+    
+    private func notifyTaskReachMaxDuration(file: URL?, duration: Double) {
+        let taskId = curTaskId
+        LNEventDeliver.notifyEvent {
+            ($0 as? LNVoiceRecorderNotify)?.onRecordTaskReachMaxDuration(taskId: taskId, fileUrl: file, duration: duration)
+        }
+    }
 }

+ 2 - 2
Lanu/Lanu-Bridging-Header.h

@@ -3,10 +3,10 @@
 //
 
 
-#import "TUICore/TUILogin.h"
 #import "TIMPush/TIMPush.h"
 
-#import <TIMCommon/TIMDefine.h>
 #import <SDWebImage/SDWebImage.h>
 
+@import ImSDK_Plus;
+
 #import "GenerateTestUserSig.h"

+ 31 - 0
Lanu/Manager/IM/Emoji/LNEmojiData.swift

@@ -0,0 +1,31 @@
+//
+//  LNEmojiData.swift
+//  Lanu
+//
+//  Created by OneeChan on 2025/12/18.
+//
+
+import Foundation
+
+
+class LNEmojiData {
+    var name: String = ""
+    var localizableName: String = ""
+    var path: String = ""
+}
+
+class LNEmojiGroup {
+    var emojis: [LNEmojiData] = []
+    var groupName: String = ""
+    
+    private var emojiMap: [String: LNEmojiData] = [:]
+    
+    func emojiFor(_ name: String) -> LNEmojiData? {
+        if emojiMap.count != emojis.count {
+            emojiMap = emojis.reduce(into: [String: LNEmojiData](), { partialResult, emoji in
+                partialResult[emoji.name] = emoji
+            })
+        }
+        return emojiMap[name]
+    }
+}

+ 118 - 0
Lanu/Manager/IM/Emoji/LNIMEmojiManager.swift

@@ -0,0 +1,118 @@
+//
+//  LNIMEmojiManager.swift
+//  Lanu
+//
+//  Created by OneeChan on 2025/12/18.
+//
+
+import Foundation
+
+
+private class LNEmojiConfig: Codable {
+    var face_name: String = ""
+    var face_file: String = ""
+    var face_id: String = ""
+}
+
+
+class LNIMEmojiManager {
+    static let shared = LNIMEmojiManager()
+    private let resourcePath = URL(fileURLWithPath: Bundle.main.path(forResource: "TUIChatFace", ofType: "bundle")!)
+    private var recentEmoji: [String] = [] {
+        didSet {
+            LNUserDefaults[.imRecentEmojis] = recentEmoji
+        }
+    }
+    
+    private(set) var emojiGroup: LNEmojiGroup?
+    var recentGroup: LNEmojiGroup? {
+        guard !recentEmoji.isEmpty else { return nil }
+        let emojis = recentEmoji.compactMap {
+            emojiGroup?.emojiFor($0)
+        }
+        guard !emojis.isEmpty else { return nil }
+        
+        let group = LNEmojiGroup()
+        group.emojis = emojis
+        group.groupName = TIMLocalizedText.shared.commonText("TUIChatFaceGroupRecentEmojiName")
+        return group
+    }
+    
+    private var faceCache: [String: UIImage] = [:]
+    
+    private init() {
+        DispatchQueue.global().async { [weak self] in
+            guard let self else { return }
+            reloadEmoji()
+            loadRecent()
+        }
+    }
+    
+    func addRecentEmoji(name: String) {
+        if recentEmoji.contains(name) {
+            recentEmoji.removeAll { $0 == name }
+        } else {
+            recentEmoji.removeLast()
+        }
+        recentEmoji.insert(name, at: 0)
+    }
+    
+    func getFaceFromCache(path: String?) -> UIImage? {
+        guard let path, !path.isEmpty else { return nil }
+        
+        if let image = faceCache[path] { return image }
+        
+        if let loaded = UIImage(contentsOfFile: path) {
+            faceCache[path] = loaded
+            return loaded
+        }
+        return nil
+    }
+}
+
+extension LNIMEmojiManager {
+    private func reloadEmoji() {
+        let plistPath = resourcePath.appendingPathComponent("emoji/emoji.plist").path
+        
+        guard let data = try? Data(contentsOf: URL(fileURLWithPath: plistPath)) else { return }
+        let decoder = PropertyListDecoder()
+        guard let infos: [LNEmojiConfig] = try? decoder.decode([LNEmojiConfig].self, from: data) else { return }
+        
+        var emojis: [LNEmojiData] = []
+        for info in infos {
+            let data = LNEmojiData()
+            data.name = info.face_name
+            data.path = resourcePath.appendingPathComponent(String(format: "emoji/%@", info.face_file)).path
+            data.localizableName = TIMLocalizedText.shared.faceText(info.face_name)
+            
+            addFaceToCache(path: data.path)
+            emojis.append(data)
+        }
+        guard !emojis.isEmpty else { return }
+        
+        let group = LNEmojiGroup()
+        group.emojis = emojis
+        group.groupName = TIMLocalizedText.shared.commonText("TUIChatFaceGroupAllEmojiName")
+        emojiGroup = group
+    }
+    
+    private func loadRecent() {
+        let cached: [String] = LNUserDefaults[.imRecentEmojis, []]
+        if !cached.isEmpty {
+            recentEmoji = Array(cached.prefix(6))
+            return
+        }
+        let plistPath = resourcePath.appendingPathComponent("emoji/emojiRecentDefaultList.plist").path
+        
+        guard let data = try? Data(contentsOf: URL(fileURLWithPath: plistPath)) else { return }
+        let decoder = PropertyListDecoder()
+        guard let infos: [LNEmojiConfig] = try? decoder.decode([LNEmojiConfig].self, from: data) else { return }
+        
+        let allNames: [String] = infos.map { $0.face_name }
+        recentEmoji = Array(allNames.prefix(6))
+    }
+    
+    private func addFaceToCache(path: String) {
+        faceCache[path] = UIImage(contentsOfFile: path)
+    }
+}

+ 117 - 0
Lanu/Manager/IM/Emoji/String+TUIEmoji.swift

@@ -0,0 +1,117 @@
+//
+//  String+TUIEmoji.swift
+//  Lanu
+//
+//  Created by OneeChan on 2025/12/17.
+//
+
+import Foundation
+
+
+private class LNEmojiDescContent {
+    var range: NSRange
+    var string: String
+    
+    init(range: NSRange, string: String) {
+        self.range = range
+        self.string = string
+    }
+}
+
+
+private class LNEnojiImageContent {
+    var range: NSRange
+    var string: NSAttributedString
+    
+    init(range: NSRange, string: NSAttributedString) {
+        self.range = range
+        self.string = string
+    }
+}
+
+extension String {
+    private static var regex_emoji = "\\[[a-zA-Z0-9_\\u4e00-\\u9fa5]+\\]"
+    
+    var getEmojiDescContent: String {
+        var content = self
+        
+        guard let emojiGroup = LNIMEmojiManager.shared.emojiGroup else { return content }
+        
+        let re = try? NSRegularExpression(pattern: Self.regex_emoji, options: .caseInsensitive)
+        guard let re else { return content }
+        
+        let resultArray = re.matches(in: content, options: [], range: .init(location: 0, length: content.count))
+        var waitingReplaceM: [LNEmojiDescContent] = []
+        for match in resultArray {
+            guard let range = Range(match.range, in: content) else {
+                continue
+            }
+            let subStr = String(content[range])
+            guard let emoji = emojiGroup.emojiFor(subStr) else { continue }
+            let text = emoji.localizableName.isEmpty ? emoji.name : emoji.localizableName
+            
+            let content = LNEmojiDescContent(range: match.range, string: text)
+            waitingReplaceM.append(content)
+        }
+        guard !waitingReplaceM.isEmpty else { return content }
+        for item in waitingReplaceM.reversed() {
+            guard let range = Range(item.range, in: content) else { continue }
+            content = content.replacingCharacters(in: range, with: item.string)
+        }
+        
+        return content
+    }
+    
+    func getEmojiString(with font: UIFont) -> NSMutableAttributedString {
+        let content = NSMutableAttributedString(string: self)
+        
+        guard let emojiGroup = LNIMEmojiManager.shared.emojiGroup else { return content }
+        
+        let re = try? NSRegularExpression(pattern: Self.regex_emoji, options: .caseInsensitive)
+        
+        guard let re else { return content }
+        let resultArray = re.matches(in: self, options: [], range: .init(location: 0, length: count))
+        var waitingReplaceM: [LNEnojiImageContent] = []
+        for match in resultArray {
+            guard let range = Range(match.range, in: self) else {
+                continue
+            }
+            let subStr = String(self[range])
+            
+            guard let emoji = emojiGroup.emojiFor(subStr) else { continue }
+            let attachment = LNIMChatEmojiAttachment()
+            attachment.name = emoji.name
+            attachment.font = font
+            attachment.image = LNIMEmojiManager.shared.getFaceFromCache(path: emoji.path)
+            let attachStr = NSAttributedString(attachment: attachment)
+            
+            let content = LNEnojiImageContent(range: match.range, string: attachStr)
+            waitingReplaceM.append(content)
+        }
+        guard !waitingReplaceM.isEmpty else { return content }
+        for item in waitingReplaceM.reversed() {
+            content.replaceCharacters(in: item.range, with: item.string)
+        }
+        
+        return content
+    }
+}
+
+extension NSAttributedString {
+    var toEmojiContent: String {
+        let sourceStr = NSMutableAttributedString(attributedString: self)
+        
+        var waitingReplaceM: [LNEmojiDescContent] = []
+        sourceStr.enumerateAttribute(.attachment, in: .init(location: 0, length: sourceStr.length))
+        { value, range, _ in
+            if let emoji = value as? LNIMChatEmojiAttachment {
+                waitingReplaceM.append(LNEmojiDescContent(range: range, string: emoji.name))
+            }
+        }
+        
+        for item in waitingReplaceM.reversed() {
+            sourceStr.replaceCharacters(in: item.range, with: item.string)
+        }
+        return sourceStr.string
+    }
+}

+ 17 - 4
Lanu/Manager/IM/LNIMManager.swift

@@ -37,6 +37,7 @@ class LNIMManager: NSObject {
     
     private override init() {
         super.init()
+        _ = LNIMEmojiManager.shared
         LNEventDeliver.addObserver(self)
         V2TIMManager.sharedInstance().addConversationListener(listener: self)
         V2TIMManager.sharedInstance().addIMSDKListener(listener: self)
@@ -128,17 +129,29 @@ extension LNIMManager: V2TIMSDKListener {
 
 extension LNIMManager: LNAccountManagerNotify {
     func onUserLogin() {
-        let userSig = GenerateTestUserSig.genTestUserSig(myUid)
+        // 初始化 SDK
+        let config = V2TIMSDKConfig()
+        config.logLevel = .LOG_INFO
+        guard V2TIMManager.sharedInstance().initSDK(Self.appId, config: config) else {
+            return
+        }
         
-        TUILogin.login(Self.appId, userID: myUid, userSig: userSig) { [weak self] in
+        // 登录
+        let userSig = GenerateTestUserSig.genTestUserSig(myUid)
+        let loginSuccessBlock = { [weak self] in
             guard let self else { return }
             reloadConversationList()
-        } fail: { code, err in
         }
+        if V2TIMManager.sharedInstance().getLoginUser()?.isMyUid == true {
+            loginSuccessBlock()
+            return
+        }
+        
+        V2TIMManager.sharedInstance().login(userID: myUid, userSig: userSig, succ: loginSuccessBlock)
     }
     
     func onUserLogout() {
-        TUILogin.logout(nil)
+        V2TIMManager.sharedInstance().logout(succ: nil)
         userStatus.removeAll()
         
         Self.shared = LNIMManager()

+ 3 - 0
Lanu/Manager/IM/LNIMMessageData.swift

@@ -45,6 +45,7 @@ class LNIMMessageData: NSObject {
     let imMessage: V2TIMMessage
     private(set) var type: LNIMMessageDataType
     private(set) var content: String?
+    private(set) var textContent: NSAttributedString?
     
     private var customMessage: (any Decodable)?
     
@@ -92,6 +93,8 @@ class LNIMMessageData: NSObject {
 extension LNIMMessageData {
     private func setupTextContent() {
         content = imMessage.textElem?.text
+        
+        textContent = content?.getEmojiString(with: .body_l)
     }
     
     private func setupImageContent() {

+ 47 - 0
Lanu/Manager/IM/TUIUtils/String+TUILocalized.swift

@@ -0,0 +1,47 @@
+//
+//  String+TUILocalized.swift
+//  Lanu
+//
+//  Created by OneeChan on 2025/12/18.
+//
+
+import Foundation
+
+
+class TIMLocalizedText {
+    static let shared = TIMLocalizedText()
+    private var commonBundle: Bundle?
+    private var faceBundle: Bundle?
+    
+    private init() {
+        rebuildBundle()
+        LNEventDeliver.addObserver(self)
+    }
+    
+    func commonText(_ key: String) -> String {
+        commonBundle?.localizedString(forKey: key, value: key, table: nil) ?? key
+    }
+    
+    func faceText(_ key: String) -> String {
+        faceBundle?.localizedString(forKey: key, value: key, table: nil) ?? key
+    }
+}
+
+extension TIMLocalizedText: LNAppMainEvent {
+    func onAppLanguageChanged(newLanguage: LNAppLanguage) {
+        rebuildBundle()
+    }
+}
+
+extension TIMLocalizedText {
+    private func rebuildBundle() {
+        let curLan = LNAppConfig.shared.curLang.bundleName
+        let language = "Localizable/" + curLan
+        
+        let commonPath = Bundle(path: Bundle.main.path(forResource: "TIMCommonLocalizable", ofType: "bundle")!)!
+        commonBundle = Bundle(path: commonPath.path(forResource: language, ofType: "lproj")!)
+        
+        let facePath = Bundle(path: Bundle.main.path(forResource: "TUIChatFace", ofType: "bundle")!)!
+        faceBundle = Bundle(path: facePath.path(forResource: language, ofType: "lproj")!)
+    }
+}

+ 6 - 10
Lanu/Manager/IM/V2TIMConversation+Extension.swift → Lanu/Manager/IM/TUIUtils/V2TIMConversation+Extension.swift

@@ -8,10 +8,6 @@
 import Foundation
 
 
-func TIMCommonLocalizedString(_ key: String) -> String {
-    TUIGlobalization.getLocalizedString(forKey: key, bundle: TIMCommonLocalizableBundle)
-}
-
 extension V2TIMConversation {
     var lastDisplayDate: Date? {
         if draftText?.isEmpty == false {
@@ -32,9 +28,9 @@ extension V2TIMConversation {
         let isRevoked = lastMessage?.status == .MSG_STATUS_LOCAL_REVOKED
         
         if draftText?.count ?? 0 > 0 {
-            let draft = NSAttributedString(string: TIMCommonLocalizedString("TUIKitMessageTypeDraftFormat"), attributes: [.foregroundColor: UIColor(hex: "#FA5151")])
+            let draft = NSAttributedString(string: TIMLocalizedText.shared.commonText("TUIKitMessageTypeDraftFormat"), attributes: [.foregroundColor: UIColor(hex: "#FA5151")])
             attrString.append(draft)
-            let draftContentStr = draftContent.getLocalizableStringWithFaceContent()
+            let draftContentStr = draftContent.getEmojiDescContent
             let draftContent = NSAttributedString(string: draftContentStr, attributes: [.foregroundColor: UIColor.systemGray])
             attrString.append(draftContent)
         } else {
@@ -52,7 +48,7 @@ extension V2TIMConversation {
         if groupType == GroupType_Meeting,
            recvOpt == .RECEIVE_NOT_NOTIFY_MESSAGE,
            unreadCount > 0 {
-            let unreadStr = NSAttributedString(string: String(format: "[%d %@]", unreadCount, TIMCommonLocalizedString("TUIKitMessageTypeLastMsgCountFormat")))
+            let unreadStr = NSAttributedString(string: String(format: "[%d %@]", unreadCount, TIMLocalizedText.shared.commonText("TUIKitMessageTypeLastMsgCountFormat")))
             attrString.insert(unreadStr, at: 0)
         }
         
@@ -97,13 +93,13 @@ extension V2TIMConversation {
         }
         var atTipsStr = ""
         if atMe, !atAll {
-            atTipsStr = TIMCommonLocalizedString("TUIKitConversationTipsAtMe")
+            atTipsStr = TIMLocalizedText.shared.commonText("TUIKitConversationTipsAtMe")
         }
         if !atMe, atAll {
-            atTipsStr = TIMCommonLocalizedString("TUIKitConversationTipsAtAll")
+            atTipsStr = TIMLocalizedText.shared.commonText("TUIKitConversationTipsAtAll")
         }
         if atMe, atAll {
-            atTipsStr = TIMCommonLocalizedString("TUIKitConversationTipsAtMeAndAll")
+            atTipsStr = TIMLocalizedText.shared.commonText("TUIKitConversationTipsAtMeAndAll")
         }
         
         return atTipsStr

+ 43 - 43
Lanu/Manager/IM/V2TIMMessage+Extension.swift → Lanu/Manager/IM/TUIUtils/V2TIMMessage+Extension.swift

@@ -13,7 +13,7 @@ extension V2TIMMessage {
         let hasRisk = hasRiskContent
         let isRevoked = status == .MSG_STATUS_LOCAL_REVOKED
         if hasRisk, !isRevoked {
-            return TIMCommonLocalizedString("TUIKitMessageDisplayRiskContent")
+            return TIMLocalizedText.shared.commonText("TUIKitMessageDisplayRiskContent")
         }
         if isRevoked {
             return revokeDisplayString
@@ -27,39 +27,39 @@ extension V2TIMMessage {
         if let revokerInfo {
             revoker = revokerInfo.userID
         }
-        var content = TIMCommonLocalizedString("TUIKitMessageTipsNormalRecallMessage")
+        var content = TIMLocalizedText.shared.commonText("TUIKitMessageTipsNormalRecallMessage")
         if revoker == sender {
             if isSelf {
-                content = TIMCommonLocalizedString("TUIKitMessageTipsYouRecallMessage")
+                content = TIMLocalizedText.shared.commonText("TUIKitMessageTipsYouRecallMessage")
             } else {
                 if userID?.isEmpty == false {
-                    content = TIMCommonLocalizedString("TUIKitMessageTipsOthersRecallMessage")
+                    content = TIMLocalizedText.shared.commonText("TUIKitMessageTipsOthersRecallMessage")
                 } else if groupID?.isEmpty == false {
-                    content = String(format: TIMCommonLocalizedString("TUIKitMessageTipsRecallMessageFormat"), showName)
+                    content = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsRecallMessageFormat"), showName)
                 } else { }
             }
         } else {
             var userName = showName
             if let revokerInfo {
-                userName = revokerInfo.showName()
+                userName = revokerInfo.nickName ?? ""
             }
-            content = String(format: TIMCommonLocalizedString("TUIKitMessageTipsRecallMessageFormat"), userName)
+            content = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsRecallMessageFormat"), userName)
         }
         return content
     }
     
     private var displayStringForMessageElement: String? {
         switch elemType {
-        case .ELEM_TYPE_TEXT: textElem?.text?.getLocalizableStringWithFaceContent()
-        case .ELEM_TYPE_IMAGE: TIMCommonLocalizedString("TUIkitMessageTypeImage")
-        case .ELEM_TYPE_SOUND: TIMCommonLocalizedString("TUIKitMessageTypeVoice")
-        case .ELEM_TYPE_VIDEO: TIMCommonLocalizedString("TUIkitMessageTypeVideo")
-        case .ELEM_TYPE_FILE: TIMCommonLocalizedString("TUIkitMessageTypeFile")
-        case .ELEM_TYPE_FACE: TIMCommonLocalizedString("TUIKitMessageTypeAnimateEmoji")
-        case .ELEM_TYPE_MERGER: String(format: "[%@]", TIMCommonLocalizedString("TUIKitRelayChatHistory"))
+        case .ELEM_TYPE_TEXT: textElem?.text?.getEmojiDescContent
+        case .ELEM_TYPE_IMAGE: TIMLocalizedText.shared.commonText("TUIkitMessageTypeImage")
+        case .ELEM_TYPE_SOUND: TIMLocalizedText.shared.commonText("TUIKitMessageTypeVoice")
+        case .ELEM_TYPE_VIDEO: TIMLocalizedText.shared.commonText("TUIkitMessageTypeVideo")
+        case .ELEM_TYPE_FILE: TIMLocalizedText.shared.commonText("TUIkitMessageTypeFile")
+        case .ELEM_TYPE_FACE: TIMLocalizedText.shared.commonText("TUIKitMessageTypeAnimateEmoji")
+        case .ELEM_TYPE_MERGER: String(format: "[%@]", TIMLocalizedText.shared.commonText("TUIKitRelayChatHistory"))
         case .ELEM_TYPE_GROUP_TIPS: groupTipsDisplayString
-        case .ELEM_TYPE_CUSTOM: TIMCommonLocalizedString("TUIKitMessageTipsUnsupportCustomMessage")
-        default: TIMCommonLocalizedString("TUIKitMessageTipsUnsupportCustomMessage")
+        case .ELEM_TYPE_CUSTOM: TIMLocalizedText.shared.commonText("TUIKitMessageTipsUnsupportCustomMessage")
+        default: TIMLocalizedText.shared.commonText("TUIKitMessageTipsUnsupportCustomMessage")
         }
     }
     
@@ -75,35 +75,35 @@ extension V2TIMMessage {
                 if userList.isEmpty
                     || (userList.count == 1
                         && opUser == userList.first) {
-                    str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsJoinGroupFormat"), opUser)
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsJoinGroupFormat"), opUser)
                 } else {
                     let users = userList.joined(separator: "、")
-                    str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsInviteJoinGroupFormat"), opUser, users)
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsInviteJoinGroupFormat"), opUser, users)
                 }
             }
         case .GROUP_TIPS_TYPE_INVITE:
             if !userList.isEmpty {
                 let users = userList.joined(separator: "、")
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsInviteJoinGroupFormat"), opUser, users)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsInviteJoinGroupFormat"), opUser, users)
             }
         case .GROUP_TIPS_TYPE_QUIT:
             if !opUser.isEmpty {
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsLeaveGroupFormat"), opUser)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsLeaveGroupFormat"), opUser)
             }
         case .GROUP_TIPS_TYPE_KICKED:
             if !userList.isEmpty {
                 let users = userList.joined(separator: "、")
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsKickoffGroupFormat"), opUser, users)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsKickoffGroupFormat"), opUser, users)
             }
         case .GROUP_TIPS_TYPE_SET_ADMIN:
             if !userList.isEmpty {
                 let users = userList.joined(separator: "、")
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsSettAdminFormat"), users)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsSettAdminFormat"), users)
             }
         case .GROUP_TIPS_TYPE_CANCEL_ADMIN:
             if !userList.isEmpty {
                 let users = userList.joined(separator: "、")
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsCancelAdminFormat"), users)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsCancelAdminFormat"), users)
             }
         case .GROUP_TIPS_TYPE_GROUP_INFO_CHANGE:
             str = opGroupInfoChangedFormatStr(opUser: opUser, userList: userList, tips: tips)
@@ -116,15 +116,15 @@ extension V2TIMMessage {
                 } else {
                     ""
                 }
-                str = String(format: "%@ %@", userId?.isMyUid == true ? TIMCommonLocalizedString("You") : showName, muteTime == 0 ? TIMCommonLocalizedString("TUIKitMessageTipsUnmute") : TIMCommonLocalizedString("TUIKitMessageTipsMute"))
+                str = String(format: "%@ %@", userId?.isMyUid == true ? TIMLocalizedText.shared.commonText("You") : showName, muteTime == 0 ? TIMLocalizedText.shared.commonText("TUIKitMessageTipsUnmute") : TIMLocalizedText.shared.commonText("TUIKitMessageTipsMute"))
             }
         case .GROUP_TIPS_TYPE_PINNED_MESSAGE_ADDED:
             if !opUser.isEmpty {
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsGroupPinMessage"), opUser)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsGroupPinMessage"), opUser)
             }
         case .GROUP_TIPS_TYPE_PINNED_MESSAGE_DELETED:
             if !opUser.isEmpty {
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsGroupUnPinMessage"), opUser)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsGroupUnPinMessage"), opUser)
             }
         default:
             break
@@ -166,53 +166,53 @@ extension V2TIMMessage {
         for info in tips.groupChangeInfoList {
             switch info.type {
             case .GROUP_INFO_CHANGE_TYPE_NAME:
-                str = String(format: TIMCommonLocalizedString("TUIkitMessageTipsEditGroupNameFormat"), str, info.value ?? "")
+                str = String(format: TIMLocalizedText.shared.commonText("TUIkitMessageTipsEditGroupNameFormat"), str, info.value ?? "")
             case .GROUP_INFO_CHANGE_TYPE_INTRODUCTION:
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsEditGroupIntroFormat"), str, info.value ?? "")
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsEditGroupIntroFormat"), str, info.value ?? "")
             case .GROUP_INFO_CHANGE_TYPE_NOTIFICATION:
                 if let value = info.value, !value.isEmpty {
-                    str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsEditGroupAnnounceFormat"), str, value)
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsEditGroupAnnounceFormat"), str, value)
                 } else {
-                    str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsDeleteGroupAnnounceFormat"), str)
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsDeleteGroupAnnounceFormat"), str)
                 }
             case .GROUP_INFO_CHANGE_TYPE_FACE:
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsEditGroupAvatarFormat"), str)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsEditGroupAvatarFormat"), str)
             case .GROUP_INFO_CHANGE_TYPE_OWNER:
                 if !userList.isEmpty {
-                    str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsEditGroupOwnerFormat"), str, userList.first!)
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsEditGroupOwnerFormat"), str, userList.first!)
                 } else {
-                    str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsEditGroupOwnerFormat"), str, info.value ?? "")
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsEditGroupOwnerFormat"), str, info.value ?? "")
                 }
             case .GROUP_INFO_CHANGE_TYPE_SHUT_UP_ALL:
                 if info.boolValue {
-                    str = String(format: TIMCommonLocalizedString("TUIKitSetShutupAllFormat"), opUser)
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitSetShutupAllFormat"), opUser)
                 } else {
-                    str = String(format: TIMCommonLocalizedString("TUIKitCancelShutupAllFormat"), opUser)
+                    str = String(format: TIMLocalizedText.shared.commonText("TUIKitCancelShutupAllFormat"), opUser)
                 }
             case .GROUP_INFO_CHANGE_TYPE_GROUP_ADD_OPT:
                 let addOpt = info.intValue
                 var addOptDesc = "unknown"
                 if addOpt == V2TIMGroupAddOpt.GROUP_ADD_FORBID.rawValue {
-                    addOptDesc = TIMCommonLocalizedString("TUIKitGroupProfileJoinDisable")
+                    addOptDesc = TIMLocalizedText.shared.commonText("TUIKitGroupProfileJoinDisable")
                 } else if addOpt == V2TIMGroupAddOpt.GROUP_ADD_AUTH.rawValue {
-                    addOptDesc = TIMCommonLocalizedString("TUIKitGroupProfileAdminApprove")
+                    addOptDesc = TIMLocalizedText.shared.commonText("TUIKitGroupProfileAdminApprove")
                 } else if addOpt == V2TIMGroupAddOpt.GROUP_ADD_ANY.rawValue {
-                    addOptDesc = TIMCommonLocalizedString("TUIKitGroupProfileAutoApproval")
+                    addOptDesc = TIMLocalizedText.shared.commonText("TUIKitGroupProfileAutoApproval")
                 }
                     
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsEditGroupAddOptFormat"), str, addOptDesc)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsEditGroupAddOptFormat"), str, addOptDesc)
             case .GROUP_INFO_CHANGE_TYPE_GROUP_APPROVE_OPT:
                 let addOpt = info.intValue
                 var addOptDesc = "unknown"
                 if addOpt == V2TIMGroupAddOpt.GROUP_ADD_FORBID.rawValue {
-                    addOptDesc = TIMCommonLocalizedString("TUIKitGroupProfileInviteDisable")
+                    addOptDesc = TIMLocalizedText.shared.commonText("TUIKitGroupProfileInviteDisable")
                 } else if addOpt == V2TIMGroupAddOpt.GROUP_ADD_AUTH.rawValue {
-                    addOptDesc = TIMCommonLocalizedString("TUIKitGroupProfileAdminApprove")
+                    addOptDesc = TIMLocalizedText.shared.commonText("TUIKitGroupProfileAdminApprove")
                 } else if addOpt == V2TIMGroupAddOpt.GROUP_ADD_ANY.rawValue {
-                    addOptDesc = TIMCommonLocalizedString("TUIKitGroupProfileAutoApproval")
+                    addOptDesc = TIMLocalizedText.shared.commonText("TUIKitGroupProfileAutoApproval")
                 }
                     
-                str = String(format: TIMCommonLocalizedString("TUIKitMessageTipsEditGroupInviteOptFormat"), str, addOptDesc)
+                str = String(format: TIMLocalizedText.shared.commonText("TUIKitMessageTipsEditGroupInviteOptFormat"), str, addOptDesc)
             default:
                 break
             }

+ 2 - 0
Lanu/Manager/LNEventDeliver.swift

@@ -9,9 +9,11 @@ import Foundation
 
 protocol LNAppMainEvent {
     func onAppLaunchFinished()
+    func onAppLanguageChanged(newLanguage: LNAppLanguage)
 }
 extension LNAppMainEvent {
     func onAppLaunchFinished() {}
+    func onAppLanguageChanged(newLanguage: LNAppLanguage) {}
 }
 
 class LNEventDeliver {

+ 6 - 10
Lanu/SceneDelegate.swift

@@ -28,8 +28,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
         
         LNEventDeliver.addObserver(self)
         
-        setupObservers()
-        
         autoLoginIfNeed()
         
         #if DEBUG
@@ -76,6 +74,12 @@ extension SceneDelegate: LNNetworkMonitorNotify {
     }
 }
 
+extension SceneDelegate: LNAppMainEvent {
+    func onAppLanguageChanged(newLanguage: LNAppLanguage) {
+        window?.rootViewController = LNNavigationController(rootViewController: LNMainViewController())
+    }
+}
+
 extension SceneDelegate {
     private func autoLoginIfNeed() {
         guard LNAccountManager.shared.wasLogin,
@@ -83,12 +87,4 @@ extension SceneDelegate {
         
         LNAccountManager.shared.loginByToken()
     }
-    
-    private func setupObservers() {
-        LNAppConfig.shared.$curLang.sink { [weak self] newLanguage in
-            guard let self else { return }
-            guard LNAppConfig.shared.curLang != newLanguage else { return }
-            window?.rootViewController = LNNavigationController(rootViewController: LNMainViewController())
-        }.store(in: &bag)
-    }
 }

+ 1 - 1
Lanu/Views/IM/Chat/Cells/LNIMChatTextMessageCell.swift

@@ -15,7 +15,7 @@ class LNIMChatTextMessageCell: LNIMChatBaseMessageCell {
     override func update(_ data: LNIMMessageData, viewModel: LNIMChatViewModel) {
         super.update(data, viewModel: viewModel)
         
-        contextLabel.text = data.content
+        contextLabel.attributedText = data.textContent
     }
     
     override func setupViews() {

+ 22 - 0
Lanu/Views/IM/Chat/Emoji/LNIMChatEmojiAttachment.swift

@@ -0,0 +1,22 @@
+//
+//  LNIMChatEmojiAttachment.swift
+//  Lanu
+//
+//  Created by OneeChan on 2025/12/18.
+//
+
+import Foundation
+
+
+class LNIMChatEmojiAttachment: NSTextAttachment {
+    var name: String = ""
+    var font: UIFont?
+    override func attachmentBounds(for attributes: [NSAttributedString.Key : Any], location: any NSTextLocation, textContainer: NSTextContainer?, proposedLineFragment: CGRect, position: CGPoint) -> CGRect {
+        .init(
+            x: 0,
+            y: -0.4 * proposedLineFragment.size.height,
+            width: (font?.pointSize ?? 14) * 1.5,
+            height: (font?.pointSize ?? 14) * 1.5
+        )
+    }
+}

+ 50 - 0
Lanu/Views/IM/Chat/Emoji/LNIMChatEmojiCell.swift

@@ -0,0 +1,50 @@
+//
+//  LNIMChatEmojiCell.swift
+//  Lanu
+//
+//  Created by OneeChan on 2025/12/17.
+//
+
+import Foundation
+import UIKit
+import SnapKit
+
+
+class LNIMChatEmojiCell: UICollectionViewCell {
+    private let imageView = UIImageView()
+    private(set) var staticImage: UIImage?
+    private(set) var gifImage: UIImage?
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        setupViews()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func update(_ data: LNEmojiData) {
+        let image = LNIMEmojiManager.shared.getFaceFromCache(path: data.path)
+        
+        if image?.sd_imageFormat == .GIF {
+            gifImage = image
+            staticImage = image?.images?.first
+        } else {
+            staticImage = image
+        }
+        
+        imageView.image = staticImage // 展示静态图
+//        imageView.image = image // 展示动态图
+    }
+}
+
+extension LNIMChatEmojiCell {
+    private func setupViews() {
+        contentView.addSubview(imageView)
+        imageView.snp.makeConstraints { make in
+            make.directionalEdges.equalToSuperview()
+        }
+    }
+}

+ 179 - 0
Lanu/Views/IM/Chat/Emoji/LNIMChatEmojiPanel.swift

@@ -0,0 +1,179 @@
+//
+//  LNIMChatEmojiPanel.swift
+//  Lanu
+//
+//  Created by OneeChan on 2025/12/17.
+//
+
+import Foundation
+import UIKit
+import SnapKit
+
+
+protocol LNIMChatEmojiPanelDelegate: NSObject {
+    func onIMChatEmojiPanelDidClickDelete(view: LNIMChatEmojiPanel)
+    func onIMChatEmojiPanel(view: LNIMChatEmojiPanel, didSelectEmoji emoji: LNEmojiData)
+}
+
+
+class LNIMChatEmojiPanel: UIView {
+    private var emojiGroups: [LNEmojiGroup] = []
+    private var collectionView: UICollectionView?
+    
+    private let menuView = UIView()
+    private let deleteButton = UIButton()
+    
+    weak var delegate: LNIMChatEmojiPanelDelegate?
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        setupViews()
+    }
+    
+    func reloadData() {
+        var groups: [LNEmojiGroup] = []
+        if let recent = LNIMEmojiManager.shared.recentGroup {
+            groups.append(recent)
+        }
+        if let emojiGroup = LNIMEmojiManager.shared.emojiGroup {
+            groups.append(emojiGroup)
+        }
+        emojiGroups = groups
+        
+        collectionView?.reloadData()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension LNIMChatEmojiPanel: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LNIMChatEmojiCell.className, for: indexPath) as! LNIMChatEmojiCell
+        
+        let item = emojiGroups[indexPath.section].emojis[indexPath.row]
+        cell.update(item)
+        return cell
+    }
+    
+    func numberOfSections(in collectionView: UICollectionView) -> Int {
+        emojiGroups.count
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        emojiGroups[section].emojis.count
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
+        let header = collectionView.dequeueReusableSupplementaryView(
+            ofKind: UICollectionView.elementKindSectionHeader,
+            withReuseIdentifier: "headerView", for: indexPath)
+        header.subviews.forEach { $0.removeFromSuperview() }
+        let titleLabel = UILabel()
+        titleLabel.font = .systemFont(ofSize: 12)
+        titleLabel.textColor = .init(hex: "#444444")
+        header.addSubview(titleLabel)
+        titleLabel.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(20)
+            make.centerY.equalToSuperview()
+        }
+        let group = emojiGroups[indexPath.section]
+        titleLabel.text = group.groupName
+        return header
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
+        let group = emojiGroups[section]
+        return if group.groupName.isEmpty {
+            .zero
+        } else {
+            .init(width: collectionView.bounds.width, height: 20)
+        }
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        collectionView.deselectItem(at: indexPath, animated: false)
+        
+        let item = emojiGroups[indexPath.section].emojis[indexPath.row]
+        delegate?.onIMChatEmojiPanel(view: self, didSelectEmoji: item)
+        LNIMEmojiManager.shared.addRecentEmoji(name: item.name)
+    }
+}
+
+extension LNIMChatEmojiPanel {
+    private func setupViews() {
+        let collectionView = buildCollectionView()
+        addSubview(collectionView)
+        collectionView.snp.makeConstraints { make in
+            make.directionalEdges.equalToSuperview()
+        }
+        
+        let line = UIView()
+        line.backgroundColor = .init(hex: "#DBDBDB")
+        addSubview(line)
+        line.snp.makeConstraints { make in
+            make.directionalHorizontalEdges.equalToSuperview()
+            make.top.equalToSuperview()
+            make.height.equalTo(0.5)
+        }
+        
+        let menu = buildMenu()
+        addSubview(menu)
+        menu.snp.makeConstraints { make in
+            make.trailing.equalToSuperview().offset(-16)
+            make.bottom.equalToSuperview().offset(20)
+        }
+    }
+    
+    private func buildCollectionView() -> UIView {
+        let layout = UICollectionViewFlowLayout()
+        layout.scrollDirection = .vertical
+        layout.minimumLineSpacing = 10
+        layout.minimumInteritemSpacing = 10
+        layout.sectionInset = .init(top: 10, left: 20, bottom: 0, right: 20)
+        
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.register(LNIMChatEmojiCell.self, forCellWithReuseIdentifier: LNIMChatEmojiCell.className)
+        collectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerView")
+        collectionView.isPagingEnabled = false
+        collectionView.showsHorizontalScrollIndicator = false
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.backgroundColor = .clear
+        collectionView.alwaysBounceVertical = true
+        collectionView.delegate = self
+        collectionView.dataSource = self
+        collectionView.contentInset = .init(top: 10, left: 0, bottom: 0, right: 0)
+        self.collectionView = collectionView
+        
+        return collectionView
+    }
+    
+    private func buildMenu() -> UIView {
+        menuView.snp.makeConstraints { make in
+            make.height.equalTo(88)
+        }
+        
+        deleteButton.setImage(.init(named: "ic_im_chat_emoji_delete"), for: .normal)
+        deleteButton.imageEdgeInsets = .init(top: 5, left: 5, bottom: 5, right: 5)
+        deleteButton.imageView?.contentMode = .scaleAspectFit
+        deleteButton.layer.cornerRadius = 2
+        deleteButton.backgroundColor = .white
+        deleteButton.addAction(UIAction(handler: { [weak self] _ in
+            guard let self else { return }
+            delegate?.onIMChatEmojiPanelDidClickDelete(view: self)
+        }), for: .touchUpInside)
+        menuView.addSubview(deleteButton)
+        deleteButton.snp.makeConstraints { make in
+            make.trailing.equalToSuperview()
+            make.leading.equalToSuperview()
+            make.top.equalToSuperview()
+            make.width.equalTo(50)
+            make.height.equalTo(30)
+        }
+        
+        return menuView
+    }
+}
+

+ 4 - 0
Lanu/Views/IM/Chat/InputMenu/LNIMChatInputMenuView.swift

@@ -29,6 +29,10 @@ class LNIMChatInputMenuView: UIView {
         setupViews()
     }
     
+    func hideInput() {
+        textInput.hideInput()
+    }
+    
     required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }

+ 135 - 32
Lanu/Views/IM/Chat/InputMenu/LNIMChatTextInputView.swift

@@ -15,12 +15,26 @@ protocol LNIMChatTextInputViewDelegate: NSObject {
 }
 
 
+private enum LNIMChatTextInputType {
+    case none
+    case keyboard
+    case emoji
+}
+
+
 class LNIMChatTextInputView: UIView {
-    private let container = UIView()
+    private var hideEmojiConstraint: Constraint?
+    
+    private let emojiButton = UIButton()
+    private let emojiPanel = LNIMChatEmojiPanel()
+    private var emojiHeight: Constraint?
+    
     private let placeholderLabel = UILabel()
     private let inputField = LNAutoSizeTextView()
+    
     private let cameraButton = UIButton()
     private var hideCameraConstraint: Constraint?
+    
     private let sendButton = UIButton()
     private let imagePicker = LNImagePicker()
     
@@ -31,6 +45,11 @@ class LNIMChatTextInputView: UIView {
         super.init(frame: frame)
         
         setupViews()
+        adjustViewsForInputTypeChanged(type: .none, animated: false)
+    }
+    
+    func hideInput() {
+        adjustViewsForInputTypeChanged(type: .none, animated: true)
     }
     
     required init?(coder: NSCoder) {
@@ -81,46 +100,140 @@ extension LNIMChatTextInputView: UITextViewDelegate {
     }
 }
 
+extension LNIMChatTextInputView: LNIMChatEmojiPanelDelegate {
+    func onIMChatEmojiPanelDidClickDelete(view: LNIMChatEmojiPanel) {
+        guard inputField.textStorage.length > 0 else { return }
+        inputField.textStorage.deleteCharacters(in: .init(location: inputField.textStorage.length - 1, length: 1))
+        textViewDidChange(inputField)
+    }
+    
+    func onIMChatEmojiPanel(view: LNIMChatEmojiPanel, didSelectEmoji emoji: LNEmojiData) {
+        let attachment = LNIMChatEmojiAttachment()
+        attachment.font = inputField.font
+        attachment.name = emoji.name
+        attachment.image = LNIMEmojiManager.shared.getFaceFromCache(path: emoji.path)
+        
+        let emojiStr = NSAttributedString(attachment: attachment)
+
+        let curRange = inputField.selectedRange
+        if curRange.length > 0 {
+            inputField.textStorage.deleteCharacters(in: curRange)
+        }
+        inputField.textStorage.insert(emojiStr, at: inputField.selectedRange.location)
+        inputField.selectedRange = .init(location: inputField.selectedRange.location + 1, length: 0)
+        
+        textViewDidChange(inputField)
+    }
+}
+
 extension LNIMChatTextInputView {
+    private func adjustViewsForInputTypeChanged(type: LNIMChatTextInputType, animated: Bool) {
+        switch type {
+        case .none:
+            inputField.inputView = nil
+            inputField.resignFirstResponder()
+            
+            emojiButton.setImage(.init(named: "ic_im_chat_emoji"), for: .normal)
+            
+            hideEmojiConstraint?.update(priority: .high)
+            emojiPanel.isHidden = true
+            emojiPanel.reloadData() // 隐藏输入框便触发刷新
+        case .keyboard:
+            inputField.becomeFirstResponder()
+            inputField.inputView = nil
+            inputField.reloadInputViews()
+            
+            emojiButton.setImage(.init(named: "ic_im_chat_emoji"), for: .normal)
+            
+            hideEmojiConstraint?.update(priority: .low)
+            emojiPanel.isHidden = true
+        case .emoji:
+            inputField.becomeFirstResponder()
+            inputField.inputView = UIView()
+            inputField.reloadInputViews()
+            
+            emojiButton.setImage(.init(named: "ic_im_chat_keyboard"), for: .normal)
+            
+            hideEmojiConstraint?.update(priority: .low)
+            emojiPanel.isHidden = false
+        }
+        if animated {
+            UIView.animate(withDuration: 0.2) { [weak self] in
+                guard let self else { return }
+                layoutIfNeeded()
+            }
+        }
+    }
+    
     private func setupViews() {
         backgroundColor = .fill
         
-        container.backgroundColor = .fill_2
-        container.layer.cornerRadius = 19
-        container.clipsToBounds = true
-        addSubview(container)
-        container.snp.makeConstraints { make in
+        let inputMenu = buildInputMenu()
+        addSubview(inputMenu)
+        inputMenu.snp.makeConstraints { make in
             make.top.equalToSuperview().inset(10)
-            make.bottom.equalToSuperview().offset(-safeBottomInset)
             make.leading.equalToSuperview().offset(12)
+            hideEmojiConstraint = make.bottom.equalToSuperview().offset(-safeBottomInset).priority(.high).constraint
         }
         
         sendButton.setImage(.init(named: "ic_im_chat_voice"), for: .normal)
         sendButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
         sendButton.addAction(UIAction(handler: { [weak self] _ in
             guard let self else { return }
-            let text = inputField.text ?? ""
-            if text.isEmpty {
+            let text = inputField.textStorage
+            if text.length == 0 {
                 delegate?.onVoiceInputClick()
+                adjustViewsForInputTypeChanged(type: .none, animated: true)
             } else {
-                viewModel?.sendTextMessage(text: text)
+                viewModel?.sendTextMessage(text: text.toEmojiContent)
                 inputField.text = nil
                 textViewDidChange(inputField)
             }
         }), for: .touchUpInside)
         addSubview(sendButton)
         sendButton.snp.makeConstraints { make in
-            make.bottom.equalTo(container)
+            make.centerY.equalTo(inputMenu)
             make.trailing.equalToSuperview().offset(-12)
-            make.leading.equalTo(container.snp.trailing).offset(10)
+            make.leading.equalTo(inputMenu.snp.trailing).offset(10)
         }
         
-        let emojiButton = UIButton()
+        let emoji = buildEmojiView()
+        addSubview(emoji)
+        emoji.snp.makeConstraints { make in
+            make.directionalHorizontalEdges.equalToSuperview()
+            make.top.equalTo(inputMenu.snp.bottom).offset(10)
+            make.bottom.equalToSuperview().priority(.medium)
+            emojiHeight = make.height.equalTo(336).constraint
+        }
+        
+        inputField.addKeyboardObserver(beforeShow: { [weak self] height in
+            guard let self else { return }
+            emojiHeight?.update(offset: height)
+            if inputField.inputView == nil {
+                adjustViewsForInputTypeChanged(type: .keyboard, animated: false)
+            }
+        }, onShow: { [weak self] height in
+            guard let self else { return }
+            superview?.layoutIfNeeded()
+        })
+    }
+    
+    private func buildInputMenu() -> UIView {
+        let container = UIView()
+        container.backgroundColor = .fill_2
+        container.layer.cornerRadius = 19
+        container.clipsToBounds = true
+        
         emojiButton.setImage(.init(named: "ic_im_chat_emoji"), for: .normal)
         emojiButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
         emojiButton.addAction(UIAction(handler: { [weak self] _ in
             guard let self else { return }
-            
+            if inputField.inputView != nil {
+                adjustViewsForInputTypeChanged(type: .keyboard, animated: true)
+            } else {
+                adjustViewsForInputTypeChanged(type: .emoji, animated: true)
+            }
+            inputField.reloadInputViews()
         }), for: .touchUpInside)
         container.addSubview(emojiButton)
         emojiButton.snp.makeConstraints { make in
@@ -151,6 +264,7 @@ extension LNIMChatTextInputView {
         cameraButton.setImage(.init(named: "ic_im_chat_camera"), for: .normal)
         cameraButton.addAction(UIAction(handler: { [weak self] _ in
             guard let self else { return }
+            adjustViewsForInputTypeChanged(type: .none, animated: true)
             inputImage()
         }), for: .touchUpInside)
         container.addSubview(cameraButton)
@@ -159,25 +273,14 @@ extension LNIMChatTextInputView {
             make.leading.equalTo(inputField.snp.trailing).offset(4)
             make.trailing.equalToSuperview().offset(-10).priority(.medium)
         }
+        return container
+    }
+    
+    private func buildEmojiView() -> UIView {
+        emojiPanel.isHidden = true
+        emojiPanel.delegate = self
         
-        inputField.addKeyboardObserver(beforeShow: { [weak self] height in
-            guard let self else { return }
-            container.snp.remakeConstraints { make in
-                make.top.equalToSuperview().inset(10)
-                make.leading.equalToSuperview().offset(12)
-                make.bottom.equalToSuperview().offset(-height - 10)
-            }
-        }, onShow: { [weak self] height in
-            guard let self else { return }
-            superview?.layoutIfNeeded()
-        }, beforeDismiss: { [weak self] in
-            guard let self else { return }
-            container.snp.remakeConstraints { make in
-                make.top.equalToSuperview().inset(10)
-                make.bottom.equalToSuperview().offset(-self.safeBottomInset)
-                make.leading.equalToSuperview().offset(12)
-            }
-        })
+        return emojiPanel
     }
 }
 

+ 14 - 7
Lanu/Views/IM/Chat/InputMenu/LNIMChatVoiceInputView.swift

@@ -48,14 +48,19 @@ extension LNIMChatVoiceInputView {
 }
 
 extension LNIMChatVoiceInputView: LNVoiceRecorderNotify {
-    func onRecordTaskStop(taskId: String, fileUrl: URL?, duration: Double) {
+    func onRecordTaskReachMaxDuration(taskId: String, fileUrl: URL?, duration: Double) {
+        if let fileUrl {
+            viewModel?.sendVoiceMessage(voicePath: fileUrl.path, duration: duration)
+        }
+    }
+    
+    func onRecordTaskStop(taskId: String) {
         guard taskId == recordTaskId else { return }
         controlButton.setImage(.init(named: "ic_im_chat_voice_input_pause"), for: .normal)
         durationLabel.text = "00:00"
         
-        if let path = fileUrl {
-            viewModel?.sendVoiceMessage(voicePath: path.path, duration: duration)
-        }
+        waveView.clear()
+        delegate?.onVoiceFinishInput()
     }
     
     func onRecordTaskPause(taskId: String) {
@@ -159,9 +164,11 @@ extension LNIMChatVoiceInputView {
         sendButton.setImage(.init(named: "ic_im_chat_send"), for: .normal)
         sendButton.addAction(UIAction(handler: { [weak self] _ in
             guard let self else { return }
-            LNVoiceRecorder.shared.stopRecord()
-            waveView.clear()
-            delegate?.onVoiceFinishInput()
+            let (path, duration) = LNVoiceRecorder.shared.stopRecord()
+            
+            if let path {
+                viewModel?.sendVoiceMessage(voicePath: path.path, duration: duration)
+            }
         }), for: .touchUpInside)
         container.addSubview(sendButton)
         sendButton.snp.makeConstraints { make in

+ 2 - 2
Lanu/Views/IM/Chat/LNIMChatViewController.swift

@@ -222,9 +222,9 @@ extension LNIMChatViewController {
             make.bottom.equalToSuperview()
         }
         
-        view.onTap { [weak self] in
+        tableView.onTap { [weak self] in
             guard let self else { return }
-            view.endEditing(true)
+            bottomMenu.hideInput()
         }
     }
 }

+ 2 - 2
Lanu/Views/Login/LNLoginPanel.swift

@@ -29,11 +29,11 @@ class LNLoginPanel: LNPopupView {
         LNEventDeliver.addObserver(self)
     }
     
-    static func show() {
+    static func show(container: UIView?) {
         guard curLoginPanel == nil else { return }
             
         let panel = LNLoginPanel()
-        panel.showIn()
+        panel.showIn(container?.window)
         curLoginPanel = panel
     }
     

+ 1 - 1
Lanu/Views/Main/LNMainViewController.swift

@@ -42,7 +42,7 @@ extension LNMainViewController: UITabBarControllerDelegate {
         if viewController is LNHomeViewController { return true }
         
         if !hasLogin {
-            LNLoginPanel.show()
+            LNLoginPanel.show(container: view.window)
             
         }
         return hasLogin

+ 1 - 1
Podfile

@@ -9,7 +9,7 @@ target 'Lanu' do
 
   # # 腾讯IM
   pod 'TIMCommon', :path => "./ThirdParty/TUIKit/TIMCommon"
-  pod 'TUICore', :path => "./ThirdParty/TUIKit/TUICore"
+  pod 'TUIChat', :path => "./ThirdParty/TUIKit/TUIChat"
   
   pod 'TIMPush'
   pod 'TXIMSDK_Plus_iOS_XCFramework'

+ 11 - 13
Podfile.lock

@@ -19,22 +19,20 @@ PODS:
   - GCDWebServer/WebUploader (3.5.4):
     - GCDWebServer/Core
   - TIMCommon (1.0.0):
-    - TUICore
+    - TIMCommon/ImSDK_Plus (= 1.0.0)
+  - TIMCommon/ImSDK_Plus (1.0.0):
+    - TXIMSDK_Plus_iOS_XCFramework
   - TIMPush (8.7.7201):
     - TXIMSDK_Plus_iOS_XCFramework (>= 8.7.7201)
-  - TUICore (1.0.0):
-    - TUICore/ImSDK_Plus (= 1.0.0)
-  - TUICore/Base (1.0.0)
-  - TUICore/ImSDK_Plus (1.0.0):
-    - TUICore/Base
-    - TXIMSDK_Plus_iOS_XCFramework
+  - TUIChat (1.0.0):
+    - TIMCommon
   - TXIMSDK_Plus_iOS_XCFramework (8.7.7201)
 
 DEPENDENCIES:
   - DoraemonKit
   - TIMCommon (from `./ThirdParty/TUIKit/TIMCommon`)
   - TIMPush
-  - TUICore (from `./ThirdParty/TUIKit/TUICore`)
+  - TUIChat (from `./ThirdParty/TUIKit/TUIChat`)
   - TXIMSDK_Plus_iOS_XCFramework
 
 SPEC REPOS:
@@ -48,18 +46,18 @@ SPEC REPOS:
 EXTERNAL SOURCES:
   TIMCommon:
     :path: "./ThirdParty/TUIKit/TIMCommon"
-  TUICore:
-    :path: "./ThirdParty/TUIKit/TUICore"
+  TUIChat:
+    :path: "./ThirdParty/TUIKit/TUIChat"
 
 SPEC CHECKSUMS:
   DoraemonKit: 0b45c9dc6ab34bd426a2782ee1bf7ab13492a60b
   FMDB: 728731dd336af3936ce00f91d9d8495f5718a0e6
   GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
-  TIMCommon: 2bde32069c5d016290f97af79d85066ada9b5d43
+  TIMCommon: 36dad82b29c87b6cfd7eb5251c44db7d03598267
   TIMPush: 4f4fa655697c4106309054d0b50a485e642b4f80
-  TUICore: e4e14a1d86f712bfce1f2ee509e37065a3167ff4
+  TUIChat: 696bca6e2a6cfd2bc22f624425b425b68bd9506c
   TXIMSDK_Plus_iOS_XCFramework: 3b435eae84c639f35ae8dc9c8b92c399a8b0a67f
 
-PODFILE CHECKSUM: 6159bb63027e2bf4fe49c25713af73bccb835746
+PODFILE CHECKSUM: 868153081aaa382b55c91fb34418d216583530a5
 
 COCOAPODS: 1.16.2

+ 0 - 30
ThirdParty/TUIKit/TIMCommon/BaseCellData/NSString+TUIEmoji.h

@@ -1,30 +0,0 @@
-//
-//  NSString+TUIEmoji.h
-//  TUIChat
-//
-//  Created by harvy on 2021/11/15.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-#import <UIKit/UIKit.h>
-#import "TIMDefine.h"
-NS_ASSUME_NONNULL_BEGIN
-
-#define kSplitStringResultKey @"result"
-#define kSplitStringTextKey @"text"
-#define kSplitStringTextIndexKey @"textIndex"
-
-@interface NSString (TUIEmoji)
-
-/**
- * Localize the emoji text in the current text and get the localized text
- * eg: The original text was @"你好, [大哭]"
- *    - If it is currently in English, this method converts the text to @"Hello,[Cry]"
- *    - If the current is Chinese, this method converts the text to @"你好,[大哭]"
- */
-- (NSString *)getLocalizableStringWithFaceContent;
-
-@end
-
-NS_ASSUME_NONNULL_END

+ 0 - 55
ThirdParty/TUIKit/TIMCommon/BaseCellData/NSString+TUIEmoji.m

@@ -1,55 +0,0 @@
-//
-//  NSString+TUIEmoji.m
-//  TUIChat
-//
-//  Created by harvy on 2021/11/15.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import "NSString+TUIEmoji.h"
-#import "TIMConfig.h"
-
-@implementation NSString (TUIEmoji)
-
-+ (NSString *)getRegex_emoji {
-    
-    NSString *regex_emoji = @"\\[[a-zA-Z0-9_\\u4e00-\\u9fa5]+\\]";  // match emoji
-
-    return regex_emoji;
-}
-- (NSString *)getLocalizableStringWithFaceContent {
-    NSString *content = self;
-    NSString *regex_emoji = [self.class getRegex_emoji];  // match emoji
-    NSError *error = nil;
-    NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:regex_emoji options:NSRegularExpressionCaseInsensitive error:&error];
-    if (re) {
-        NSArray *resultArray = [re matchesInString:content options:0 range:NSMakeRange(0, content.length)];
-        TUIFaceGroup *group = [TIMConfig defaultConfig].faceGroups[0];
-        NSMutableArray *waitingReplaceM = [NSMutableArray array];
-        for (NSTextCheckingResult *match in resultArray) {
-            NSRange range = [match range];
-            NSString *subStr = [content substringWithRange:range];
-            for (TUIFaceCellData *face in group.faces) {
-                if ([face.name isEqualToString:subStr]) {
-                    [waitingReplaceM
-                        addObject:@{@"range" : NSStringFromRange(range), @"localizableStr" : face.localizableName.length ? face.localizableName : face.name}];
-                    break;
-                }
-            }
-        }
-
-        if (waitingReplaceM.count) {
-            /**
-             * Replace from back to front, otherwise it will cause positional problems
-             */
-            for (int i = (int)waitingReplaceM.count - 1; i >= 0; i--) {
-                NSRange range = NSRangeFromString(waitingReplaceM[i][@"range"]);
-                NSString *localizableStr = waitingReplaceM[i][@"localizableStr"];
-                content = [content stringByReplacingCharactersInRange:range withString:localizableStr];
-            }
-        }
-    }
-    return content;
-}
-
-@end

+ 0 - 27
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonMediator.h

@@ -1,27 +0,0 @@
-//
-//  TIMCommonMediator.h
-//  TUIEmojiPlugin
-//
-//  Created by cologne on 2023/11/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface TIMCommonMediator : NSObject
-
-+ (instancetype)share;
-
-///  Protocol : Class
-/// Register Protocol : Class
-- (void)registerService:(Protocol *)service class:(Class)cls;
-
-///  Protocol  [Class new]
-/// get  [class new]  by Protocol 
-- (id)getObject:(Protocol *)service;
-
-@end
-
-NS_ASSUME_NONNULL_END

+ 0 - 41
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonMediator.m

@@ -1,41 +0,0 @@
-//
-//  TIMCommonMediator.m
-//  TUIEmojiPlugin
-//
-//  Created by cologne on 2023/11/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import "TIMCommonMediator.h"
-
-@interface TIMCommonMediator()
-@property (nonatomic, strong) NSMutableDictionary *map;
-@end
-
-@implementation TIMCommonMediator
-
-+ (instancetype)share {
-    static TIMCommonMediator *mediator = nil;
-    static dispatch_once_t onceToken;
-    dispatch_once(&onceToken, ^{
-        mediator = [TIMCommonMediator new];
-        mediator.map = [NSMutableDictionary new];
-    });
-    return mediator;
-}
-
-- (void)registerService:(Protocol *)service class:(Class)cls {
-    if (!service || !cls) return;
-    self.map[NSStringFromProtocol(service)] = cls;
-}
-
-- (id)getObject:(Protocol *)service {
-    if (!service) return nil;
-    Class cls = self.map[NSStringFromProtocol(service)];
-    id obj = [cls new];
-    if ([obj conformsToProtocol:service]) {
-        return obj;
-    }
-    return nil;
-}
-@end

+ 0 - 102
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonModel.h

@@ -1,102 +0,0 @@
-//
-//  TIMCommonModel.h
-//  TIMCommon
-//
-//  Created by cologne on 2023/3/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-#import <TUICore/TUICommonModel.h>
-#import "TIMDefine.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/////////////////////////////////////////////////////////////////////////////////
-//
-//                             TUIFaceCell & data
-//
-/////////////////////////////////////////////////////////////////////////////////
-
-/**
- * 【Module name】 TUIFaceCellData
- * 【Function description]】The name and local storage path of the stored emoticon.
- */
-@interface TUIFaceCellData : NSObject
-
-/**
- * The name of emoticon
- */
-@property(nonatomic, strong) NSString *name;
-
-/**
- * The localized name of the emoticon (the attribute used for internationalization, if it is empty or the length is 0, the name is displayed by default)
- */
-@property(nonatomic, copy) NSString *localizableName;
-
-/**
- * The storage path of the emoticon cached locally.
- */
-@property(nonatomic, strong) NSString *path;
-@end
-
-/////////////////////////////////////////////////////////////////////////////////
-//
-//                          TUIFaceGroup
-//
-/////////////////////////////////////////////////////////////////////////////////
-
-/**
- * 【Module name】 TUIFaceGroup
- * 【Function description】 It is used to realize the grouping of emoticon, which is convenient for users to browse and select under different emoticon themes.
- *  This class stores the index of each emoticon group, so that FaceView can locate each emoticon group.
- *  At the same time, this class stores the path of all emoticon pictures in an emoticon group, and provides data such as the number of lines, the number of
- * emoticons in each line, etc., to locate specific emoticons
- */
-@interface TUIFaceGroup : NSObject
-
-/**
- *  Index of emoticons group, begining with zero.
- */
-@property(nonatomic, assign) int groupIndex;
-
-/**
- *  The resource path of the entire expression group
- */
-@property(nonatomic, strong) NSString *groupPath;
-
-/**
- *  The number of lines of emoticons in the emoticon group
- */
-@property(nonatomic, assign) int rowCount;
-
-/**
- *  The number of emoticons contained in each line
- */
-@property(nonatomic, assign) int itemCountPerRow;
-
-@property(nonatomic, strong) NSMutableArray *faces;
-
-@property(nonatomic, strong) NSDictionary *facesMap;
-
-/**
- *  The flag of indicating whether to display the delete button
- *  When set to YES, FaceView will display a "delete" icon in the lower right corner of the emoticon view. Clicking the icon can delete the entered emoticon
- * directly without evoking the keyboard.
- */
-@property(nonatomic, assign) BOOL needBackDelete;
-
-/**
- *  The path to the cover image of the emoticon group
- */
-@property(nonatomic, strong) NSString *menuPath;
-
-@property(nonatomic, strong) TUIFaceGroup *recentGroup;
-
-@property(nonatomic, assign) BOOL isNeedAddInInputBar;
-
-@property(nonatomic, copy) NSString *groupName;
-
-@end
-
-NS_ASSUME_NONNULL_END

+ 0 - 39
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMCommonModel.m

@@ -1,39 +0,0 @@
-//
-//  TIMCommonModel.m
-//  TIMCommon
-//
-//  Created by cologne on 2023/3/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import "TIMCommonModel.h"
-
-
-/////////////////////////////////////////////////////////////////////////////////
-//
-//                             TUIFaceCell & data
-//
-/////////////////////////////////////////////////////////////////////////////////
-@implementation TUIFaceCellData
-@end
-
-/////////////////////////////////////////////////////////////////////////////////
-//
-//                          TUIFaceGroup
-//
-/////////////////////////////////////////////////////////////////////////////////
-@implementation TUIFaceGroup
-
-- (NSDictionary *)facesMap {
-    if (!_facesMap || (_facesMap.count != _faces.count )) {
-        NSMutableDictionary *faceDic = [NSMutableDictionary dictionaryWithCapacity:3];
-        if (_faces.count > 0) {
-            for (TUIFaceCellData *data in _faces) {
-                [faceDic setObject:data.path forKey:data.name];
-            }
-        }
-        _facesMap = [NSDictionary dictionaryWithDictionary:faceDic];
-    }
-    return _facesMap;
-}
-@end

+ 0 - 42
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMConfig.h

@@ -1,42 +0,0 @@
-//
-//  TIMConfig.h
-//  Pods
-//
-//  Created by cologne on 2023/3/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-#import "TIMCommonModel.h"
-#import "TIMDefine.h"
-
-@class TUIFaceCellData;
-@class TUIFaceGroup;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface TIMConfig : NSObject
-
-+ (TIMConfig *)defaultConfig;
-/**
- * In respect for the copyright of the emoji design, the Chat Demo/TUIKit project does not include the cutouts of large emoji elements. Please replace them
- * with your own designed or copyrighted emoji packs before the official launch for commercial use. The default small yellow face emoji pack is copyrighted by
- * Tencent Cloud and can be authorized for a fee. If you wish to obtain authorization, please submit a ticket to contact us.
- *
- * submit a ticket url:https://console.cloud.tencent.com/workorder/category?level1_id=29&level2_id=40&source=14&data_title=%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%20IM&step=1 (China mainland)
- * submit a ticket url:https://console.tencentcloud.com/workorder/category?level1_id=29&level2_id=40&source=14&data_title=Chat&step=1 (Other regions)
- */
-@property(nonatomic, strong) NSArray<TUIFaceGroup *> *faceGroups;
-
-/**
- * 
- * The list of emoticons displayed after long-pressing the message on the chat interface
- */
-@property(nonatomic, strong) NSArray<TUIFaceGroup *> *chatPopDetailGroups;
-
-
-@property(nonatomic, assign) BOOL enableMessageBubble;
-
-@end
-
-NS_ASSUME_NONNULL_END

+ 0 - 55
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMConfig.m

@@ -1,55 +0,0 @@
-//
-//  TIMConfig.m
-//  Pods
-//
-//  Created by cologne on 2023/3/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import "TIMConfig.h"
-#import "TIMCommonMediator.h"
-#import "TUIEmojiMeditorProtocol.h"
-#define kTUIKitFirstInitAppStyleID @"Classic";  // Classic / Minimalist
-
-typedef NS_OPTIONS(NSInteger, emojiFaceType) {
-    emojiFaceTypeKeyBoard = 1 << 0,
-    emojiFaceTypePopDetail = 1 << 1,
-};
-
-@interface TIMConfig ()
-
-@end
-
-@implementation TIMConfig
-
-+ (void)load {
-}
-
-- (id)init {
-    self = [super init];
-    if (self) {
-        self.enableMessageBubble = YES;
-    }
-    return self;
-}
-
-+ (id)defaultConfig {
-    static dispatch_once_t onceToken;
-    static TIMConfig *config;
-    dispatch_once(&onceToken, ^{
-      config = [[TIMConfig alloc] init];
-    });
-    return config;
-}
-
-- (NSArray<TUIFaceGroup *> *)faceGroups {
-    id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
-    return [service getFaceGroup];
-}
-
-- (NSArray<TUIFaceGroup *> *)chatPopDetailGroups {
-    id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
-    return [service getChatPopDetailGroups];
-}
-
-@end

+ 0 - 16
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMDefine.h

@@ -1,16 +0,0 @@
-//
-//  TIMDefine.h
-//  Pods
-//
-//  Created by cologne on 2023/3/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#ifndef TIMDefine_h
-#define TIMDefine_h
-
-#import <TUICore/TUIDefine.h>
-#import "TIMConfig.h"
-#import "TIMCommonModel.h"
-
-#endif /* TIMDefine_h */

+ 0 - 51
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMRTLUtil.h

@@ -1,51 +0,0 @@
-//
-//  TIMRTLUtil.h
-//  TIMCommon
-//
-//  Created by cologne on 2023/7/21.
-//  Copyright © 2023 Tencent. All rights reserved
-//
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-
-@interface TIMRTLUtil : NSObject
-
-@end
-
-@interface UIView (TUIRTL)
-- (void)resetFrameToFitRTL;
-
-@end
-
-@interface UIImage (TUIRTL)
-
-- (UIImage *_Nonnull)checkOverturn;
-- (UIImage *)rtl_imageFlippedForRightToLeftLayoutDirection;
-@end
-
-typedef NS_ENUM(NSUInteger, TUITextRTLAlignment) {
-    TUITextRTLAlignmentUndefine,
-    TUITextRTLAlignmentLeading,
-    TUITextRTLAlignmentTrailing,
-    TUITextRTLAlignmentCenter,
-};
-@interface UILabel (TUIRTL)
-@property (nonatomic, assign) TUITextRTLAlignment rtlAlignment;
-@end
-
-@interface NSMutableAttributedString (TUIRTL)
-@property (nonatomic, assign) TUITextRTLAlignment rtlAlignment;
-@end
-
-BOOL isRTLString(NSString *string);
-NSString * rtlString(NSString *string);
-NSAttributedString *rtlAttributeString(NSAttributedString *attributeString ,NSTextAlignment textAlignment );
-UIEdgeInsets rtlEdgeInsetsWithInsets(UIEdgeInsets insets);
-
-@interface TUICollectionRTLFitFlowLayout : UICollectionViewFlowLayout
-
-@end
-NS_ASSUME_NONNULL_END

+ 0 - 291
ThirdParty/TUIKit/TIMCommon/CommonModel/TIMRTLUtil.m

@@ -1,291 +0,0 @@
-//
-//  TIMRTLUtil.m
-//  TIMCommon
-//
-//  Created by cologne on 2023/7/21.
-//  Copyright © 2023 Tencent. All rights reserved
-//
-
-#import "TIMRTLUtil.h"
-#import <objc/runtime.h>
-#import <TUICore/TUIGlobalization.h>
-
-@implementation TIMRTLUtil
-
-@end
-
-
-@interface UIView (TUIRTL)
-
-@end
-@implementation UIView (TUIRTL)
-- (void)setRTLFrame:(CGRect)frame width:(CGFloat)width {
-    if (isRTL()) {
-        if (self.superview == nil) {
-            NSAssert(0, @"must invoke after have superView");
-        }
-        CGFloat x = width - frame.origin.x - frame.size.width;
-        frame.origin.x = x;
-    }
-    self.frame = frame;
-}
-
-- (void)setRTLFrame:(CGRect)frame {
-    [self setRTLFrame:frame width:self.superview.frame.size.width];
-}
-
-- (void)resetFrameToFitRTL {
-    [self setRTLFrame:self.frame];
-}
-
-@end
-
-@interface UIImage (TUIRTL)
-
-@end
-@implementation UIImage (TUIRTL)
-- (UIImage *_Nonnull)checkOverturn{
-    if (isRTL()) {
-        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
-        CGContextRef bitmap = UIGraphicsGetCurrentContext();
-        CGContextTranslateCTM(bitmap, self.size.width / 2, self.size.height / 2);
-        CGContextScaleCTM(bitmap, -1.0, -1.0);
-        CGContextTranslateCTM(bitmap, -self.size.width / 2, -self.size.height / 2);
-        CGContextDrawImage(bitmap, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage);
-        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
-        return image;
-    }
-    return self;
-}
-- (UIImage *)_imageFlippedForRightToLeftLayoutDirection {
-    if (isRTL()) {
-        return [UIImage imageWithCGImage:self.CGImage
-                                   scale:self.scale
-                             orientation:UIImageOrientationUpMirrored];
-    }
-
-    return self;
-}
-
-- (UIImage *)rtl_imageFlippedForRightToLeftLayoutDirection {
-    if (isRTL()) {
-        if (@available(iOS 13.0, *)) {
-            UITraitCollection *const scaleTraitCollection = [UITraitCollection currentTraitCollection];
-            UITraitCollection *const darkUnscaledTraitCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
-            UITraitCollection *const darkScaledTraitCollection =
-                [UITraitCollection traitCollectionWithTraitsFromCollections:@[ scaleTraitCollection, darkUnscaledTraitCollection ]];
-
-            UIImage *lightImg = [[self.imageAsset imageWithTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]]
-                _imageFlippedForRightToLeftLayoutDirection];
-
-            UIImage *darkImage = [[self.imageAsset imageWithTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]]
-                _imageFlippedForRightToLeftLayoutDirection];
-
-            UIImage *image =
-                [lightImg imageWithConfiguration:[self.configuration
-                                                     configurationWithTraitCollection:[UITraitCollection
-                                                                                          traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]]];
-            [image.imageAsset registerImage:darkImage withTraitCollection:darkScaledTraitCollection];
-            return image;
-        } else {
-            return [UIImage imageWithCGImage:self.CGImage scale:self.scale orientation:UIImageOrientationUpMirrored];
-        }
-    }
-    return self;
-}
-
-@end
-@interface UINavigationController (TUIRTL)
-@end
-@implementation UINavigationController (TUIRTL)
-+ (void)load {
-    Method oldMethod = class_getInstanceMethod(self, @selector(initWithRootViewController:));
-    Method newMethod = class_getInstanceMethod(self, @selector(rtl_initWithRootViewController:));
-    method_exchangeImplementations(oldMethod, newMethod);
-}
-
-- (instancetype)rtl_initWithRootViewController:(UIViewController *)rootViewController {
-    if ([self rtl_initWithRootViewController:rootViewController]) {
-        if (@available(iOS 9.0, *)) {
-            if (isRTL()) {
-                self.navigationBar.semanticContentAttribute = [UIView appearance].semanticContentAttribute;
-                self.view.semanticContentAttribute = [UIView appearance].semanticContentAttribute;
-            }
-        }
-    }
-    return self;
-}
-@end
-
-
-UIEdgeInsets rtlEdgeInsetsWithInsets(UIEdgeInsets insets) {
-    if (insets.left != insets.right && isRTL()) {
-        CGFloat temp = insets.left;
-        insets.left = insets.right;
-        insets.right = temp;
-    }
-    return insets;
-    
-}
-
-@implementation UIButton (TUIRTL)
-
-void swizzleInstanceMethod(Class cls, SEL originSelector, SEL swizzleSelector){
-    if (!cls) {
-        return;
-    }
-    /* if current class not exist selector, then get super*/
-    Method originalMethod = class_getInstanceMethod(cls, originSelector);
-    Method swizzledMethod = class_getInstanceMethod(cls, swizzleSelector);
-    
-    /* add selector if not exist, implement append with method */
-    if (class_addMethod(cls,
-                        originSelector,
-                        method_getImplementation(swizzledMethod),
-                        method_getTypeEncoding(swizzledMethod)) ) {
-        /* replace class instance method, added if selector not exist */
-        /* for class cluster , it always add new selector here */
-        class_replaceMethod(cls,
-                            swizzleSelector,
-                            method_getImplementation(originalMethod),
-                            method_getTypeEncoding(originalMethod));
-        
-    } else {
-        /* swizzleMethod maybe belong to super */
-        class_replaceMethod(cls,
-                            swizzleSelector,
-                            class_replaceMethod(cls,
-                                                originSelector,
-                                                method_getImplementation(swizzledMethod),
-                                                method_getTypeEncoding(swizzledMethod)),
-                            method_getTypeEncoding(originalMethod));
-    }
-}
-+ (void)load
-{
-    swizzleInstanceMethod(self, @selector(setContentEdgeInsets:), @selector(rtl_setContentEdgeInsets:));
-    swizzleInstanceMethod(self, @selector(setImageEdgeInsets:), @selector(rtl_setImageEdgeInsets:));
-    swizzleInstanceMethod(self, @selector(setTitleEdgeInsets:), @selector(rtl_setTitleEdgeInsets:));
-}
-
-- (void)rtl_setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets {
-    [self rtl_setContentEdgeInsets:rtlEdgeInsetsWithInsets(contentEdgeInsets)];
-}
-
-- (void)rtl_setImageEdgeInsets:(UIEdgeInsets)imageEdgeInsets {
-    [self rtl_setImageEdgeInsets:rtlEdgeInsetsWithInsets(imageEdgeInsets)];
-}
-
-- (void)rtl_setTitleEdgeInsets:(UIEdgeInsets)titleEdgeInsets {
-    [self rtl_setTitleEdgeInsets:rtlEdgeInsetsWithInsets(titleEdgeInsets)];
-}
-
-
-@end
-
-
-@implementation UILabel (TUIRTL)
-
-- (void)setRtlAlignment:(TUITextRTLAlignment)rtlAlignment {
-    objc_setAssociatedObject(self, @selector(rtlAlignment), @(rtlAlignment), OBJC_ASSOCIATION_ASSIGN);
-    switch (rtlAlignment) {
-        case TUITextRTLAlignmentLeading:
-            self.textAlignment = (isRTL() ? NSTextAlignmentRight : NSTextAlignmentLeft);
-            break;
-        case TUITextRTLAlignmentTrailing:
-            self.textAlignment = (isRTL() ? NSTextAlignmentLeft : NSTextAlignmentRight);
-            break;
-        case TUITextRTLAlignmentCenter:
-            self.textAlignment = NSTextAlignmentCenter;
-        case TUITextRTLAlignmentUndefine:
-            break;
-        default:
-            break;
-    }
-}
-
-- (TUITextRTLAlignment)rtlAlignment {
-    NSNumber *identifier = objc_getAssociatedObject(self, @selector(rtlAlignment));
-    if (identifier) {
-        return identifier.integerValue;
-    }
-    return TUITextRTLAlignmentUndefine;
-}
-@end
-
-@implementation NSMutableAttributedString (TUIRTL)
-- (void)setRtlAlignment:(TUITextRTLAlignment)rtlAlignment {
-    switch (rtlAlignment) {
-        case TUITextRTLAlignmentLeading:
-            self.rtlAlignment = (isRTL() ? NSTextAlignmentRight : NSTextAlignmentLeft);
-            break;
-        case TUITextRTLAlignmentTrailing:
-            self.rtlAlignment = (isRTL() ? NSTextAlignmentLeft : NSTextAlignmentRight);
-            break;
-        case TUITextRTLAlignmentCenter:
-            self.rtlAlignment = NSTextAlignmentCenter;
-        case TUITextRTLAlignmentUndefine:
-            break;
-        default:
-            break;
-    }
-}
-@end
-
-BOOL isRTLString(NSString *string) {
-    if ([string hasPrefix:@"\u202B"] || [string hasPrefix:@"\u202A"]) {
-        return YES;
-    }
-    return NO;
-}
-
-NSString * rtlString(NSString *string) {
-    if (string.length == 0 || isRTLString(string)) {
-        return string;
-    }
-    if (isRTL()) {
-        string = [@"\u202B" stringByAppendingString:string];
-    } else {
-        string = [@"\u202A" stringByAppendingString:string];
-    }
-    return string;
-}
-
-NSAttributedString *rtlAttributeString(NSAttributedString *attributeString ,NSTextAlignment textAlignment ){
-    if (attributeString.length == 0) {
-        return attributeString;
-    }
-    NSRange range;
-    NSDictionary *originAttributes = [attributeString attributesAtIndex:0 effectiveRange:&range];
-    NSParagraphStyle *style = [originAttributes objectForKey:NSParagraphStyleAttributeName];
-
-    if (style && isRTLString(attributeString.string)) {
-        return attributeString;
-    }
-
-    NSMutableDictionary *attributes = originAttributes ? [originAttributes mutableCopy] : [NSMutableDictionary new];
-    if (!style) {
-        NSMutableParagraphStyle *mutableParagraphStyle = [[NSMutableParagraphStyle alloc] init];
-        UILabel *test = [UILabel new];
-        test.textAlignment = textAlignment;
-        mutableParagraphStyle.alignment = test.textAlignment;
-        style = mutableParagraphStyle;
-        [attributes setValue:mutableParagraphStyle forKey:NSParagraphStyleAttributeName];
-    }
-    NSString *string = rtlString(attributeString.string);
-    return [[NSAttributedString alloc] initWithString:string attributes:attributes];
-}
-
-@implementation TUICollectionRTLFitFlowLayout
-- (UIUserInterfaceLayoutDirection)effectiveUserInterfaceLayoutDirection {
-    if (isRTL()) {
-        return UIUserInterfaceLayoutDirectionRightToLeft;
-    }
-    return UIUserInterfaceLayoutDirectionLeftToRight;
-}
-
-- (BOOL)flipsHorizontallyInOppositeLayoutDirection{
-    
-    return isRTL()? YES:NO;
-}
-@end

+ 0 - 27
ThirdParty/TUIKit/TIMCommon/CommonModel/TUIEmojiMeditorProtocol.h

@@ -1,27 +0,0 @@
-//
-//  TUIEmojiMeditorProtocol.h
-//  TUIEmojiPlugin
-//
-//  Created by wyl on 2023/11/14.
-//  Copyright © 2023 Tencent. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-#import "TIMDefine.h"
-#import "TIMCommonModel.h"
-@class V2TIMMessage;
-@class TUIFaceGroup;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@protocol TUIEmojiMeditorProtocol <NSObject>
-- (void)updateEmojiGroups;
-- (id)getFaceGroup;
-- (void)appendFaceGroup:(TUIFaceGroup *)faceGroup;
-- (id)getChatPopDetailGroups;
-- (id)getChatContextEmojiDetailGroups;
-- (id)getChatPopMenuRecentQueue;
-- (void)updateRecentMenuQueue:(NSString *)faceName;
-@end
-
-NS_ASSUME_NONNULL_END

+ 6 - 1
ThirdParty/TUIKit/TIMCommon/TIMCommon.podspec

@@ -12,8 +12,13 @@ Pod::Spec.new do |spec|
   spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147'
   spec.authors      = 'tencent video cloud'
   spec.summary      = 'TIMCommon'
-  spec.dependency 'TUICore'
   spec.requires_arc = true
+  
+  spec.default_subspec = 'ImSDK_Plus'
+  spec.subspec 'ImSDK_Plus' do |plus|
+      plus.dependency 'TXIMSDK_Plus_iOS_XCFramework'
+  end
+
 
   spec.source = { :git => 'https://git.woa.com/lynxzhang/tui-components.git', :tag => spec.version}
   spec.source_files = '**/*.{h,m,mm,c}'

+ 24 - 0
ThirdParty/TUIKit/TUICore/Resources/PrivacyInfo.xcprivacy → ThirdParty/TUIKit/TUIChat/Resources/PrivacyInfo.xcprivacy

@@ -8,6 +8,30 @@
 	<array/>
 	<key>NSPrivacyCollectedDataTypes</key>
 	<array>
+		<dict>
+			<key>NSPrivacyCollectedDataType</key>
+			<string>NSPrivacyCollectedDataTypePhotosorVideos</string>
+			<key>NSPrivacyCollectedDataTypeLinked</key>
+			<false/>
+			<key>NSPrivacyCollectedDataTypeTracking</key>
+			<false/>
+			<key>NSPrivacyCollectedDataTypePurposes</key>
+			<array>
+				<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
+			</array>
+		</dict>
+		<dict>
+			<key>NSPrivacyCollectedDataType</key>
+			<string>NSPrivacyCollectedDataTypeAudioData</string>
+			<key>NSPrivacyCollectedDataTypeLinked</key>
+			<false/>
+			<key>NSPrivacyCollectedDataTypeTracking</key>
+			<false/>
+			<key>NSPrivacyCollectedDataTypePurposes</key>
+			<array>
+				<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
+			</array>
+		</dict>
 		<dict>
 			<key>NSPrivacyCollectedDataType</key>
 			<string>NSPrivacyCollectedDataTypeUserID</string>

+ 62 - 0
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/ar.lproj/Localizable.strings

@@ -0,0 +1,62 @@
+"[TUIEmoji_Smile]" = "[ابتسامة]";
+"[TUIEmoji_Expect]" = "[توقع]";
+"[TUIEmoji_Blink]" = "[غمز]";
+"[TUIEmoji_Guffaw]" = "[ضحكة عالية]";
+"[TUIEmoji_KindSmile]" = "[ابتسامة لطيفة]";
+"[TUIEmoji_Haha]" = "[هاها]";
+"[TUIEmoji_Cheerful]" = "[مرح]";
+"[TUIEmoji_Speechless]" = "[بلا كلمات]";
+"[TUIEmoji_Amazed]" = "[مدهش]";
+"[TUIEmoji_Sorrow]" = "[حزن]";
+"[TUIEmoji_Complacent]" = "[راض]";
+"[TUIEmoji_Silly]" = "[ضحكة غبية]";
+"[TUIEmoji_Lustful]" = "[شهواني]";
+"[TUIEmoji_Giggle]" = "[قهقهة]";
+"[TUIEmoji_Kiss]" = "[قبلة]";
+"[TUIEmoji_Wail]" = "[بكاء]";
+"[TUIEmoji_TearsLaugh]" = "[ضحك حتى الدموع]";
+"[TUIEmoji_Trapped]" = "[محاصر]";
+"[TUIEmoji_Mask]" = "[قناع]";
+"[TUIEmoji_Fear]" = "[خوف]";
+"[TUIEmoji_BareTeeth]" = "[أسنان عارية]";
+"[TUIEmoji_FlareUp]" = "[غضب]";
+"[TUIEmoji_Yawn]" = "[تثاؤب]";
+"[TUIEmoji_Tact]" = "[دهاء]";
+"[TUIEmoji_Stareyes]" = "[عيون النجوم]";
+"[TUIEmoji_ShutUp]" = "[أغلق فمك]";
+"[TUIEmoji_Sigh]" = "[تنهد]";
+"[TUIEmoji_Hehe]" = "[ههه]";
+"[TUIEmoji_Silent]" = "[صامت]";
+"[TUIEmoji_Surprised]" = "[متفاجئ]";
+"[TUIEmoji_Askance]" = "[نظرة جانبية]";
+"[TUIEmoji_Ok]" = "[حسنا]";
+"[TUIEmoji_Shit]" = "[براز]";
+"[TUIEmoji_Monster]" = "[وحش]";
+"[TUIEmoji_Daemon]" = "[شيطان]";
+"[TUIEmoji_Rage]" = "[غضب]";
+"[TUIEmoji_Fool]" = "[أحمق]";
+"[TUIEmoji_Pig]" = "[خنزير]";
+"[TUIEmoji_Cow]" = "[بقرة]";
+"[TUIEmoji_Ai]" = "[الذكاء الصناعي]";
+"[TUIEmoji_Skull]" = "[جمجمة]";
+"[TUIEmoji_Bombs]" = "[قنابل]";
+"[TUIEmoji_Coffee]" = "[قهوة]";
+"[TUIEmoji_Cake]" = "[كعكة]";
+"[TUIEmoji_Beer]" = "[بيرة]";
+"[TUIEmoji_Flower]" = "[زهرة]";
+"[TUIEmoji_Watermelon]" = "[بطيخ]";
+"[TUIEmoji_Rich]" = "[غني]";
+"[TUIEmoji_Heart]" = "[قلب]";
+"[TUIEmoji_Moon]" = "[قمر]";
+"[TUIEmoji_Sun]" = "[شمس]";
+"[TUIEmoji_Star]" = "[نجمة]";
+"[TUIEmoji_RedPacket]" = "[حزمة حمراء]";
+"[TUIEmoji_Celebrate]" = "[احتفال]";
+"[TUIEmoji_Bless]" = "[بركة]";
+"[TUIEmoji_Fortune]" = "[ثروة]";
+"[TUIEmoji_Convinced]" = "[مقتنع]";
+"[TUIEmoji_Prohibit]" = "[ممنوع]";
+"[TUIEmoji_666]" = "[666]";
+"[TUIEmoji_857]" = "[857]";
+"[TUIEmoji_Knife]" = "[سكين]";
+"[TUIEmoji_Like]" = "[أعجبني]";

+ 62 - 0
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/en.lproj/Localizable.strings

@@ -0,0 +1,62 @@
+"[TUIEmoji_Smile]" = "[Smile]";
+"[TUIEmoji_Expect]" = "[Expect]";
+"[TUIEmoji_Blink]" = "[Blink]";
+"[TUIEmoji_Guffaw]" = "[Guffaw]";
+"[TUIEmoji_KindSmile]" = "[Kind Smile]";
+"[TUIEmoji_Haha]" = "[Haha]";
+"[TUIEmoji_Cheerful]" = "[Cheerful]";
+"[TUIEmoji_Speechless]" = "[Speechless]";
+"[TUIEmoji_Amazed]" = "[Amazed]";
+"[TUIEmoji_Sorrow]" = "[Sorrow]";
+"[TUIEmoji_Complacent]" = "[Complacent]";
+"[TUIEmoji_Silly]" = "[Silly]";
+"[TUIEmoji_Lustful]" = "[Lustful]";
+"[TUIEmoji_Giggle]" = "[Giggle]";
+"[TUIEmoji_Kiss]" = "[Kiss]";
+"[TUIEmoji_Wail]" = "[Wail]";
+"[TUIEmoji_TearsLaugh]" = "[Tears Laugh]";
+"[TUIEmoji_Trapped]" = "[Trapped]";
+"[TUIEmoji_Mask]" = "[Mask]";
+"[TUIEmoji_Fear]" = "[Fear]";
+"[TUIEmoji_BareTeeth]" = "[Bare Teeth]";
+"[TUIEmoji_FlareUp]" = "[Flare Up]";
+"[TUIEmoji_Yawn]" = "[Yawn]";
+"[TUIEmoji_Tact]" = "[Tact]";
+"[TUIEmoji_Stareyes]" = "[Stareyes]";
+"[TUIEmoji_ShutUp]" = "[Shut Up]";
+"[TUIEmoji_Sigh]" = "[Sigh]";
+"[TUIEmoji_Hehe]" = "[Hehe]";
+"[TUIEmoji_Silent]" = "[Silent]";
+"[TUIEmoji_Surprised]" = "[Surprised]";
+"[TUIEmoji_Askance]" = "[Askance]";
+"[TUIEmoji_Ok]" = "[OK]";
+"[TUIEmoji_Shit]" = "[Shit]";
+"[TUIEmoji_Monster]" = "[Monster]";
+"[TUIEmoji_Daemon]" = "[Daemon]";
+"[TUIEmoji_Rage]" = "[Rage]";
+"[TUIEmoji_Fool]" = "[Fool]";
+"[TUIEmoji_Pig]" = "[Pig]";
+"[TUIEmoji_Cow]" = "[Cow]";
+"[TUIEmoji_Ai]" = "[Ai]";
+"[TUIEmoji_Skull]" = "[Skull]";
+"[TUIEmoji_Bombs]" = "[Bombs]";
+"[TUIEmoji_Coffee]" = "[Coffee]";
+"[TUIEmoji_Cake]" = "[Cake]";
+"[TUIEmoji_Beer]" = "[Beer]";
+"[TUIEmoji_Flower]" = "[Flower]";
+"[TUIEmoji_Watermelon]" = "[Watermelon]";
+"[TUIEmoji_Rich]" = "[Rich]";
+"[TUIEmoji_Heart]" = "[Heart]";
+"[TUIEmoji_Moon]" = "[Moon]";
+"[TUIEmoji_Sun]" = "[Sun]";
+"[TUIEmoji_Star]" = "[Star]";
+"[TUIEmoji_RedPacket]" = "[Red Packet]";
+"[TUIEmoji_Celebrate]" = "[Celebrate]";
+"[TUIEmoji_Bless]" = "[Bless]";
+"[TUIEmoji_Fortune]" = "[Fortune]";
+"[TUIEmoji_Convinced]" = "[Convinced]";
+"[TUIEmoji_Prohibit]" = "[Prohibit]";
+"[TUIEmoji_666]" = "[666]";
+"[TUIEmoji_857]" = "[857]";
+"[TUIEmoji_Knife]" = "[Knife]";
+"[TUIEmoji_Like]" = "[Like]";

+ 62 - 0
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/zh-Hans.lproj/Localizable.strings

@@ -0,0 +1,62 @@
+"[TUIEmoji_Smile]" = "[微笑]";
+"[TUIEmoji_Expect]" = "[期待]";
+"[TUIEmoji_Blink]" = "[眨眼]";
+"[TUIEmoji_Guffaw]" = "[大笑]";
+"[TUIEmoji_KindSmile]" = "[姨母笑]";
+"[TUIEmoji_Haha]" = "[[哈哈哈]";
+"[TUIEmoji_Cheerful]" = "[愉快]";
+"[TUIEmoji_Speechless]" = "[无语]";
+"[TUIEmoji_Amazed]" = "[惊讶]";
+"[TUIEmoji_Sorrow]" = "[悲伤]";
+"[TUIEmoji_Complacent]" = "[得意]";
+"[TUIEmoji_Silly]" = "[傻了]";
+"[TUIEmoji_Lustful]" = "[色]";
+"[TUIEmoji_Giggle]" = "[憨笑]";
+"[TUIEmoji_Kiss]" = "[亲亲]";
+"[TUIEmoji_Wail]" = "[大哭]";
+"[TUIEmoji_TearsLaugh]" = "[哭笑]";
+"[TUIEmoji_Trapped]" = "[困]";
+"[TUIEmoji_Mask]" = "[口罩]";
+"[TUIEmoji_Fear]" = "[恐惧]";
+"[TUIEmoji_BareTeeth]" = "[龇牙]";
+"[TUIEmoji_FlareUp]" = "[发怒]";
+"[TUIEmoji_Yawn]" = "[打哈欠]";
+"[TUIEmoji_Tact]" = "[机智]";
+"[TUIEmoji_Stareyes]" = "[星星眼]";
+"[TUIEmoji_ShutUp]" = "[闭嘴]";
+"[TUIEmoji_Sigh]" = "[叹气]";
+"[TUIEmoji_Hehe]" = "[呵呵]";
+"[TUIEmoji_Silent]" = "[收声]";
+"[TUIEmoji_Surprised]" = "[惊喜]";
+"[TUIEmoji_Askance]" = "[白眼]";
+"[TUIEmoji_Ok]" = "[OK]";
+"[TUIEmoji_Shit]" = "[便便]";
+"[TUIEmoji_Monster]" = "[怪兽]";
+"[TUIEmoji_Daemon]" = "[恶魔]";
+"[TUIEmoji_Rage]" = "[恶魔怒]";
+"[TUIEmoji_Fool]" = "[衰]";
+"[TUIEmoji_Pig]" = "[猪]";
+"[TUIEmoji_Cow]" = "[牛]";
+"[TUIEmoji_Ai]" = "[AI]";
+"[TUIEmoji_Skull]" = "[骷髅]";
+"[TUIEmoji_Bombs]" = "[炸弹]";
+"[TUIEmoji_Coffee]" = "[咖啡]";
+"[TUIEmoji_Cake]" = "[蛋糕]";
+"[TUIEmoji_Beer]" = "[啤酒]";
+"[TUIEmoji_Flower]" = "[花]";
+"[TUIEmoji_Watermelon]" = "[瓜]";
+"[TUIEmoji_Rich]" = "[壕]";
+"[TUIEmoji_Heart]" = "[爱心]";
+"[TUIEmoji_Moon]" = "[月亮]";
+"[TUIEmoji_Sun]" = "[太阳]";
+"[TUIEmoji_Star]" = "[星星]";
+"[TUIEmoji_RedPacket]" = "[红包]";
+"[TUIEmoji_Celebrate]" = "[庆祝]";
+"[TUIEmoji_Bless]" = "[福]";
+"[TUIEmoji_Fortune]" = "[发]";
+"[TUIEmoji_Convinced]" = "[服]";
+"[TUIEmoji_Prohibit]" = "[禁]";
+"[TUIEmoji_666]" = "[666]";
+"[TUIEmoji_857]" = "[857]";
+"[TUIEmoji_Knife]" = "[刀]";
+"[TUIEmoji_Like]" = "[赞]";

+ 62 - 0
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/Localizable/zh-Hant.lproj/Localizable.strings

@@ -0,0 +1,62 @@
+"[TUIEmoji_Smile]" = "[微笑]";
+"[TUIEmoji_Expect]" = "[期待]";
+"[TUIEmoji_Blink]" = "[眨眼]";
+"[TUIEmoji_Guffaw]" = "[大笑]";
+"[TUIEmoji_KindSmile]" = "[姨母笑]";
+"[TUIEmoji_Haha]" = "[哈哈]";
+"[TUIEmoji_Cheerful]" = "[愉快]";
+"[TUIEmoji_Speechless]" = "[無語]";
+"[TUIEmoji_Amazed]" = "[驚訝]";
+"[TUIEmoji_Sorrow]" = "[悲傷]";
+"[TUIEmoji_Complacent]" = "[得意]";
+"[TUIEmoji_Silly]" = "[傻了]";
+"[TUIEmoji_Lustful]" = "[色]";
+"[TUIEmoji_Giggle]" = "[憨笑]";
+"[TUIEmoji_Kiss]" = "[親親]";
+"[TUIEmoji_Wail]" = "[大哭]";
+"[TUIEmoji_TearsLaugh]" = "[哭笑]";
+"[TUIEmoji_Trapped]" = "[困]";
+"[TUIEmoji_Mask]" = "[口罩]";
+"[TUIEmoji_Fear]" = "[恐懼]";
+"[TUIEmoji_BareTeeth]" = "[龇牙]";
+"[TUIEmoji_FlareUp]" = "[發怒]";
+"[TUIEmoji_Yawn]" = "[打哈欠]";
+"[TUIEmoji_Tact]" = "[機智]";
+"[TUIEmoji_Stareyes]" = "[星星眼]";
+"[TUIEmoji_ShutUp]" = "[閉嘴]";
+"[TUIEmoji_Sigh]" = "[嘆氣]";
+"[TUIEmoji_Hehe]" = "[呵呵]";
+"[TUIEmoji_Silent]" = "[收聲]";
+"[TUIEmoji_Surprised]" = "[驚喜]";
+"[TUIEmoji_Askance]" = "[白眼]";
+"[TUIEmoji_Ok]" = "[OK]";
+"[TUIEmoji_Shit]" = "[便便]";
+"[TUIEmoji_Monster]" = "[怪獸]";
+"[TUIEmoji_Daemon]" = "[惡魔]";
+"[TUIEmoji_Rage]" = "[惡魔怒]";
+"[TUIEmoji_Fool]" = "[衰]";
+"[TUIEmoji_Pig]" = "[豬]";
+"[TUIEmoji_Cow]" = "[牛]";
+"[TUIEmoji_Ai]" = "[AI]";
+"[TUIEmoji_Skull]" = "[骷髏]";
+"[TUIEmoji_Bombs]" = "[炸彈]";
+"[TUIEmoji_Coffee]" = "[咖啡]";
+"[TUIEmoji_Cake]" = "[蛋糕]";
+"[TUIEmoji_Beer]" = "[啤酒]";
+"[TUIEmoji_Flower]" = "[花]";
+"[TUIEmoji_Watermelon]" = "[瓜]";
+"[TUIEmoji_Rich]" = "[壕]";
+"[TUIEmoji_Heart]" = "[愛心]";
+"[TUIEmoji_Moon]" = "[月亮]";
+"[TUIEmoji_Sun]" = "[太陽]";
+"[TUIEmoji_Star]" = "[星星]";
+"[TUIEmoji_RedPacket]" = "[紅包]";
+"[TUIEmoji_Celebrate]" = "[慶祝]";
+"[TUIEmoji_Bless]" = "[福]";
+"[TUIEmoji_Fortune]" = "[發]";
+"[TUIEmoji_Convinced]" = "[服]";
+"[TUIEmoji_Prohibit]" = "[禁]";
+"[TUIEmoji_666]" = "[666]";
+"[TUIEmoji_857]" = "[857]";
+"[TUIEmoji_Knife]" = "[刀]";
+"[TUIEmoji_Like]" = "[讚]";

BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/del_normal@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/del_pressed@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoj_normal@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoj_pressed@2x.png


+ 462 - 0
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji.plist

@@ -0,0 +1,462 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Smile]</string>
+		<key>face_file</key>
+		<string>emoji_0</string>
+		<key>face_id</key>
+		<string>001</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Expect]</string>
+		<key>face_file</key>
+		<string>emoji_1</string>
+		<key>face_id</key>
+		<string>002</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Blink]</string>
+		<key>face_file</key>
+		<string>emoji_2</string>
+		<key>face_id</key>
+		<string>003</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Guffaw]</string>
+		<key>face_file</key>
+		<string>emoji_3</string>
+		<key>face_id</key>
+		<string>004</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_KindSmile]</string>
+		<key>face_file</key>
+		<string>emoji_4</string>
+		<key>face_id</key>
+		<string>005</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Haha]</string>
+		<key>face_file</key>
+		<string>emoji_5</string>
+		<key>face_id</key>
+		<string>006</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Cheerful]</string>
+		<key>face_file</key>
+		<string>emoji_6</string>
+		<key>face_id</key>
+		<string>007</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Speechless]</string>
+		<key>face_file</key>
+		<string>emoji_7</string>
+		<key>face_id</key>
+		<string>008</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Amazed]</string>
+		<key>face_file</key>
+		<string>emoji_8</string>
+		<key>face_id</key>
+		<string>009</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Sorrow]</string>
+		<key>face_file</key>
+		<string>emoji_9</string>
+		<key>face_id</key>
+		<string>010</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Complacent]</string>
+		<key>face_file</key>
+		<string>emoji_10</string>
+		<key>face_id</key>
+		<string>011</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Silly]</string>
+		<key>face_file</key>
+		<string>emoji_11</string>
+		<key>face_id</key>
+		<string>012</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Lustful]</string>
+		<key>face_file</key>
+		<string>emoji_12</string>
+		<key>face_id</key>
+		<string>013</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Giggle]</string>
+		<key>face_file</key>
+		<string>emoji_13</string>
+		<key>face_id</key>
+		<string>014</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Kiss]</string>
+		<key>face_file</key>
+		<string>emoji_14</string>
+		<key>face_id</key>
+		<string>015</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Wail]</string>
+		<key>face_file</key>
+		<string>emoji_15</string>
+		<key>face_id</key>
+		<string>016</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_TearsLaugh]</string>
+		<key>face_file</key>
+		<string>emoji_16</string>
+		<key>face_id</key>
+		<string>017</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Trapped]</string>
+		<key>face_file</key>
+		<string>emoji_17</string>
+		<key>face_id</key>
+		<string>018</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Mask]</string>
+		<key>face_file</key>
+		<string>emoji_18</string>
+		<key>face_id</key>
+		<string>019</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Fear]</string>
+		<key>face_file</key>
+		<string>emoji_19</string>
+		<key>face_id</key>
+		<string>020</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_BareTeeth]</string>
+		<key>face_file</key>
+		<string>emoji_20</string>
+		<key>face_id</key>
+		<string>021</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_FlareUp]</string>
+		<key>face_file</key>
+		<string>emoji_21</string>
+		<key>face_id</key>
+		<string>022</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Yawn]</string>
+		<key>face_file</key>
+		<string>emoji_22</string>
+		<key>face_id</key>
+		<string>023</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Tact]</string>
+		<key>face_file</key>
+		<string>emoji_23</string>
+		<key>face_id</key>
+		<string>024</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Stareyes]</string>
+		<key>face_file</key>
+		<string>emoji_24</string>
+		<key>face_id</key>
+		<string>025</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_ShutUp]</string>
+		<key>face_file</key>
+		<string>emoji_25</string>
+		<key>face_id</key>
+		<string>026</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Sigh]</string>
+		<key>face_file</key>
+		<string>emoji_26</string>
+		<key>face_id</key>
+		<string>027</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Hehe]</string>
+		<key>face_file</key>
+		<string>emoji_27</string>
+		<key>face_id</key>
+		<string>028</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Silent]</string>
+		<key>face_file</key>
+		<string>emoji_28</string>
+		<key>face_id</key>
+		<string>029</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Surprised]</string>
+		<key>face_file</key>
+		<string>emoji_29</string>
+		<key>face_id</key>
+		<string>030</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Askance]</string>
+		<key>face_file</key>
+		<string>emoji_30</string>
+		<key>face_id</key>
+		<string>031</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Ok]</string>
+		<key>face_file</key>
+		<string>emoji_31</string>
+		<key>face_id</key>
+		<string>032</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Shit]</string>
+		<key>face_file</key>
+		<string>emoji_32</string>
+		<key>face_id</key>
+		<string>033</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Monster]</string>
+		<key>face_file</key>
+		<string>emoji_33</string>
+		<key>face_id</key>
+		<string>034</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Daemon]</string>
+		<key>face_file</key>
+		<string>emoji_34</string>
+		<key>face_id</key>
+		<string>035</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Rage]</string>
+		<key>face_file</key>
+		<string>emoji_35</string>
+		<key>face_id</key>
+		<string>036</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Fool]</string>
+		<key>face_file</key>
+		<string>emoji_36</string>
+		<key>face_id</key>
+		<string>037</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Pig]</string>
+		<key>face_file</key>
+		<string>emoji_37</string>
+		<key>face_id</key>
+		<string>038</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Cow]</string>
+		<key>face_file</key>
+		<string>emoji_38</string>
+		<key>face_id</key>
+		<string>039</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Ai]</string>
+		<key>face_file</key>
+		<string>emoji_39</string>
+		<key>face_id</key>
+		<string>040</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Skull]</string>
+		<key>face_file</key>
+		<string>emoji_40</string>
+		<key>face_id</key>
+		<string>041</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Bombs]</string>
+		<key>face_file</key>
+		<string>emoji_41</string>
+		<key>face_id</key>
+		<string>042</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Coffee]</string>
+		<key>face_file</key>
+		<string>emoji_42</string>
+		<key>face_id</key>
+		<string>043</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Cake]</string>
+		<key>face_file</key>
+		<string>emoji_43</string>
+		<key>face_id</key>
+		<string>044</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Beer]</string>
+		<key>face_file</key>
+		<string>emoji_44</string>
+		<key>face_id</key>
+		<string>045</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Flower]</string>
+		<key>face_file</key>
+		<string>emoji_45</string>
+		<key>face_id</key>
+		<string>046</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Watermelon]</string>
+		<key>face_file</key>
+		<string>emoji_46</string>
+		<key>face_id</key>
+		<string>047</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Heart]</string>
+		<key>face_file</key>
+		<string>emoji_48</string>
+		<key>face_id</key>
+		<string>049</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Moon]</string>
+		<key>face_file</key>
+		<string>emoji_49</string>
+		<key>face_id</key>
+		<string>050</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Sun]</string>
+		<key>face_file</key>
+		<string>emoji_50</string>
+		<key>face_id</key>
+		<string>051</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Star]</string>
+		<key>face_file</key>
+		<string>emoji_51</string>
+		<key>face_id</key>
+		<string>052</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_RedPacket]</string>
+		<key>face_file</key>
+		<string>emoji_52</string>
+		<key>face_id</key>
+		<string>053</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Celebrate]</string>
+		<key>face_file</key>
+		<string>emoji_53</string>
+		<key>face_id</key>
+		<string>054</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_666]</string>
+		<key>face_file</key>
+		<string>emoji_58</string>
+		<key>face_id</key>
+		<string>059</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_857]</string>
+		<key>face_file</key>
+		<string>emoji_59</string>
+		<key>face_id</key>
+		<string>060</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Knife]</string>
+		<key>face_file</key>
+		<string>emoji_60</string>
+		<key>face_id</key>
+		<string>061</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Like]</string>
+		<key>face_file</key>
+		<string>emoji_61</string>
+		<key>face_id</key>
+		<string>062</string>
+	</dict>
+</array>
+</plist>

+ 70 - 0
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emojiRecentDefaultList.plist

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Smile]</string>
+		<key>face_file</key>
+		<string>emoji_0</string>
+		<key>face_id</key>
+		<string>001</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Expect]</string>
+		<key>face_file</key>
+		<string>emoji_1</string>
+		<key>face_id</key>
+		<string>002</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Blink]</string>
+		<key>face_file</key>
+		<string>emoji_2</string>
+		<key>face_id</key>
+		<string>003</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Guffaw]</string>
+		<key>face_file</key>
+		<string>emoji_3</string>
+		<key>face_id</key>
+		<string>004</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_KindSmile]</string>
+		<key>face_file</key>
+		<string>emoji_4</string>
+		<key>face_id</key>
+		<string>005</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Haha]</string>
+		<key>face_file</key>
+		<string>emoji_5</string>
+		<key>face_id</key>
+		<string>006</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Cheerful]</string>
+		<key>face_file</key>
+		<string>emoji_6</string>
+		<key>face_id</key>
+		<string>007</string>
+	</dict>
+	<dict>
+		<key>face_name</key>
+		<string>[TUIEmoji_Speechless]</string>
+		<key>face_file</key>
+		<string>emoji_7</string>
+		<key>face_id</key>
+		<string>008</string>
+	</dict>
+</array>
+</plist>

BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_0@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_10@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_11@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_12@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_13@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_14@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_15@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_16@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_17@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_18@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_19@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_1@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_20@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_21@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_22@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_23@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_24@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_25@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_26@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_27@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_28@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_29@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_2@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_30@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_31@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_32@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_33@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_34@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_35@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_36@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_37@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_38@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_39@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_3@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_40@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_41@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_42@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_43@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_44@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_45@2x.png


BIN
ThirdParty/TUIKit/TUIChat/Resources/TUIChatFace.bundle/emoji/emoji_46@2x.png


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است