Prechádzať zdrojové kódy

[*] 外部合并250415

SuperC 11 mesiacov pred
rodič
commit
15eb527044
62 zmenil súbory, kde vykonal 1010 pridanie a 455 odobranie
  1. 1 1
      Podfile
  2. 7 7
      Podfile.lock
  3. 12 0
      bugu/bugu.xcodeproj/project.pbxproj
  4. 5 3
      bugu/bugu/HXCore/Base/Common/UINavigationController+Extension.swift
  5. 44 1
      bugu/bugu/HXCore/DataBase/Emoticon+Database.swift
  6. 28 0
      bugu/bugu/HXCore/DataBase/MicroServer+Database.swift
  7. 30 0
      bugu/bugu/HXCore/DataBase/Moment+Database.swift
  8. 16 0
      bugu/bugu/HXCore/DataBase/Organization+Database.swift
  9. 20 5
      bugu/bugu/HXCore/Extensions/Foundation/NSAttributedString/NSAttributedString+Extensions.swift
  10. 1 1
      bugu/bugu/HXCore/Extensions/RxSwift/Kingfisher+Rx.swift
  11. 28 32
      bugu/bugu/HXCore/Manager/AccountManager.swift
  12. 10 4
      bugu/bugu/HXCore/Manager/HistoryMessageManager.swift
  13. 16 3
      bugu/bugu/HXCore/Widget/TextView/HXTextView.swift
  14. 11 0
      bugu/bugu/IMSDK/Private/DataBase/IMDatabaseManager+Group.swift
  15. 11 0
      bugu/bugu/IMSDK/Private/DataBase/IMDatabaseManager+Notification.swift
  16. 6 5
      bugu/bugu/IMSDK/Private/DataBase/SQLite/GroupRobot+Database.swift
  17. 17 0
      bugu/bugu/IMSDK/Private/DataBase/SQLite/Notification+Database.swift
  18. 7 0
      bugu/bugu/IMSDK/Private/IMClientManager+Group.swift
  19. 5 4
      bugu/bugu/IMSDK/Private/IMClientManager+Message.swift
  20. 12 8
      bugu/bugu/IMSDK/Private/IMClientManager+Silent.swift
  21. 2 2
      bugu/bugu/IMSDK/Private/MessageHandler/MessageHandler+FriendShip.swift
  22. 7 0
      bugu/bugu/IMSDK/Public/IMClient/IMClient+Group.swift
  23. 1 1
      bugu/bugu/Modules/AddressBook/Group/Controller/GroupCreateRobotViewController.swift
  24. 1 1
      bugu/bugu/Modules/AddressBook/Group/Controller/GroupInfoViewController.swift
  25. 1 1
      bugu/bugu/Modules/AddressBook/Group/Controller/GroupRobotDetailViewController.swift
  26. 1 0
      bugu/bugu/Modules/AddressBook/PersonalCenter/Controller/PersonalCenterViewController.swift
  27. 37 21
      bugu/bugu/Modules/AddressBook/PersonalCenter/View/PersonalCenterHeaderView.swift
  28. 1 1
      bugu/bugu/Modules/Discover/Moment/Detail/MomentDetailViewController.swift
  29. 1 1
      bugu/bugu/Modules/Discover/Moment/Home/MomentHomeViewController.swift
  30. 1 1
      bugu/bugu/Modules/Discover/Moment/Manager/MomentChangeCoverManager.swift
  31. 1 1
      bugu/bugu/Modules/Discover/Moment/Manager/MomentPushlishManager.swift
  32. 7 5
      bugu/bugu/Modules/IM/Audio/HXAudioController.swift
  33. 1 1
      bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+Keyboard.swift
  34. 27 7
      bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+Menu.swift
  35. 222 172
      bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+MessageCellDelegate.swift
  36. 2 2
      bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+MoreKeyboard.swift
  37. 1 1
      bugu/bugu/Modules/IM/Chat/Controller/ChatViewController.swift
  38. 1 5
      bugu/bugu/Modules/IM/Chat/Display/MessageElem+Display.swift
  39. 1 0
      bugu/bugu/Modules/IM/Chat/Manager/MessageSendManager.swift
  40. 41 3
      bugu/bugu/Modules/IM/Chat/Manager/Register/MessageSignalingUpdateManager.swift
  41. 5 12
      bugu/bugu/Modules/IM/Chat/Protocols/MessageCellDelegate.swift
  42. 1 0
      bugu/bugu/Modules/IM/Chat/View/Input/EmojiKeyboard/EmojiHelper.swift
  43. 57 75
      bugu/bugu/Modules/IM/Chat/View/Input/HXInputMessageBar.swift
  44. 8 0
      bugu/bugu/Modules/IM/Chat/View/Input/TextView/HXInputTextView.swift
  45. 1 1
      bugu/bugu/Modules/IM/Chat/View/MessageCell/HXImageMessageContentCell.swift
  46. 4 0
      bugu/bugu/Modules/IM/Chat/View/MessageCell/HXMessageContentCell.swift
  47. 21 0
      bugu/bugu/Modules/IM/Chat/View/MessageCell/HXTextMessageContentCell.swift
  48. 1 1
      bugu/bugu/Modules/IM/Chat/View/MessageCell/HXVideoMessageContentCell.swift
  49. 14 0
      bugu/bugu/Modules/IM/Chat/View/QuoteMessageView/HXMessageCellQuoteView.swift
  50. 15 8
      bugu/bugu/Modules/IM/Chat/ViewModel/ChatViewModel.swift
  51. 3 2
      bugu/bugu/Modules/IM/Collection/Controller/ChatCollectionViewController.swift
  52. 2 2
      bugu/bugu/Modules/IM/CombineMessage/Controller/ChatCombineMessageViewController+MessagesCellDelegate.swift
  53. 46 4
      bugu/bugu/Modules/IM/File/Controller/HXFilePreviewViewController.swift
  54. 111 0
      bugu/bugu/Modules/IM/Preview/TextPreviewViewController.swift
  55. 48 41
      bugu/bugu/Modules/IM/Session/ViewModel/SessionViewModel.swift
  56. 10 2
      bugu/bugu/Modules/Login/Controller/LoginViewController.swift
  57. 1 1
      bugu/bugu/Modules/Login/Controller/RegisterViewController.swift
  58. 1 1
      bugu/bugu/Modules/Mine/Profile/Controller/MyAccountViewController.swift
  59. 2 1
      bugu/bugu/Modules/Search/Controller/GlobalSearchContainerViewController.swift
  60. 4 3
      bugu/bugu/Modules/Search/Controller/GlobalSearchListViewController.swift
  61. 1 1
      bugu/bugu/Modules/Search/Enum/GlobalSearchEnum.swift
  62. 10 0
      bugu/bugu/Modules/Search/ViewModel/GlobalSearchResultViewModel.swift

+ 1 - 1
Podfile

@@ -108,7 +108,7 @@ target 'bugu' do
   
   # 图片选择 & 浏览
   # ZLPhotoBrowser - 一个功能强大的图片选择和浏览库,支持多种自定义功能
-  pod 'ZLPhotoBrowser', '4.5.6', :inhibit_warnings => true  # https://github.com/longitachi/ZLPhotoBrowser
+  pod 'ZLPhotoBrowser', '4.6.0.1', :inhibit_warnings => true  # https://github.com/longitachi/ZLPhotoBrowser
   # Lantern - 一个轻量级的图片浏览库,支持手势操作
   pod 'Lantern','1.1.5' #https://github.com/fcbox/Lantern
 

+ 7 - 7
Podfile.lock

@@ -124,9 +124,9 @@ PODS:
   - UMCommon (7.4.7):
     - UMDevice
   - UMDevice (3.4.0)
-  - ZLPhotoBrowser (4.5.6):
-    - ZLPhotoBrowser/Core (= 4.5.6)
-  - ZLPhotoBrowser/Core (4.5.6)
+  - ZLPhotoBrowser (4.6.0.1):
+    - ZLPhotoBrowser/Core (= 4.6.0.1)
+  - ZLPhotoBrowser/Core (4.6.0.1)
 
 DEPENDENCIES:
   - CryptoSwift (~> 1.4.1)
@@ -181,7 +181,7 @@ DEPENDENCIES:
   - UMAPM
   - UMCommon
   - UMDevice
-  - ZLPhotoBrowser (= 4.5.6)
+  - ZLPhotoBrowser (= 4.6.0.1)
 
 SPEC REPOS:
   https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git:
@@ -189,6 +189,7 @@ SPEC REPOS:
     - RxCocoa
     - RxSwift
     - SwiftProtobuf
+    - ZLPhotoBrowser
   trunk:
     - Alamofire
     - CryptoSwift
@@ -230,7 +231,6 @@ SPEC REPOS:
     - UMAPM
     - UMCommon
     - UMDevice
-    - ZLPhotoBrowser
 
 EXTERNAL SOURCES:
   DesignKit:
@@ -294,8 +294,8 @@ SPEC CHECKSUMS:
   UMAPM: a52ef6d5be72dd11591b917d96df97627970dae6
   UMCommon: 8b4cd0423297c39bca6eea1ec896558b40e5bcf7
   UMDevice: dcdf7ec167387837559d149fbc7d793d984faf82
-  ZLPhotoBrowser: 9fdd801da47d5aaff778b8213fc711bbadab9cb2
+  ZLPhotoBrowser: 20f32e6429448cc1c008795a1b55472d5772939c
 
-PODFILE CHECKSUM: 5be87975dac3452353d097a44e2e7f451791e913
+PODFILE CHECKSUM: 1cefcbb7f727526e59ca623daae025d19fc1edc8
 
 COCOAPODS: 1.16.2

+ 12 - 0
bugu/bugu.xcodeproj/project.pbxproj

@@ -582,6 +582,7 @@
 		81B39E612948689A00572838 /* AppDelegate+MR.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B39E602948689A00572838 /* AppDelegate+MR.swift */; };
 		81F70E6A29BE1696000543D7 /* AppDelegate+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F70E6929BE1696000543D7 /* AppDelegate+Remote.swift */; };
 		8F09E2A7844EB5FD1FF545D1 /* Pods_ScreenShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 312E128C38B5E0E8FA654C8C /* Pods_ScreenShareExtension.framework */; };
+		A6A4D4D42DB781A3007F6009 /* TextPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6A4D4D22DB781A3007F6009 /* TextPreviewViewController.swift */; };
 		E813ECEC2775F0B700E5C7C7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E813ECEB2775F0B700E5C7C7 /* AppDelegate.swift */; };
 		E813ECF52775F0B900E5C7C7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E813ECF42775F0B900E5C7C7 /* Assets.xcassets */; };
 		E813ECF82775F0B900E5C7C7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E813ECF62775F0B900E5C7C7 /* LaunchScreen.storyboard */; };
@@ -1354,6 +1355,7 @@
 		81B39E602948689A00572838 /* AppDelegate+MR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+MR.swift"; sourceTree = "<group>"; };
 		81F70E6929BE1696000543D7 /* AppDelegate+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Remote.swift"; sourceTree = "<group>"; };
 		81F70E6B29BE320A000543D7 /* bugu.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = bugu.entitlements; sourceTree = "<group>"; };
+		A6A4D4D22DB781A3007F6009 /* TextPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextPreviewViewController.swift; sourceTree = "<group>"; };
 		B1F478CF8C29FA40E487D6CD /* Pods_bugu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_bugu.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		BA3469ADEA407DDBC6B6047C /* Pods-ScreenShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScreenShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ScreenShareExtension/Pods-ScreenShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
 		BD2CCE8F436CC03FD9545114 /* Pods-bugu.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bugu.release.xcconfig"; path = "Target Support Files/Pods-bugu/Pods-bugu.release.xcconfig"; sourceTree = "<group>"; };
@@ -3959,6 +3961,14 @@
 			path = ViewController;
 			sourceTree = "<group>";
 		};
+		A6A4D4D32DB781A3007F6009 /* Preview */ = {
+			isa = PBXGroup;
+			children = (
+				A6A4D4D22DB781A3007F6009 /* TextPreviewViewController.swift */,
+			);
+			path = Preview;
+			sourceTree = "<group>";
+		};
 		C811199077226A8CF8FEEF01 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
@@ -4047,6 +4057,7 @@
 		E82FAE3C27847415007B488E /* IM */ = {
 			isa = PBXGroup;
 			children = (
+				A6A4D4D32DB781A3007F6009 /* Preview */,
 				2DA219022A41C3660028543D /* Session */,
 				81171F39293C977700D13E3F /* Chat */,
 				2D05F5E32B2B183500C5D1C4 /* At */,
@@ -5071,6 +5082,7 @@
 				2D6E39982CEE0D0B001BA7D2 /* UIViewController+Extension.swift in Sources */,
 				E8286CE927BE8837002E4EA7 /* FriendApplyViewController.swift in Sources */,
 				2D05F5EA2B2B210200C5D1C4 /* ChatGroupMemberHeaderView.swift in Sources */,
+				A6A4D4D42DB781A3007F6009 /* TextPreviewViewController.swift in Sources */,
 				2D297DB72B0DAC6C00CBD958 /* MessageSendReadReceiptMessage.swift in Sources */,
 				2D1C3FDA2CB90642009A8A41 /* MomentLinkView.swift in Sources */,
 				E8F4BCB5278AD9620073AE1D /* MicroServerListViewController.swift in Sources */,

+ 5 - 3
bugu/bugu/HXCore/Base/Common/UINavigationController+Extension.swift

@@ -38,19 +38,21 @@ extension UINavigationController {
         setNeedsNavigationBarAlpha(barAlpha)
     }
 
+    private static var _barAlpha: CGFloat = 1
     func setNeedsNavigationBarAlpha(_ barAlpha: CGFloat) {
         if let barTopView = navigationBar.subviews.first  {
             barTopView.alpha = barAlpha
+            UINavigationController._barAlpha = barAlpha
         }
         
-        /// 17.0 一下 push 瞬间的时候没有 navigationBar.backgroundBar,pop 有
+        /// 17.0  push 瞬间的时候没有 navigationBar.backgroundBar,pop 有
         if #available(iOS 17.0, *) {
             if let view = self.navigationBar.subviews.first?.subviews.first  {
                 view.alpha = barAlpha
             } else {
                 DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
-                    if let barTopView = self.navigationBar.subviews.first  {
-                        barTopView.alpha = barAlpha
+                    if let barTopView = self.navigationBar.subviews.first {
+                        barTopView.alpha = UINavigationController._barAlpha
                     }
                 }
             }

+ 44 - 1
bugu/bugu/HXCore/DataBase/Emoticon+Database.swift

@@ -104,6 +104,21 @@ struct EmoticonDataHelper: DataHelperProtocol {
             throw DBError.deleteErr
         }
     } // end delete
+    
+    static func deleteAll() throws {
+        guard let db = Database.shared.connection else {
+            throw DBError.connectionErr
+        }
+        
+        do {
+            // 开始事务,以确保删除操作是原子的
+            try db.transaction {
+                try db.run(table.delete())
+            }
+        } catch {
+            throw DBError.deleteErr
+        }
+    }
 
     static func find(emoticonId: Int) throws -> Emoticon? {
         guard let db = Database.shared.connection else {
@@ -259,7 +274,20 @@ struct EmoticonItemDataHelper: DataHelperProtocol {
         }
     } // end delete
     
-    
+    static func deleteAll() throws {
+        guard let db = Database.shared.connection else {
+            throw DBError.connectionErr
+        }
+        
+        do {
+            // 开始事务,以确保删除操作是原子的
+            try db.transaction {
+                try db.run(table.delete())
+            }
+        } catch {
+            throw DBError.deleteErr
+        }
+    }
 
     static func findItems(eid: Int) throws -> [EmoticonItem]? {
         guard let db = Database.shared.connection else {
@@ -409,6 +437,21 @@ struct MyEmoticonDataHelper: DataHelperProtocol {
         }
         return arr
     }
+    
+    static func deleteAll() throws {
+        guard let db = Database.shared.connection else {
+            throw DBError.connectionErr
+        }
+        
+        do {
+            // 开始事务,以确保删除操作是原子的
+            try db.transaction {
+                try db.run(table.delete())
+            }
+        } catch {
+            throw DBError.deleteErr
+        }
+    }
 }
 
 extension MyEmoticonDataHelper {

+ 28 - 0
bugu/bugu/HXCore/DataBase/MicroServer+Database.swift

@@ -59,6 +59,19 @@ struct MicroServerDataHelper: DataHelperProtocol {
         }
     }
     
+    static func deleteAndInsert(list: [MicroServer]) throws {
+        do {
+            try deleteAllMicroServers()
+            
+            for i in list {
+               let _ = try insert(i: i)
+            }
+        } catch {
+            printLog("insert(list: [MicroServer]) error\(error)")
+            throw error
+        }
+    }
+    
     static func insert(i: MicroServer) throws -> Int64 {
         guard let db = Database.shared.connection else {
             throw DBError.connectionErr
@@ -199,6 +212,21 @@ struct MicroServerDataHelper: DataHelperProtocol {
         return array
     }
     
+    static func deleteAllMicroServers() throws {
+        guard let db = Database.shared.connection else {
+            throw DBError.connectionErr
+        }
+        
+        do {
+            // 开始事务,以确保删除操作是原子的
+            try db.transaction {
+                try db.run(table.delete())
+            }
+        } catch {
+            printLog("deleteAllMicroServers error\(error)")
+            throw DBError.deleteErr
+        }
+    }
 }
 
 struct MicroServerMenuDataHelper: DataHelperProtocol {

+ 30 - 0
bugu/bugu/HXCore/DataBase/Moment+Database.swift

@@ -545,6 +545,19 @@ struct MomentRuleDataHelper: DataHelperProtocol {
         }
     }
     
+    static func deleteAndInsert(list: [MomentRule], type: MomentRuleType) throws {
+        do {
+            try deleteAll(type: type)
+            
+            for i in list {
+               let _ = try insert(i: i)
+            }
+        } catch {
+            printLog("deleteAndInsertMomentRule error\(error)")
+            throw error
+        }
+    }
+    
     static func insert(i: MomentRule) throws -> Int64 {
         guard let db = Database.shared.connection else {
             throw DBError.connectionErr
@@ -615,6 +628,23 @@ struct MomentRuleDataHelper: DataHelperProtocol {
         return arr
     }
     
+    static func deleteAll(type: MomentRuleType) throws {
+        guard let db = Database.shared.connection else {
+            throw DBError.connectionErr
+        }
+        
+        do {
+            // 开始事务,以确保删除操作是原子的
+            let query = table.filter(self.type == type.rawValue)
+            try db.transaction {
+                try db.run(query.delete())
+            }
+        } catch {
+            printLog("deleteAllMomentRules error\(error)")
+            throw DBError.deleteErr
+        }
+    }
+    
 }
 
 struct MomentMessageDataHelper: DataHelperProtocol {

+ 16 - 0
bugu/bugu/HXCore/DataBase/Organization+Database.swift

@@ -122,6 +122,22 @@ struct OrganizationDataHelper: DataHelperProtocol {
         }
         return nil
     }
+    
+    static func deleteAllOrganizations() throws {
+        guard let db = Database.shared.connection else {
+            throw DBError.connectionErr
+        }
+        
+        do {
+            // 开始事务,以确保删除操作是原子的
+            try db.transaction {
+                try db.run(table.delete())
+            }
+        } catch {
+            printLog("deleteAllOrganizations error\(error)")
+            throw DBError.deleteErr
+        }
+    }
 }
 
 extension OrganizationDataHelper {

+ 20 - 5
bugu/bugu/HXCore/Extensions/Foundation/NSAttributedString/NSAttributedString+Extensions.swift

@@ -45,11 +45,26 @@ extension NSAttributedString {
     }
     
     public func size(considering size: CGSize) -> CGSize {
-        let rect = boundingRect(with: size, options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin, .usesFontLeading], context: nil)
-        var size = rect.size
-        size.width = ceil(size.width)
-        size.height = ceil(size.height)
-        return size
+        let textStorage = NSTextStorage(attributedString: self)
+        let textContainer = NSTextContainer(size: size)
+        let layoutManager = NSLayoutManager()
+        
+        layoutManager.addTextContainer(textContainer)
+        textStorage.addLayoutManager(layoutManager)
+        if let attributes = self.attributes {
+            textStorage.addAttributes(attributes, range: NSRange(location: 0, length: textStorage.length))
+        }
+        
+        textContainer.lineFragmentPadding = 0
+        textContainer.maximumNumberOfLines = 0
+        
+        layoutManager.glyphRange(for: textContainer)
+        let rect = layoutManager.usedRect(for: textContainer)
+        var rectSize = rect.size
+        rectSize.width = ceil(rectSize.width)
+        rectSize.height = ceil(rectSize.height)
+        
+        return rectSize
     }
     
     public func lineCount(limitedWidth: CGFloat) -> Int {

+ 1 - 1
bugu/bugu/HXCore/Extensions/RxSwift/Kingfisher+Rx.swift

@@ -28,7 +28,7 @@ extension Reactive where Base: UIImageView {
     }
 }
 
-extension ImageCache: ReactiveCompatible {}
+extension ImageCache: @retroactive ReactiveCompatible {}
 
 extension Reactive where Base: ImageCache {
 

+ 28 - 32
bugu/bugu/HXCore/Manager/AccountManager.swift

@@ -63,42 +63,43 @@ extension AccountManager {
             .mapHandyModel(type: AppBaseMainData.self)
             .observe(on: ConcurrentDispatchQueueScheduler(qos: .background)) // 切换子程序,否则卡顿UI
             .map { mainData  in
+                guard let mainData = mainData else { return }
                 do {
+                    
+                    /// 删除所有联系人
                     try ContactDataHelper.deleteAll()
                     
                     /// 组织成员又是好友列表
-                    if let bothList = mainData?.bothList {
+                    if let bothList = mainData.bothList {
                         try saveAccountData(bothList, relation: .both)
                     }
                     
                     /// 仅仅是组织成员列表
-                    if let contactList = mainData?.contactList {
+                    if let contactList = mainData.contactList {
                         try saveAccountData(contactList, relation: .organizationMember)
                     }
                     
                     /// 仅仅是好友列表
-                    if let friendList = mainData?.friendList {
+                    if let friendList = mainData.friendList {
                         try saveAccountData(friendList, relation: .friend)
                     }
                     
                     /// 更新群列表
-                    if let groupList = mainData?.groupList {
-                        imdatabase.synAllGroup(infos: groupList)
-                    }
+                    let groupList = mainData.groupList ?? []
+                    imdatabase.synAllGroup(infos: groupList)
                     
                     /// 更新公众号信息
-                    if let microServerList = mainData?.microServerList {
-                        for microServer in microServerList {
-                            let _ = try MicroServerDataHelper.insert(i: microServer)
-                        }
-                    }
+                    let microServerList = mainData.microServerList ?? []
+                    try MicroServerDataHelper.deleteAndInsert(list: microServerList)
                     
                     /// 更新组织信息
-                    if var organization = mainData?.organization {
+                    if var organization = mainData.organization {
                         organization.updateDepartmentAndMember()
                         let _ = try OrganizationDataHelper.insert(i: organization)
+                    } else {
+                        /// 删除本地组织
+                        try OrganizationDataHelper.deleteAllOrganizations()
                     }
-                    
                 } catch {
                     throw DBError.dataError
                 }
@@ -119,18 +120,20 @@ extension AccountManager {
             .asObservable()
             .mapHandyModel(type: AppBaseSecondData.self)
             .map { secondData  in
+                guard let secondData = secondData else { return }
                 do {
                     /// 免打扰
-                    if let silentList =  secondData?.silentNotificationList {
-                        let result = IMClient.setNotificationSilentList(list: silentList)
-                        if !result {
-                            throw DBError.dataError
-                        }
+                    let silentList =  secondData.silentNotificationList ?? []
+                    let result = IMClient.setNotificationSilentList(list: silentList)
+                    if !result {
+                        throw DBError.dataError
                     }
                     
                     /// 订阅表情包
-                    if let emoticons = secondData?.emoticonList {
-
+                    try EmoticonDataHelper.deleteAll()
+                    try MyEmoticonDataHelper.deleteAll()
+                    try EmoticonItemDataHelper.deleteAll()
+                    if let emoticons = secondData.emoticonList {
                         /// 更新订阅表情包的状态
                         for emoticon in emoticons {
                             let _ = try EmoticonDataHelper.insert(i: emoticon)
@@ -146,20 +149,13 @@ extension AccountManager {
                     }
                     
                     /// [朋友圈]我不让他们看
-                    if let momentBlacked = secondData?.momentBlackedList {
-                        for userId in momentBlacked {
-                            let rule = MomentRule(uid: AppStorage.user.userId!, targetId: userId, type: .noLookMe)
-                            let _ = try MomentRuleDataHelper.insert(i: rule)
-                        }
-                    }
+                    let momentBlackedList = secondData.momentBlackedList?.compactMap { MomentRule(uid: AppStorage.user.userId!, targetId: $0, type: .noLookMe) } ?? []
+                    try MomentRuleDataHelper.deleteAndInsert(list: momentBlackedList, type: .noLookMe)
                     
                     /// [朋友圈]我不看他们的
-                    if let momentIgnored = secondData?.momentIgnoredList {
-                        for userId in momentIgnored {
-                            let rule = MomentRule(uid: AppStorage.user.userId!, targetId: userId, type: .noLookTA)
-                            let _ = try MomentRuleDataHelper.insert(i: rule)
-                        }
-                    }
+                    let momentIgnoreds = secondData.momentIgnoredList?.compactMap { MomentRule(uid: AppStorage.user.userId!, targetId: $0, type: .noLookTA) } ?? []
+                    try MomentRuleDataHelper.deleteAndInsert(list: momentIgnoreds, type: .noLookTA)
+                    
                 } catch {
                     throw DBError.dataError
                 }

+ 10 - 4
bugu/bugu/HXCore/Manager/HistoryMessageManager.swift

@@ -25,7 +25,7 @@ extension HistoryMessageManager {
     ///   - convId: 会话id
     ///   - messageUid: 远端消息 id
     static func setCleanHistoryMessage(convId: String, convType: ConversationType) {
-        let key = convId + convType.rawValue
+        let key = fetchKey(convId: convId, convType: convType)
         var messageIds = historyMinMessageIds
         messageIds[key] = true
         historyMinMessageIds = messageIds
@@ -35,10 +35,16 @@ extension HistoryMessageManager {
     /// - Parameters:
     ///   - convId: 会话id
     /// - Returns: 远端消息 id
-    static func cleanHistoryMessage(convId: String, convType: ConversationType) -> Bool? {
-        let key = convId + convType.rawValue
+    static func cleanHistoryMessage(convId: String, convType: ConversationType) -> Bool {
+        let key = fetchKey(convId: convId, convType: convType)
         
         let messageIds = historyMinMessageIds
-        return messageIds[key]
+        return messageIds[key] ?? false
     }
 }
+extension HistoryMessageManager {
+    private static func fetchKey(convId: String, convType: ConversationType) -> String {
+        return "\(convId)_\(convType.rawValue)"
+    }
+    
+}

+ 16 - 3
bugu/bugu/HXCore/Widget/TextView/HXTextView.swift

@@ -41,9 +41,22 @@ class HXTextView: UITextView {
     override var text: String! {
         didSet {
             textDidChange()
+            delegate?.textViewDidChange?(self)
         }
     }
     
+    override func insertAttributedText(_ string: NSAttributedString) {
+        super.insertAttributedText(string)
+        textDidChange()
+    }
+    
+    override var textStorage: NSTextStorage {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            self.textDidChange()
+        }
+        return super.textStorage
+    }
+    
     override init(frame: CGRect, textContainer: NSTextContainer?) {
         super.init(frame: frame, textContainer: textContainer)
         commonInit()
@@ -77,11 +90,11 @@ class HXTextView: UITextView {
     }
     
     @objc private func textDidChange() {
-        if self.text.isEmpty {
+        if self.text.isNotEmpty || self.attributedText.length > 0 {
+            placeholderLabel.removeFromSuperview()
+        } else {
             addSubview(placeholderLabel)
             sendSubviewToBack(placeholderLabel)
-        } else {
-            placeholderLabel.removeFromSuperview()
         }
     }
     

+ 11 - 0
bugu/bugu/IMSDK/Private/DataBase/IMDatabaseManager+Group.swift

@@ -332,6 +332,17 @@ extension IMDatabaseManager {
         return list
     }
     
+    func findGroupRobotList(filterName: String? = nil) -> [GroupRobot]? {
+        var list: [GroupRobot]?
+        do {
+            list = try GroupRobotDataHelper.findGroupRobotList(groupId: nil, filterName: filterName)
+        } catch {
+            
+        }
+        
+        return list
+    }
+    
     /// 查询群机器人
     /// - Parameter id: 机器人id
     /// - Returns: GroupRobot

+ 11 - 0
bugu/bugu/IMSDK/Private/DataBase/IMDatabaseManager+Notification.swift

@@ -48,6 +48,17 @@ extension IMDatabaseManager {
         }
         return result
     }
+    
+    func cancelAllConversationNoDisturbing() -> Bool {
+        var result: Bool = true
+        
+        do {
+            try NotificationDataHelper.cancelAllConversationNoDisturbing()
+        } catch {
+            result = false
+        }
+        return result
+    }
 }
 
 

+ 6 - 5
bugu/bugu/IMSDK/Private/DataBase/SQLite/GroupRobot+Database.swift

@@ -187,15 +187,16 @@ struct GroupRobotDataHelper: DataHelperProtocol {
         }
     }
     
-    static func findGroupRobotList(groupId: Int, filterName: String? = nil) throws -> [T]? {
+    static func findGroupRobotList(groupId: Int?, filterName: String? = nil) throws -> [T]? {
         guard let db = Database.shared.connection else {
             throw DBError.connectionErr
         }
     
-        var query = table
-            .filter(self.groupId == groupId)
-            .filter(delete == false)
-            .order(name.asc)
+        var query = table.filter(delete == false)
+        if let groupId = groupId {
+            query = query.filter(self.groupId == groupId)
+        }
+        query = query.order(name.asc)
         if let filterName = filterName, filterName.isNotEmpty {
             query = query.filter(name.like("%\(filterName)%"))
         }

+ 17 - 0
bugu/bugu/IMSDK/Private/DataBase/SQLite/Notification+Database.swift

@@ -99,4 +99,21 @@ struct NotificationDataHelper: DataHelperProtocol {
         try db.run(query.delete())
     }
     
+    /// 取消全部会话免打扰
+    static func  cancelAllConversationNoDisturbing() throws {
+        guard let db = Database.shared.connection else {
+            throw DBError.connectionErr
+        }
+        
+        do {
+            // 开始事务,以确保删除操作是原子的
+            try db.transaction {
+                try db.run(table.delete())
+            }
+        } catch {
+            printLog("cancelAllConversationNoDisturbing error\(error)")
+            throw DBError.deleteErr
+        }
+    }
 }
+

+ 7 - 0
bugu/bugu/IMSDK/Private/IMClientManager+Group.swift

@@ -561,6 +561,13 @@ extension IMClientManager {
         return imdatabase.findGroupRobotList(groupId: groupId, filterName: filterName) ?? []
     }
     
+    /// 获取搜索机器人
+    /// - Parameter filterName: 关键字
+    /// - Returns: 机器人列表
+    func searchRobot(filterName: String? = nil) -> [GroupRobot] {
+        return imdatabase.findGroupRobotList(filterName: filterName) ?? []
+    }
+    
     /// 获取群机器人
     /// - Parameters:
     ///   - groupId: 群组id

+ 5 - 4
bugu/bugu/IMSDK/Private/IMClientManager+Message.swift

@@ -925,11 +925,12 @@ extension IMClientManager {
             
             func checkCompleted() {
                 guard count == webMessages.count else { return }
-                guard messages.count > 0 else { return }
-                self.callReceivedNewMessageListener(messages: messages, source: source)
+                completed()
+                
+                if messages.count > 0  {
+                    self.callReceivedNewMessageListener(messages: messages, source: source)
+                }
             }
-            
-            completed()
         }
     }
     

+ 12 - 8
bugu/bugu/IMSDK/Private/IMClientManager+Silent.swift

@@ -113,14 +113,18 @@ extension IMClientManager {
     func setNotificationSilentList(silentList: [MessageNotification]) -> Bool {
         
         var result: Bool = true
-        for notification in silentList {
-            guard let convId = notification.convId,
-                  let convType = notification.convType else {
-                continue
-            }
-            result = imdatabase.setConversationNoDisturbing(convId: convId, convType: convType)
-            if result == false {
-                break
+        if silentList.count == 0 {
+            result = imdatabase.cancelAllConversationNoDisturbing()
+        } else {
+            for notification in silentList {
+                guard let convId = notification.convId,
+                      let convType = notification.convType else {
+                    continue
+                }
+                result = imdatabase.setConversationNoDisturbing(convId: convId, convType: convType)
+                if result == false {
+                    break
+                }
             }
         }
         

+ 2 - 2
bugu/bugu/IMSDK/Private/MessageHandler/MessageHandler+FriendShip.swift

@@ -168,9 +168,9 @@ extension MessageHandler {
             guard let userId = message.sender.int else {  return true }
             
             /// 数据库查询用户信息
-            guard let account = imdatabase.getUserInfo(userId: userId) else { return true }
+            guard let account = imdatabase.getUserInfo(userId: userId), let result = message.content.int, let gender = AccountGender(rawValue: result) else { return true }
             
-            account.motto = message.content
+            account.gender = gender
             
             /// 数据库更新用户信息
             imdatabase.updateGender(userId: userId, gender: message.content)

+ 7 - 0
bugu/bugu/IMSDK/Public/IMClient/IMClient+Group.swift

@@ -215,6 +215,13 @@ extension IMClient {
         return IMClientManager.shared.getGroupRobotList(groupId: groupId, filterName: filterName)
     }
     
+    /// 获取搜索机器人
+    /// - Parameter filterName: 关键字
+    /// - Returns: 机器人列表
+    public static func searchRobot(filterName: String? = nil) -> [GroupRobot] {
+        return IMClientManager.shared.searchRobot(filterName: filterName)
+    }
+    
     /// 获取群机器人
     /// - Parameters:
     ///   - groupId: 群组id

+ 1 - 1
bugu/bugu/Modules/AddressBook/Group/Controller/GroupCreateRobotViewController.swift

@@ -187,7 +187,7 @@ extension GroupCreateRobotViewController {
         config.editImageConfiguration.tools = [.clip]
         config.editImageConfiguration.showClipDirectlyIfOnlyHasClipTool = true
         
-        let photoPicker = ZLPhotoPreviewSheet()
+        let photoPicker = ZLPhotoPicker()
         photoPicker.selectImageBlock = { [weak self] (models, _) in
             guard let self = self else { return }
             guard models.count > 0 else { return }

+ 1 - 1
bugu/bugu/Modules/AddressBook/Group/Controller/GroupInfoViewController.swift

@@ -220,7 +220,7 @@ extension GroupInfoViewController {
         config.editImageConfiguration.tools = [.clip]
         config.editImageConfiguration.showClipDirectlyIfOnlyHasClipTool = true
         
-        let photoPicker = ZLPhotoPreviewSheet()
+        let photoPicker = ZLPhotoPicker()
         photoPicker.selectImageBlock = { [weak self] (models, _) in
             guard let self = self else { return }
             guard models.count > 0 else { return }

+ 1 - 1
bugu/bugu/Modules/AddressBook/Group/Controller/GroupRobotDetailViewController.swift

@@ -324,7 +324,7 @@ extension GroupRobotDetailViewController {
         config.editImageConfiguration.tools = [.clip]
         config.editImageConfiguration.showClipDirectlyIfOnlyHasClipTool = true
         
-        let photoPicker = ZLPhotoPreviewSheet()
+        let photoPicker = ZLPhotoPicker()
         photoPicker.selectImageBlock = { [weak self] (models, _) in
             guard let self = self else { return }
             guard models.count > 0 else { return }

+ 1 - 0
bugu/bugu/Modules/AddressBook/PersonalCenter/Controller/PersonalCenterViewController.swift

@@ -178,6 +178,7 @@ class PersonalCenterViewController: HXBaseViewController {
         
         tableHeaderView.backgroundImageView.kfCache(userInfo.bannerURL?.absoluteString, placeholderImage: R.image.persopn_detailed_banner())
         tableHeaderView.avatarView.kfAvatarRefresh(userInfo.avatarURL?.absoluteString)
+        tableHeaderView.mottoLabel.text = userInfo.motto
 
         let itemViews = viewModel.bottomItems.map({ [unowned self]  item -> PersonalBottomItemView in
             let view = PersonalBottomItemView()

+ 37 - 21
bugu/bugu/Modules/AddressBook/PersonalCenter/View/PersonalCenterHeaderView.swift

@@ -10,29 +10,13 @@ import DesignKit
 
 class PersonalCenterHeaderView: HXView {
 
-    lazy var backgroundImageView: UIImageView = {
-        let imageView = UIImageView()
-        imageView.contentMode = .scaleAspectFill
-        imageView.layer.masksToBounds = true
-        return imageView
-    }()
-    
-
-    
-    lazy var avatarView: UIImageView = {
-        let avatarView = UIImageView()
-        avatarView.contentMode = .scaleAspectFill
-        avatarView.setCornerRadius(40, nil)
-        avatarView.layer.borderColor = UIColor.white.cgColor
-        avatarView.layer.borderWidth = 2
-        avatarView.backgroundColor = R.color.foreground()
-        return avatarView
-    }()
-    
     override init(frame: CGRect) {
         super.init(frame: frame)
+        
         self.addSubview(backgroundImageView)
         self.addSubview(avatarView)
+        self.addSubview(mottoLabel)
+        
         backgroundImageView.snp.makeConstraints { make in
             make.top.leading.trailing.equalToSuperview()
             make.height.equalTo(225)
@@ -42,12 +26,44 @@ class PersonalCenterHeaderView: HXView {
             make.leading.equalTo(self.safeAreaLayoutGuide).offset(26)
             make.centerY.equalTo(backgroundImageView.snp.bottom)
             make.width.height.equalTo(80)
-            make.bottom.equalTo(self).offset(-24)
+        }
+
+        mottoLabel.snp.makeConstraints { make in
+            make.leading.equalTo(avatarView)
+            make.trailing.equalToSuperview().offset(-26)
+            make.top.equalTo(avatarView.snp.bottom).offset(12)
+            make.bottom.equalToSuperview().offset(-24)
         }
     }
     
     required public init?(coder aDecoder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
-    }    
+    }
+    
+    // MARK: - getters and setters
+    lazy var backgroundImageView: UIImageView = {
+        let imageView = UIImageView()
+        imageView.contentMode = .scaleAspectFill
+        imageView.layer.masksToBounds = true
+        return imageView
+    }()
+    
+    lazy var avatarView: UIImageView = {
+        let avatarView = UIImageView()
+        avatarView.contentMode = .scaleAspectFill
+        avatarView.setCornerRadius(40, nil)
+        avatarView.layer.borderColor = UIColor.white.cgColor
+        avatarView.layer.borderWidth = 2
+        avatarView.backgroundColor = R.color.foreground()
+        return avatarView
+    }()
+    
+    lazy var mottoLabel: UILabel = {
+        let mottoLabel = UILabel()
+        mottoLabel.font = .font(ofSize: 14)
+        mottoLabel.textColor = R.color.sub_title_color()
+        mottoLabel.textAlignment = .left
+        return mottoLabel
+    }()
 
 }

+ 1 - 1
bugu/bugu/Modules/Discover/Moment/Detail/MomentDetailViewController.swift

@@ -354,7 +354,7 @@ extension MomentDetailViewController {
     
     public func cleanReplyData() {
        self.lastSelectedIndexPath = nil
-       self.messageInputBar.inputTextView.attributedText = nil
+       self.messageInputBar.inputTextView.cleanTextView()
    }
     
     private func didTapComment(indexPath: IndexPath?) {

+ 1 - 1
bugu/bugu/Modules/Discover/Moment/Home/MomentHomeViewController.swift

@@ -212,7 +212,7 @@ extension MomentHomeViewController {
         self.lastCommentCellView = nil
         self.lastCell = nil
         self.lastComment = nil
-        self.messageInputBar.inputTextView.attributedText = nil
+        self.messageInputBar.inputTextView.cleanTextView()
     }
     
 }

+ 1 - 1
bugu/bugu/Modules/Discover/Moment/Manager/MomentChangeCoverManager.swift

@@ -52,7 +52,7 @@ extension MomentChangeCoverManager {
         config.allowMixSelect = false
         config.maxSelectCount = 1
         
-        let photoPicker = ZLPhotoPreviewSheet(selectedAssets: selectedAssets)
+        let photoPicker = ZLPhotoPicker(selectedAssets: selectedAssets)
         photoPicker.selectImageBlock = { (models, _) in
             uploadCover(models: models)
         }

+ 1 - 1
bugu/bugu/Modules/Discover/Moment/Manager/MomentPushlishManager.swift

@@ -47,7 +47,7 @@ extension MomentPushlishManager {
         config.maxSelectCount = 9
         config.maxEditVideoTime = 15
         
-        let photoPicker = ZLPhotoPreviewSheet(selectedAssets: selectedAssets)
+        let photoPicker = ZLPhotoPicker(selectedAssets: selectedAssets)
         photoPicker.selectImageBlock = { (models, _) in
             callback?(models)
         }

+ 7 - 5
bugu/bugu/Modules/IM/Audio/HXAudioController.swift

@@ -78,7 +78,7 @@ open class HXAudioController: NSObject, AVAudioPlayerDelegate {
     /// - Parameters:
     ///   - message: The `MessageType` that contain the audio item to be played.
     ///   - audioCell: The `AudioMessageCell` that needs to be updated while audio is playing.
-    open func playSound(for messageViewModel: MessageViewModel, in audioCell: HXAudioMessageContentCell) {
+    open func playSound(for messageViewModel: MessageViewModel, in audioCell: HXAudioMessageContentCell?) {
         let message = messageViewModel.message
         switch message.kind {
         case .audio(let item):
@@ -105,9 +105,11 @@ open class HXAudioController: NSObject, AVAudioPlayerDelegate {
             audioPlayer?.delegate = self
             audioPlayer?.play()
             state = .playing
-            audioCell.imageView.startAnimating()
             startProgressTimer()
-            audioCell.delegate?.didStartAudio(in: audioCell)
+            if let audioCell = audioCell {
+                audioCell.imageView.startAnimating()
+                audioCell.delegate?.didStartAudio(in: audioCell)
+            }
         default:
             print("BasicAudioPlayer failed play sound because given message kind is not Audio")
         }
@@ -118,10 +120,10 @@ open class HXAudioController: NSObject, AVAudioPlayerDelegate {
     /// - Parameters:
     ///   - message: The `MessageType` that contain the audio item to be pause.
     ///   - audioCell: The `AudioMessageCell` that needs to be updated by the pause action.
-    open func pauseSound(for messageViewModel: MessageViewModel, in audioCell: HXAudioMessageContentCell) {
+    open func pauseSound(for messageViewModel: MessageViewModel, in audioCell: HXAudioMessageContentCell?) {
         audioPlayer?.pause()
         state = .pause
-        audioCell.imageView.stopAnimating()
+        audioCell?.imageView.stopAnimating()
         progressTimer?.invalidate()
         if let cell = playingCell {
             cell.delegate?.didPauseAudio(in: cell)

+ 1 - 1
bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+Keyboard.swift

@@ -46,7 +46,7 @@ extension ChatViewController {
         if plainText.isNotEmpty {
             self.inputBar(self.messageInputBar, didPressSendButtonWithText: plainText)
         }
-        self.messageInputBar.inputTextView.attributedText = nil
+        self.messageInputBar.inputTextView.cleanTextView()
         self.messageInputBar.removeQuote()
     }
     

+ 27 - 7
bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+Menu.swift

@@ -68,17 +68,38 @@ extension ChatViewController {
         let lines = (items.count / lineMax ) + ((items.count % lineMax) == 0 ? 0 : 1)
         let height: CGFloat = CGFloat(lines) * itemHeight
         
-        let menuArrow = getMenuDirection(selectedIndexPath: indexPath, menuHeight: height)
+        let menuDirection = getMenuDirection(selectedIndexPath: indexPath, menuHeight: height)
         let popover = Popover(options: nil, showHandler: nil, dismissHandler: nil)
-        popover.popoverType = menuArrow == .up ? .down : .up
+        popover.popoverType = menuDirection == .up ? .down : .up
         popover.popoverColor = popBackgroundColor
         popover.blackOverlayColor = .clear
         
         let view = createMenuVoew(popover: popover, items: items, message: message, height: height)
-        popover.show(view, fromView: cell.messageContainerView)
+        guard let inView = UIApplication.shared.keyWindow else {
+            return
+        }
+        
+        let fromView = cell.messageContainerView
+        
+        var point: CGPoint
+        if popover.popoverType == .down {
+            point = inView.convert(
+                CGPoint(
+                    x: fromView.frame.origin.x + (fromView.frame.size.width / 2),
+                    y: fromView.frame.origin.y + fromView.frame.size.height
+                ), from: fromView.superview)
+        } else {
+            point = inView.convert(
+                CGPoint(
+                    x: fromView.frame.origin.x + (fromView.frame.size.width / 2),
+                    y: fromView.frame.origin.y
+                ), from: fromView.superview)
+            if point.y < (height + 30) {
+                point = CGPoint(x: fromView.frame.origin.x + (fromView.frame.size.width / 2), y: ScreenHeight / 5 * 2)
+            }
+        }
+        popover.show(view, point: point, inView: inView)
     }
-    
-    
 }
 
 extension ChatViewController {
@@ -115,8 +136,7 @@ extension ChatViewController {
         
         
         /// Message bubble intersects with navigationBar and keyboard
-        if
-            selectedCellMessageBubblePlusMenuFrame.intersects(topNavigationBarFrame),
+        if selectedCellMessageBubblePlusMenuFrame.intersects(topNavigationBarFrame),
             selectedCellMessageBubblePlusMenuFrame.intersects(messageInputBarFrame) {
             return .down
             

+ 222 - 172
bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+MessageCellDelegate.swift

@@ -97,6 +97,122 @@ extension ChatViewController: MessageCellDelegate {
             print("Failed to identify message when audio cell receive tap gesture")
             return
         }
+        didTapMessage(in: cell, message: messageViewModel.message)
+    }
+    
+    func didLongTapMessage(in cell: MessageCollectionViewCell) {
+        if viewModel.chatInputState == .multiSelected {
+            return
+        }
+        
+        showMenu(cell: cell as! HXMessageContentCell)
+    }
+    
+    public func didTapCellTopLabel(in cell: MessageCollectionViewCell) {
+        self.view.endEditing(true)
+    }
+    
+    public func didTapBackground(in cell: MessageCollectionViewCell) {
+        self.view.endEditing(true)
+        
+        guard
+            let indexPath = messagesCollectionView.indexPath(for: cell),
+            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView)
+        else {
+            print("Failed to identify didTapBackground message when cell receive tap gesture")
+            return
+        }
+        
+        if messageViewModel.isMultiSelected {
+            viewModel.update(message: messageViewModel.message, selected: !messageViewModel.isSelected)
+        }
+    }
+    
+    func didTapCellBottomLabel(in _: MessageCollectionViewCell) {
+        print("Bottom cell label tapped")
+    }
+    
+    func didTapMessageTopLabel(in _: MessageCollectionViewCell) {
+        print("Top message label tapped")
+    }
+    
+    func didTapMessageBottomLabel(in _: MessageCollectionViewCell) {
+        print("Bottom label tapped")
+    }
+    
+    
+    func didStartAudio(in _: HXAudioMessageContentCell) {
+        print("Did start playing audio sound")
+    }
+    
+    func didPauseAudio(in _: HXAudioMessageContentCell) {
+        print("Did pause audio sound")
+    }
+    
+    func didStopAudio(in _: HXAudioMessageContentCell) {
+        print("Did stop audio sound")
+    }
+    
+    func didTapAccessoryView(in _: MessageCollectionViewCell) {
+        print("Accessory view tapped")
+    }
+    
+    func didSelectPhoneNumber(_ phoneNumber: String) {
+        printLog("")
+    }
+    
+    func didTapResend(in cell: MessageCollectionViewCell) {
+        guard
+            let indexPath = messagesCollectionView.indexPath(for: cell),
+            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView) else {
+            return
+        }
+        
+        if viewModel.chatInputState == .multiSelected {
+            return
+        }
+        
+        let message = messageViewModel.message
+        let infos = MessageAttachmentProvider.fileUploadInfos(of: message.kind)
+        viewModel.resendMessage(message, infos: infos)
+    }
+    
+    func didTapCloseMessageTimeView(in cell: MessageCollectionViewCell) {
+        guard
+            let indexPath = messagesCollectionView.indexPath(for: cell),
+            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView) else {
+            return
+        }
+        
+        if viewModel.chatInputState == .multiSelected {
+            return
+        }
+        
+        viewModel.cleanShowMessageTime(messageId: messageViewModel.message.messageId)
+    }
+    
+    /// 点击了引用消息
+    func didTapQuoteMessageView(in cell: MessageCollectionViewCell) {
+        self.view.endEditing(true)
+        guard
+            let indexPath = messagesCollectionView.indexPath(for: cell),
+            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView) else {
+            return
+        }
+        
+        guard let replyMessage = messageViewModel.replyMessage else {
+            return
+        }
+        
+        didTapMessage(in: nil, message: replyMessage)
+    }
+    
+    func didTapReadReceipt(in cell: MessageCollectionViewCell) {
+        guard
+            let indexPath = messagesCollectionView.indexPath(for: cell),
+            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView) else {
+            return
+        }
         
         if viewModel.chatInputState == .multiSelected {
             return
@@ -104,20 +220,67 @@ extension ChatViewController: MessageCellDelegate {
         
         let message = messageViewModel.message
         
+        let targetId: Int?
+        switch message.conversationType {
+        case .single:
+            targetId = message.userId
+        case .system:
+            return
+        case .group:
+            targetId = message.groupId
+        case .microServer:
+            return
+        }
+        
+        guard let targetId = targetId else { return }
+        self.navigator.show(segue: .readReceipt(messageUid: message.id, targetId: targetId, convType: message.conversationType), sender: self)
+    }
+    
+    func didTapRewriteText(in cell: MessageCollectionViewCell) {
+        guard
+            let indexPath = messagesCollectionView.indexPath(for: cell),
+            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView),
+            case .text(let textElem) = messageViewModel.message.messageKind else {
+            return
+        }
+        
+        guard viewModel.chatInputState == .normal else {
+            return
+        }
+        
+        if viewModel.chatInputState == .multiSelected {
+            return
+        }
+        
+        messageInputBar.appendTextView(textElem.text)
+        
+    }
+}
+
+// MARK: - tap message
+extension ChatViewController {
+    
+    private func didTapMessage(in cell: MessageCollectionViewCell?, message: Message) {
+        if viewModel.chatInputState == .multiSelected {
+            return
+        }
+        
         switch message.kind {
-        case .text:
+        case .text(let textElem):
+            let controller = TextPreviewViewController(navigator: self.navigator, text: textElem.text)
+            self.present(controller, animated: false)
             break
         case .image:
-            break
+            didTapImage(in: cell, message: message)
         case .video:
-            break
+            didTapImage(in: cell, message: message)
         case .location(let locationItem):
             self.navigator.show(segue: .locationPreview(location: locationItem), sender: self)
         case .emoticon(let emoticonItem):
             let item = EmoticonItem(id: emoticonItem.itemId, emoticonId: emoticonItem.id, name: emoticonItem.name, type: emoticonItem.type)
             self.navigator.show(segue: .emoticonPreview(emoticonItem: item), sender: self)
         case .audio:
-            didTapPlayAudio(in: cell as! HXAudioMessageContentCell)
+            didTapPlayAudio(in: cell as? HXAudioMessageContentCell, message: message)
         case .contact(let contactItem):
             switch contactItem.type {
             case .contact:
@@ -159,30 +322,13 @@ extension ChatViewController: MessageCellDelegate {
         }
     }
     
-    func didLongTapMessage(in cell: MessageCollectionViewCell) {
-        if viewModel.chatInputState == .multiSelected {
-            return
-        }
-        
-        showMenu(cell: cell as! HXMessageContentCell)
-    }
-    
-    func didTapImage(in cell: MessageCollectionViewCell) {
+    private func didTapImage(in cell: MessageCollectionViewCell?, message: Message) {
         print("Image tapped")
-        guard
-            let indexPath = messagesCollectionView.indexPath(for: cell),
-            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView)
-        else {
-            print("Failed to identify message when audio cell receive tap gesture")
-            return
-        }
-        
+
         if viewModel.chatInputState == .multiSelected {
             return
         }
         
-        let message = messageViewModel.message
-        
         if case .image(_) = message.kind  {
             
             viewModel.getHistoryImageMessageList { [weak self] messages in
@@ -280,8 +426,28 @@ extension ChatViewController: MessageCellDelegate {
             }
             
             // 更丝滑的Zoom动画
-            lantern.transitionAnimator = LanternSmoothZoomAnimator(transitionViewAndFrame: { (index, destinationView) -> LanternSmoothZoomAnimator.TransitionViewAndFrame? in
-                guard let imageView = (cell as? HXVideoMessageContentCell)?.imageView else {
+            lantern.transitionAnimator = LanternSmoothZoomAnimator(transitionViewAndFrame: { [weak self] (index, destinationView) -> LanternSmoothZoomAnimator.TransitionViewAndFrame? in
+                guard let self = self else { return  nil }
+                
+                let fromCell: HXVideoMessageContentCell?
+                if let cell = cell as? HXVideoMessageContentCell {
+                    fromCell = cell
+                } else {
+                    guard let index = viewModel.messages.firstIndex(where: { $0.message.id == message.id }),
+                          let cell = messagesCollectionView.visibleCells.first(where: { cell in
+                              guard let cell = (cell as? HXVideoMessageContentCell) else { return false }
+                              guard let indexPath = self.messagesCollectionView.indexPath(for: cell) else {
+                                  return false
+                              }
+                              return indexPath.row == index
+                          })
+                    else {
+                        return nil
+                    }
+                    fromCell = cell as? HXVideoMessageContentCell
+                }
+                
+                guard let imageView = fromCell?.imageView else {
                     return nil
                 }
                 let image = imageView.image
@@ -300,39 +466,7 @@ extension ChatViewController: MessageCellDelegate {
         fatalError("Configuring LocationMessageCell with wrong")
     }
     
-    public func didTapCellTopLabel(in cell: MessageCollectionViewCell) {
-        self.view.endEditing(true)
-    }
-    
-    public func didTapBackground(in cell: MessageCollectionViewCell) {
-        self.view.endEditing(true)
-        
-        guard
-            let indexPath = messagesCollectionView.indexPath(for: cell),
-            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView)
-        else {
-            print("Failed to identify didTapBackground message when cell receive tap gesture")
-            return
-        }
-        
-        if messageViewModel.isMultiSelected {
-            viewModel.update(message: messageViewModel.message, selected: !messageViewModel.isSelected)
-        }
-    }
-    
-    func didTapCellBottomLabel(in _: MessageCollectionViewCell) {
-        print("Bottom cell label tapped")
-    }
-    
-    func didTapMessageTopLabel(in _: MessageCollectionViewCell) {
-        print("Top message label tapped")
-    }
-    
-    func didTapMessageBottomLabel(in _: MessageCollectionViewCell) {
-        print("Bottom label tapped")
-    }
-    
-    func didTapPlayAudio(in cell: HXAudioMessageContentCell) {
+    private func didTapPlayAudio(in cell: HXAudioMessageContentCell?, message: Message) {
         if viewModel.chatInputState == .multiSelected {
             return
         }
@@ -342,12 +476,30 @@ extension ChatViewController: MessageCellDelegate {
             return
         }
         
-        guard
-            let indexPath = messagesCollectionView.indexPath(for: cell),
-            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView)
-        else {
-            print("Failed to identify message when audio cell receive tap gesture")
-            return
+        let messageViewModel: MessageViewModel
+        let playCell: HXAudioMessageContentCell?
+        
+        if let cell = cell,
+           let indexPath = messagesCollectionView.indexPath(for: cell),
+           let viewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView)  {
+            playCell = cell
+            messageViewModel = viewModel
+        } else {
+            if let index = viewModel.messages.firstIndex(where: { $0.message.id == message.id }) {
+                /// 数据中找到这个 message 了(来自引用)
+                let cell = messagesCollectionView.visibleCells.first(where: { cell in
+                    guard let cell = (cell as? HXAudioMessageContentCell) else { return false }
+                    guard let indexPath = self.messagesCollectionView.indexPath(for: cell) else {
+                        return false
+                    }
+                    return indexPath.row == index
+                })
+                playCell = cell as? HXAudioMessageContentCell
+                messageViewModel = viewModel.messages[index]
+            } else {
+                playCell = nil
+                messageViewModel = MessageViewModel(message: message)
+            }
         }
         
         let message = messageViewModel.message
@@ -357,10 +509,10 @@ extension ChatViewController: MessageCellDelegate {
         }
         
         viewModel.audioMessagePlayed(messageViewModel: messageViewModel)
-        cell.unReadIcon.isHidden = true
+        playCell?.unReadIcon.isHidden = true
         
         guard let localPath = audioItem.localPath, FileManager.default.fileExists(atPath: localPath) else {
-            cell.activityIndicatorView.startAnimating()
+            playCell?.activityIndicatorView.startAnimating()
             
             let fileKey = audioItem.file
             guard fileKey.isNotEmpty else {
@@ -381,12 +533,12 @@ extension ChatViewController: MessageCellDelegate {
             let info = MessageDownloadInfo(bucket: "\(bucket)", fileKey: fileKey, fileName: fileKey, fileType: .audio)
             manager.prepareDownloadTask(messageId: messageId, info: info) { [weak self] result in
                 DispatchQueue.main.async {
-                    cell.activityIndicatorView.stopAnimating()
+                    playCell?.activityIndicatorView.stopAnimating()
                     
                     guard let self = self else { return }
                     switch result {
                     case .success:
-                        self.playSound(messageViewModel: messageViewModel, cell: cell)
+                        self.playSound(messageViewModel: messageViewModel, cell: playCell)
                     case .failed:
                         break
                     case.cancel:
@@ -398,112 +550,10 @@ extension ChatViewController: MessageCellDelegate {
             return
         }
         
-        self.playSound(messageViewModel: messageViewModel, cell: cell)
+        self.playSound(messageViewModel: messageViewModel, cell: playCell)
     }
     
-    func didStartAudio(in _: HXAudioMessageContentCell) {
-        print("Did start playing audio sound")
-    }
-    
-    func didPauseAudio(in _: HXAudioMessageContentCell) {
-        print("Did pause audio sound")
-    }
-    
-    func didStopAudio(in _: HXAudioMessageContentCell) {
-        print("Did stop audio sound")
-    }
-    
-    func didTapAccessoryView(in _: MessageCollectionViewCell) {
-        print("Accessory view tapped")
-    }
-    
-    func didSelectPhoneNumber(_ phoneNumber: String) {
-        printLog("")
-    }
-    
-    func didTapResend(in cell: MessageCollectionViewCell) {
-        guard
-            let indexPath = messagesCollectionView.indexPath(for: cell),
-            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView) else {
-            return
-        }
-        
-        if viewModel.chatInputState == .multiSelected {
-            return
-        }
-        
-        let message = messageViewModel.message
-        let infos = MessageAttachmentProvider.fileUploadInfos(of: message.kind)
-        viewModel.resendMessage(message, infos: infos)
-    }
-    
-    func didTapCloseMessageTimeView(in cell: MessageCollectionViewCell) {
-        guard
-            let indexPath = messagesCollectionView.indexPath(for: cell),
-            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView) else {
-            return
-        }
-        
-        if viewModel.chatInputState == .multiSelected {
-            return
-        }
-        
-        viewModel.cleanShowMessageTime(messageId: messageViewModel.message.messageId)
-    }
-    
-    func didTapReadReceipt(in cell: MessageCollectionViewCell) {
-        guard
-            let indexPath = messagesCollectionView.indexPath(for: cell),
-            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView) else {
-            return
-        }
-        
-        if viewModel.chatInputState == .multiSelected {
-            return
-        }
-        
-        let message = messageViewModel.message
-        
-        let targetId: Int?
-        switch message.conversationType {
-        case .single:
-            targetId = message.userId
-        case .system:
-            return
-        case .group:
-            targetId = message.groupId
-        case .microServer:
-            return
-        }
-        
-        guard let targetId = targetId else { return }
-        self.navigator.show(segue: .readReceipt(messageUid: message.id, targetId: targetId, convType: message.conversationType), sender: self)
-    }
-    
-    func didTapRewriteText(in cell: MessageCollectionViewCell) {
-        guard
-            let indexPath = messagesCollectionView.indexPath(for: cell),
-            let messageViewModel = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView),
-            case .text(let textElem) = messageViewModel.message.messageKind else {
-            return
-        }
-        
-        guard viewModel.chatInputState == .normal else {
-            return
-        }
-        
-        if viewModel.chatInputState == .multiSelected {
-            return
-        }
-        
-        messageInputBar.appendTextView(textElem.text)
-        
-    }
-}
-
-extension ChatViewController {
-    
-    private func playSound(messageViewModel: MessageViewModel, cell: HXAudioMessageContentCell) {
+    private func playSound(messageViewModel: MessageViewModel, cell: HXAudioMessageContentCell?) {
         guard audioController.state != .stopped else {
             // There is no audio sound playing - prepare to start playing for given audio message
             audioController.playSound(for: messageViewModel, in: cell)

+ 2 - 2
bugu/bugu/Modules/IM/Chat/Controller/ChatViewController+MoreKeyboard.swift

@@ -206,7 +206,7 @@ extension ChatViewController {
         config.maxSelectCount = 9
         config.maxEditVideoTime = 15
         
-        let photoPicker = ZLPhotoPreviewSheet()
+        let photoPicker = ZLPhotoPicker()
         photoPicker.selectImageBlock = { [weak self] (models, _) in
             guard let self = self else { return }
             guard models.count > 0 else { return }
@@ -227,7 +227,7 @@ extension ChatViewController {
                         
                         let videoKey = "\(UUID().uuidString).mp4"
                         let videoLocalPath = Account.videoPath.appendingPathComponent(videoKey)
-                        ZLVideoManager.exportVideo(for: asset, exportType: .mp4, presetName: AVAssetExportPreset640x480) { url, error in
+                        ZLVideoManager.exportVideo(for: asset, exportType: .mp4, presetName: AVAssetExportPreset1920x1080) { url, error in
                             do {
                                 if let url = url {
                                     let destURL = URL(fileURLWithPath: videoLocalPath)

+ 1 - 1
bugu/bugu/Modules/IM/Chat/Controller/ChatViewController.swift

@@ -354,7 +354,7 @@ extension ChatViewController {
     private func disableInputBar() {
         messageInputBar.isHidden = true
         
-        messageInputBar.inputTextView.attributedText = nil
+        messageInputBar.inputTextView.cleanTextView()
         messageInputBar.inputTextView.resignFirstResponder()
         //把输入框(如果有的话)绑定给管理类
         keyboardManager.bindTextView(nil)

+ 1 - 5
bugu/bugu/Modules/IM/Chat/Display/MessageElem+Display.swift

@@ -84,11 +84,7 @@ extension TextMessageElem {
         }
         
         var types: NSTextCheckingResult.CheckingType = []
-        if attributedString.length < 100 {
-            types.insert(.phoneNumber)
- 
-        }
-
+        types.insert(.phoneNumber)
         types.insert(.link)
         
         guard let detector = try? NSDataDetector(types: types.rawValue) else {

+ 1 - 0
bugu/bugu/Modules/IM/Chat/Manager/MessageSendManager.swift

@@ -20,6 +20,7 @@ extension MessageSendManager {
     ///   - message: 消息对象
     ///   - infos: 媒体上传资源
     ///   - progress: 上传进度
+    ///   - displayed: 是否需要屏幕展示
     ///   - completion: 发送成功
     public func send(message: Message,
                      conversationId: String,

+ 41 - 3
bugu/bugu/Modules/IM/Chat/Manager/Register/MessageSignalingUpdateManager.swift

@@ -72,7 +72,21 @@ extension MessageSignalingUpdateManager: MicroServiceListener {
 extension MessageSignalingUpdateManager: FriendshipListener {
     
     func friendProfileChanged(user: Account, type: HXUserInfoChangeType) {
-        if type == .cover, let userId = user.userId {
+        guard let userId = user.userId else {
+            return
+        }
+        switch type {
+        case .avatar:
+            UIImageView.cleanKey(url: RemoteImage.avatar(path: "\(userId)"))
+        case .nickName:
+            break
+        case .motto:
+            break
+        case .email:
+            break
+        case .gender:
+            break
+        case .cover:
             UIImageView.cleanKey(url: RemoteImage.userBanner(path: "\(userId)"))
         }
     }
@@ -82,9 +96,33 @@ extension MessageSignalingUpdateManager: FriendshipListener {
 extension MessageSignalingUpdateManager: GroupListener {
     
     func groupInfoChanged(groupId: Int, opUser: GroupMember, changeInfo: GroupChangeInfo) {
-        
-        if case .avatar  = changeInfo.changeValue {
+        switch changeInfo.changeValue {
+        case .name(_):
+            break
+        case .notice(_):
+            break
+        case .allMute(_):
+            break
+        case .ownerChange(_):
+            break
+        case .avatar:
             UIImageView.cleanKey(url: RemoteImage.groupAvatar(path: "\(groupId)"))
         }
     }
+    
+    func groupRobotInfoChanged(groupId: Int, changeInfo: GroupRobotChangeInfo) {
+        switch changeInfo.changeValue {
+        case .created(_):
+            break
+        case .deleted(_):
+            break
+        case .info:
+            break
+        case .avatar:
+            UIImageView.cleanKey(url: RemoteImage.robotAvatar(path: "\(groupId)"))
+        case .status(_):
+            break
+        }
+    }
 }
+

+ 5 - 12
bugu/bugu/Modules/IM/Chat/Protocols/MessageCellDelegate.swift

@@ -113,16 +113,6 @@ public protocol MessageCellDelegate: AnyObject  {
     /// method `messageForItem(at:indexPath:messagesCollectionView)`.
     func didTapAccessoryView(in cell: MessageCollectionViewCell)
     
-    /// Triggered when a tap occurs on the image.
-    ///
-    /// - Parameters:
-    ///   - cell: The image where the touch occurred.
-    ///
-    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
-    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
-    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
-    func didTapImage(in cell: MessageCollectionViewCell)
-    
     /// Triggered when audio player start playing audio.
     ///
     /// - Parameters:
@@ -159,6 +149,9 @@ public protocol MessageCellDelegate: AnyObject  {
     /// 点击关闭消息时间
     func didTapCloseMessageTimeView(in cell: MessageCollectionViewCell)
     
+    /// 点击了引用消息
+    func didTapQuoteMessageView(in cell: MessageCollectionViewCell)
+    
     /// 点击了已读
     func didTapReadReceipt(in cell: MessageCollectionViewCell)
     
@@ -183,8 +176,6 @@ extension MessageCellDelegate {
     
     public func didTapMessageTopLabel(in _: MessageCollectionViewCell) { }
     
-    public func didTapImage(in _: MessageCollectionViewCell) { }
-    
     public func didStartAudio(in _: HXAudioMessageContentCell) { }
     
     public func didPauseAudio(in _: HXAudioMessageContentCell) { }
@@ -199,6 +190,8 @@ extension MessageCellDelegate {
     
     public func didTapCloseMessageTimeView(in cell: MessageCollectionViewCell) { }
     
+    public func didTapQuoteMessageView(in cell: MessageCollectionViewCell) { }
+    
     public func didTapReadReceipt(in cell: MessageCollectionViewCell) { }
     
     public func didTapRewriteText(in cell: MessageCollectionViewCell) { } 

+ 1 - 0
bugu/bugu/Modules/IM/Chat/View/Input/EmojiKeyboard/EmojiHelper.swift

@@ -121,6 +121,7 @@ struct EmojiHelper {
         textView.textStorage.insert(emotionAttributedString, at: textView.selectedRange.location)
         textView.textStorage.addAttributes([NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: textView.textColor!], range: NSRange(location: textView.selectedRange.location, length: emotionAttributedString.length))
         textView.selectedRange = NSRange(location: textView.selectedRange.location + 1, length: textView.selectedRange.length)
+        textView.delegate?.textViewDidChange?(textView)
     }
     
     static func getPlanText(attributedText: NSAttributedString) -> String {

+ 57 - 75
bugu/bugu/Modules/IM/Chat/View/Input/HXInputMessageBar.swift

@@ -65,14 +65,13 @@ class HXInputMessageBar: UIView {
     private var inputedText: NSAttributedString?
     
     /// 记录旧的textView Height
-    private var previousTextViewContentHeight: CGFloat = 0.0
+    private lazy var previousTextViewContentHeight: CGFloat = {
+        return getTextViewContentHeight()
+    }()
     
     /// 刚才清空文本框是因为点击了”发送“按钮。加入这个全局变量是为了Delegate的heightDidChange方法可以回调特殊的返回值
     private var clearInputTextBySendSoon: Bool = false
     
-    /// Key-value Observing (KVO)
-    private var textViewKVOContext = 0
-    
     private var showMenuBar: Bool = false
     
     // MARK: - life cycle
@@ -87,68 +86,6 @@ class HXInputMessageBar: UIView {
     }
     
     
-    // MARK: - override
-    // Observing changes in KVO
-    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
-        guard let keyPath = keyPath, let object = object as? UITextView, keyPath == "contentSize" else {
-            // 不是我们需要的观察者对象或者键路径,直接返回
-            return
-        }
-        
-        let textView = object
-        let newContentHeight = getTextViewContentHeight()
-        
-        // true表示tv行数减少了
-        let heightShouldShrink = newContentHeight < previousTextViewContentHeight
-        
-        // 由于内容的输入,tv应该发生高度的变化,这个值就是应该tv改变的高度差
-        var textViewHeightShouldChangeValue = newContentHeight - previousTextViewContentHeight
-        
-        // 要根据max和minheight重新计算一下高度变化差
-        if !heightShouldShrink && (previousTextViewContentHeight == MessageInputBarConfiguration.Space.textViewMaxHeight || textView.text.isEmpty) {
-            textViewHeightShouldChangeValue = 0
-        } else {
-            textViewHeightShouldChangeValue = min(textViewHeightShouldChangeValue, MessageInputBarConfiguration.Space.textViewMaxHeight - previousTextViewContentHeight)
-        }
-        
-        if textViewHeightShouldChangeValue != 0.0 {
-            // textView的高度有所改变
-            
-            UIView.animate(
-                withDuration: clearInputTextBySendSoon ? MessageInputBarConfiguration.AnimationDuration.inputBarHeightChangeWhenSend : MessageInputBarConfiguration.AnimationDuration.inputBarHeightChange,
-                animations: { [weak self] in
-                    guard let self = self else { return }
-                    
-                    // 设置本bar的frame
-                    let inputViewFrame = frame
-                    frame = CGRect(x: inputViewFrame.origin.x,
-                                   y: inputViewFrame.origin.y - textViewHeightShouldChangeValue,
-                                   width: inputViewFrame.size.width,
-                                   height: inputViewFrame.size.height + textViewHeightShouldChangeValue)
-                    
-                    resetTextViewHeight(by: textViewHeightShouldChangeValue)
-                    
-                    // 回调给QKeyboardManager
-                    delegate?.inputBarView(self, inputTextView: inputTextView, heightDidChange: textViewHeightShouldChangeValue, becauseSendText: clearInputTextBySendSoon)
-                    
-                }, completion: { finished in })
-            previousTextViewContentHeight = min(newContentHeight, MessageInputBarConfiguration.Space.textViewMaxHeight)
-        }
-        
-        // 这一句可以不写,为了保险还是写了
-        clearInputTextBySendSoon = false
-        
-        // 达到最大高度的时候(无论textView的高度是否有所改变),要更新tv的ContentOffset,让他滚起来
-        if previousTextViewContentHeight == MessageInputBarConfiguration.Space.textViewMaxHeight {
-            let delayInSeconds = 0.01
-            let popTime = DispatchTime.now() + delayInSeconds
-            DispatchQueue.main.asyncAfter(deadline: popTime) {
-                let bottomOffset = CGPoint(x: 0.0, y: newContentHeight - textView.bounds.size.height)
-                textView.setContentOffset(bottomOffset, animated: true)
-            }
-        }
-    }
-    
     // MARK: - getters and setters
     private lazy var inputBarContainerView: UIView = {
         let inputBarContainerView = UIView()
@@ -215,7 +152,6 @@ class HXInputMessageBar: UIView {
         textView.enablesReturnKeyAutomatically = true
         textView.autoresizingMask = [.flexibleWidth]
         textView.delegate = self
-        textView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
         return textView
     }()
     
@@ -412,14 +348,6 @@ extension HXInputMessageBar {
         return CGFloat(ceilf(inputTextView.sizeThatFits(inputTextView.frame.size).height.float))
     }
 
-    private func observeTextViewContentSize() {
-        inputTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: &textViewKVOContext)
-    }
-    
-    private func removeTextViewContentSizeObserver() {
-        inputTextView.removeObserver(self, forKeyPath: "contentSize", context: &textViewKVOContext)
-    }
-
     private func configMenuView(_ menuView: HXInputMenuView) {
         if let menus = datasource?.dataOfMenus(in: self) {
             menuBarContainerView.initialize(menus: menus)
@@ -469,6 +397,58 @@ extension HXInputMessageBar {
         delegate?.inputBarView(self, inputTextView: inputTextView, heightDidChange: textViewHeightShouldChangeValue, becauseSendText: clearInputTextBySendSoon)
         self.bottomView.superview?.layoutIfNeeded()
     }
+    
+    private func inputBarTextDidChange(_ textView: UITextView) {
+        let newContentHeight = getTextViewContentHeight()
+        
+        // true表示tv行数减少了
+        let heightShouldShrink = newContentHeight < previousTextViewContentHeight
+        
+        // 由于内容的输入,tv应该发生高度的变化,这个值就是应该tv改变的高度差
+        var textViewHeightShouldChangeValue = newContentHeight - previousTextViewContentHeight
+        
+        // 要根据max和minheight重新计算一下高度变化差
+        if !heightShouldShrink && (previousTextViewContentHeight == MessageInputBarConfiguration.Space.textViewMaxHeight || textView.text.isEmpty) {
+            textViewHeightShouldChangeValue = 0
+        } else {
+            textViewHeightShouldChangeValue = min(textViewHeightShouldChangeValue, MessageInputBarConfiguration.Space.textViewMaxHeight - previousTextViewContentHeight)
+        }
+        
+        if textViewHeightShouldChangeValue != 0.0 {
+            // textView的高度有所改变
+            
+            UIView.animate(
+                withDuration: clearInputTextBySendSoon ? MessageInputBarConfiguration.AnimationDuration.inputBarHeightChangeWhenSend : MessageInputBarConfiguration.AnimationDuration.inputBarHeightChange,
+                animations: { [weak self] in
+                    guard let self = self else { return }
+                    
+                    // 设置本bar的frame
+                    let inputViewFrame = frame
+                    frame = CGRect(x: inputViewFrame.origin.x,
+                                   y: inputViewFrame.origin.y - textViewHeightShouldChangeValue,
+                                   width: inputViewFrame.size.width,
+                                   height: inputViewFrame.size.height + textViewHeightShouldChangeValue)
+                    
+                    resetTextViewHeight(by: textViewHeightShouldChangeValue)
+                    
+                    // 回调给QKeyboardManager
+                    delegate?.inputBarView(self, inputTextView: inputTextView, heightDidChange: textViewHeightShouldChangeValue, becauseSendText: clearInputTextBySendSoon)
+                    
+                }, completion: { finished in })
+            previousTextViewContentHeight = min(newContentHeight, MessageInputBarConfiguration.Space.textViewMaxHeight)
+        }
+        
+        // 这一句可以不写,为了保险还是写了
+        clearInputTextBySendSoon = false
+        
+        // 达到最大高度的时候(无论textView的高度是否有所改变),要更新tv的ContentOffset,让他滚起来
+        if previousTextViewContentHeight == MessageInputBarConfiguration.Space.textViewMaxHeight {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
+                let bottomOffset = CGPoint(x: 0.0, y: max(0, textView.contentSize.height - textView.bounds.size.height))
+                textView.setContentOffset(bottomOffset, animated: true)
+            }
+        }
+    }
 }
 
 
@@ -594,6 +574,7 @@ extension HXInputMessageBar {
 extension HXInputMessageBar: UITextViewDelegate {
     
     func textViewDidChange(_ textView: UITextView) {
+        inputBarTextDidChange(textView)
         autocompleteManager.textViewDidChange(textView)
         delegate?.inputBarView(self, textViewDidChange: inputTextView)
     }
@@ -602,6 +583,7 @@ extension HXInputMessageBar: UITextViewDelegate {
         if keyboardSendEnabled && text == "\n" {
             removeQuote()
             delegate?.inputBarView(self, onKeyboardSendClick: inputTextView)
+            inputBarTextDidChange(textView)
             return false
         }
         

+ 8 - 0
bugu/bugu/Modules/IM/Chat/View/Input/TextView/HXInputTextView.swift

@@ -29,8 +29,14 @@ class HXInputTextView: HXTextView {
             let location = selectedRange.location + emojiText.length
             selectedRange = NSRange(location: location, length: 0)
         }
+        
+        delegate?.textViewDidChange?(self)
     }
     
+    public func cleanTextView() {
+        self.attributedText = nil
+        self.text = ""
+    }
     
     public func appendText(text: String) {
         let emojiText = EmojiHelper.replaceEmojiImage(textView: self, text: text)
@@ -42,6 +48,8 @@ class HXInputTextView: HXTextView {
         // Advance the range to the selected range plus the number of characters added
         let location = selectedRange.location + emojiText.length
         selectedRange = NSRange(location: location, length: 0)
+        
+        delegate?.textViewDidChange?(self)
     }
     
 }

+ 1 - 1
bugu/bugu/Modules/IM/Chat/View/MessageCell/HXImageMessageContentCell.swift

@@ -91,6 +91,6 @@ class HXImageMessageContentCell: HXMessageContentCell {
             super.handleTapGesture(gesture)
             return
         }
-        delegate?.didTapImage(in: self)
+        delegate?.didTapMessage(in: self)
     }
 }

+ 4 - 0
bugu/bugu/Modules/IM/Chat/View/MessageCell/HXMessageContentCell.swift

@@ -99,6 +99,10 @@ public class HXMessageContentCell: MessageCollectionViewCell {
     lazy var quoteMessageView: HXMessageCellQuoteView = {
         let quoteView = HXMessageCellQuoteView(frame: CGRect(0, 0, 100, 30))
         quoteView.layer.masksToBounds = true
+        quoteView.clickAction = { [weak self] in
+            guard let self = self else { return }
+            self.delegate?.didTapQuoteMessageView(in: self)
+        }
         return quoteView
     }()
     

+ 21 - 0
bugu/bugu/Modules/IM/Chat/View/MessageCell/HXTextMessageContentCell.swift

@@ -10,6 +10,11 @@ import UIKit
 
 class HXTextMessageContentCell: HXMessageContentCell {
     
+    private var lastTapTime: TimeInterval = 0
+    private let tapInterval: TimeInterval = 0.2
+    
+    private weak var messageViewModel: MessageViewModel?
+    
     private lazy var messageLabel: HXMessageAttributedTextView = {
         let messageLabel = HXMessageAttributedTextView()
         messageLabel.tapAction = { url in
@@ -55,9 +60,25 @@ class HXTextMessageContentCell: HXMessageContentCell {
             fatalError("messageContainerSize received unhandled MessageDataType: \(messageViewModel.message.kind)")
         }
         
+        self.messageViewModel = messageViewModel
+        
         let textColor = displayDelegate.textColor(for: messageViewModel, at: indexPath, in: messagesCollectionView)
         messageLabel.attributedText = sizeCalculator.messageText
         messageLabel.textColor = textColor
         messageLabel.frame = sizeCalculator.messageLabelFrame
     }
+    
+    override func handleTapGesture(_ gesture: UIGestureRecognizer) {
+        if let messageViewModel = messageViewModel, messageViewModel.isMultiSelected {
+            super.handleTapGesture(gesture)
+        } else {
+            let currentTime = Date().timeIntervalSince1970
+            guard currentTime - lastTapTime < tapInterval else {
+                lastTapTime = currentTime
+                return
+            }
+            super.handleTapGesture(gesture)
+            lastTapTime = 0
+        }
+    }
 }

+ 1 - 1
bugu/bugu/Modules/IM/Chat/View/MessageCell/HXVideoMessageContentCell.swift

@@ -127,6 +127,6 @@ class HXVideoMessageContentCell: HXMessageContentCell {
             super.handleTapGesture(gesture)
             return
         }
-        delegate?.didTapImage(in: self)
+        delegate?.didTapMessage(in: self)
     }
 }

+ 14 - 0
bugu/bugu/Modules/IM/Chat/View/QuoteMessageView/HXMessageCellQuoteView.swift

@@ -11,8 +11,14 @@ import UIKit
 
 class HXMessageCellQuoteView: HXView {
     
+    public var clickAction: (() -> Void)? = nil
+    
     override init(frame: CGRect) {
         super.init(frame: frame)
+        
+        let tap = UITapGestureRecognizer(target: self, action: #selector(onTapAction))
+        self.addGestureRecognizer(tap)
+        
         initializeView()
     }
     
@@ -84,3 +90,11 @@ extension HXMessageCellQuoteView {
         titleLabel.lineBreakMode = .byTruncatingTail
     }
 }
+
+extension HXMessageCellQuoteView {
+    
+    @objc
+    private func onTapAction() {
+        clickAction?()
+    }
+}

+ 15 - 8
bugu/bugu/Modules/IM/Chat/ViewModel/ChatViewModel.swift

@@ -160,14 +160,17 @@ extension ChatViewModel {
             
             let historys = IMClient.getHistoryMessageList(convId: convId, convType: convType, lastMessageId: lastMessageId, count: count)
             if historys.count > 0 {
+                let cellModels = self.messageViewModels(messages: historys)
                 mainQueueTask {
-                    self.messages = self.messageViewModels(messages: historys) + self.messages
+                    self.messages = cellModels + self.messages
                     self.messageChange?(isLoadMore ? .refreshLoadMore : .refreshFrist(scrollToBottom: true))
                 }
                 return
             }
             
-            if let cleanHistory = HistoryMessageManager.cleanHistoryMessage(convId: convId, convType: convType), cleanHistory == true {
+            /// 是否清除过本地历史消息
+            let cleanHistory = HistoryMessageManager.cleanHistoryMessage(convId: convId, convType: convType)
+            if cleanHistory == true {
                 mainQueueTask {
                     self.messageChange?(.refreshFrist(scrollToBottom: false))
                 }
@@ -198,14 +201,15 @@ extension ChatViewModel {
                                       timestamp: Date().milliStamp,
                                       extra: nil)
                 message = IMClient.insertLocalMessage(message)
+                let cellModels = self.messageViewModels(messages: [message])
                 mainQueueTask {
-                    self.messages = self.messageViewModels(messages: [message])
+                    self.messages = cellModels
                     self.messageChange?(.refreshFrist(scrollToBottom: true))
                 }
             }
             
             /// 公众号不需要拉取历史信息
-            if convType != .microServer {
+            if convType != .microServer, cleanHistory == false {
                 fetchRemoteHistoryMessage(count: count, isLoadMore: isLoadMore)
             }
             
@@ -231,8 +235,9 @@ extension ChatViewModel {
                     }
                 }
                 
+                let cellModels = self.messageViewModels(messages: historys)
                 mainQueueTask {
-                    self.messages = self.messageViewModels(messages: historys) + self.messages
+                    self.messages = cellModels + self.messages
                     self.messageChange?(isLoadMore ? (historys.count > 0 ? .refreshLoadMore : .refreshFrist(scrollToBottom: false)) : .refreshFrist(scrollToBottom: true))
                 }
             } failed: { [weak self] code, des in
@@ -253,8 +258,9 @@ extension ChatViewModel {
             
             let historys = IMClient.getHistoryMessageList(convId: convId, convType: convType, lastMessageId: lastMessageId, count: count, loadType: .backward)
             if historys.count > 0 {
+                let cellModels = self.messageViewModels(messages: historys)
                 mainQueueTask {
-                    self.messages = self.messageViewModels(messages: historys)
+                    self.messages = cellModels
                     self.messageChange?(.refreshScrollToTop)
                 }
                 return
@@ -271,8 +277,9 @@ extension ChatViewModel {
             
             let historys = IMClient.getHistoryMessageList(convId: convId, convType: convType, date: date, count: count, loadType: .backward)
             if historys.count > 0 {
+                let viewModels = self.messageViewModels(messages: historys)
                 mainQueueTask {
-                    self.messages = self.messageViewModels(messages: historys)
+                    self.messages = viewModels
                     self.messageChange?(.refreshScrollToTop)
                 }
                 return
@@ -300,7 +307,7 @@ extension ChatViewModel {
     /// - Parameters:
     ///   - message: 消息
     ///   - infos: 文件 infos
-    ///   - displayed: 是否需要上屏(朋友群部分发送的消息不需要上屏)
+    ///   - displayed: 是否需要上屏(公众号部分发送的消息不需要上屏)
     public func sendMessage(_ message: Message, infos: [FileUploadInfo]? = nil, displayed: Bool = true, atRobotIds: [Int]? = nil) {
         
         messagesQueue.async { [weak self] in

+ 3 - 2
bugu/bugu/Modules/IM/Collection/Controller/ChatCollectionViewController.swift

@@ -244,8 +244,9 @@ extension ChatCollectionViewController {
     
     private func didTap(indexPath: IndexPath, message: Message) {
         switch message.kind {
-        case .text:
-            break
+        case .text(let textElem):
+            let controller = TextPreviewViewController(navigator: self.navigator, text: textElem.text)
+            self.present(controller, animated: false)
         case .image:
             didTapImage(in: indexPath, message: message)
         case .video:

+ 2 - 2
bugu/bugu/Modules/IM/CombineMessage/Controller/ChatCombineMessageViewController+MessagesCellDelegate.swift

@@ -37,9 +37,9 @@ extension ChatCombineMessageViewController: MessageCellDelegate {
         case .text:
             break
         case .image:
-            break
+            didTapImage(in: cell)
         case .video:
-            break
+            didTapImage(in: cell)
         case .location(let locationItem):
             self.navigator.show(segue: .locationPreview(location: locationItem), sender: self)
         case .emoticon(let emoticonItem):

+ 46 - 4
bugu/bugu/Modules/IM/File/Controller/HXFilePreviewViewController.swift

@@ -42,7 +42,6 @@ class HXFilePreviewViewController: HXBaseViewController {
         return webView
     }()
     
-    
     lazy var fileIcon: UIImageView = {
         let imageView = UIImageView()
         imageView.contentMode = .scaleAspectFill
@@ -199,7 +198,11 @@ extension HXFilePreviewViewController {
             } completedCallBack: { [weak self] result in
                 guard let self = self else { return }
                 guard let pathURL = fileURL() else { return }
-                loadFileInWebView(pathURL)
+                if supportSpecialImageSupport() {
+                    loadSpecialImageInView(pathURL)
+                } else {
+                    loadFileInWebView(pathURL)
+                }
                 updateMoreBtn()
             }
         } else {
@@ -250,12 +253,36 @@ extension HXFilePreviewViewController {
     }
     
     private func supportPreview() -> Bool {
+        return supportWebPreview() || supportSpecialImageSupport()
+    }
+    
+    private func supportWebPreview() -> Bool {
         let fileName = fileItem.name
         
         guard fileName.isNotEmpty else { return false }
         let fileType = FileManager.fileType(fileName: fileName)
-        let result = fileType == .doc || fileType == .xls || fileType == .ppt || fileType == .pdf || fileType == .pages || fileType == .keynote || fileType == .numbers || fileType == .jpg || fileType == .png || fileType == .txt
-        return result
+        let whileList: [FileExtType] = [
+            .doc,
+            .xls,
+            .ppt,
+            .pdf,
+            .pages,
+            .keynote,
+            .numbers,
+            .jpg,
+            .png,
+            .txt
+        ]
+        return whileList.contains(fileType)
+    }
+    
+    private func supportSpecialImageSupport() -> Bool {
+        let fileName = fileItem.name
+        let fileType = FileManager.fileType(fileName: fileName)
+        let whileList: [FileExtType] = [
+            .heic
+        ]
+        return whileList.contains(fileType)
     }
     
     private func downloadFile(progressCallBack:((Double)-> Void)? = nil, completedCallBack:((MessageDownloadResult)-> Void)?) {
@@ -295,6 +322,21 @@ extension HXFilePreviewViewController {
         webView.load(request)
     }
     
+    private func loadSpecialImageInView(_ fileURL: URL) {
+        if let heicImage = UIImage(contentsOfFile: fileURL.path),
+           let jpegData = heicImage.jpegData(compressionQuality: 1.0) {
+            let base64 = jpegData.base64EncodedString()
+            let html = """
+            <html>
+            <body style="margin:0;padding:0;text-align:center;">
+            <img src="data:image/jpeg;base64,\(base64)" style="max-width:100%;height:auto;" />
+            </body>
+            </html>
+            """
+            webView.loadHTMLString(html, baseURL: nil)
+        }
+    }
+    
     private func shareFileWithActivityController(_ fileURL: URL) {
         let activityViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
         present(activityViewController, animated: true, completion: nil)

+ 111 - 0
bugu/bugu/Modules/IM/Preview/TextPreviewViewController.swift

@@ -0,0 +1,111 @@
+//
+//  TextPreviewViewController.swift
+//  bugu
+//
+//  Created by Tan Cheng on 2025/4/12.
+//  Copyright © 2025 Bugu. All rights reserved.
+//
+
+class TextPreviewViewController: HXBaseViewController {
+
+    private let textElem: TextMessageElem
+    
+    init(navigator: Navigator, text: String) {
+        self.textElem = TextMessageElem(text: text)
+        super.init(navigator: navigator)
+        modalPresentationStyle = .fullScreen
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        initializeView()
+        initializeData()
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+    }
+    
+    override var prefersStatusBarHidden: Bool {
+        return true
+    }
+    
+    // MARK: - getters and setters
+    private lazy var scrollView: UIScrollView = {
+        let scrollView = UIScrollView()
+        scrollView.showsVerticalScrollIndicator = true
+        scrollView.showsHorizontalScrollIndicator = false
+        return scrollView
+    }()
+
+    private lazy var containerView: UIView = {
+        let containerView = UIView()
+        let tap = UITapGestureRecognizer(target: self, action: #selector(dismissPreview))
+        containerView.addGestureRecognizer(tap)
+        return containerView
+    }()
+    
+    private lazy var messageLabel: HXMessageAttributedTextView = {
+        let messageLabel = HXMessageAttributedTextView()
+        messageLabel.tapAction = { url in
+            MessageLinkManager.openURL(url: url)
+        }
+        
+        messageLabel.tapOthersLocation = { [weak self] gesture in
+            guard let self = self else { return }
+            dismissPreview()
+        }
+        
+        return messageLabel
+    }()
+}
+
+// MARK: - private method
+extension TextPreviewViewController {
+    
+    private func initializeView() {
+        containerView.addSubview(messageLabel)
+        scrollView.addSubview(containerView)
+        view.addSubview(scrollView)
+        
+        scrollView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        containerView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+            make.width.equalToSuperview()
+            make.height.greaterThanOrEqualTo(view.height)
+        }
+
+        messageLabel.snp.makeConstraints { make in
+            make.leading.greaterThanOrEqualToSuperview().offset(30)
+            make.trailing.lessThanOrEqualToSuperview().offset(-30)
+            make.center.equalToSuperview()
+            make.top.greaterThanOrEqualToSuperview().offset(50)
+            make.bottom.lessThanOrEqualToSuperview().offset(-50)
+        }
+    }
+    
+    private func initializeData() {
+        let attributeText = NSMutableAttributedString(attributedString: textElem.message)
+        attributeText.font = UIFont.font(ofSize: 24, weight: .regular)
+        attributeText.color = R.color.title_color()
+        let paragraphStyle = NSMutableParagraphStyle()
+        paragraphStyle.lineSpacing = 8
+        attributeText.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributeText.length))
+        messageLabel.attributedText = attributeText
+    }
+}
+
+extension TextPreviewViewController {
+    @objc
+    private func dismissPreview() {
+        self.dismiss(animated: false)
+    }
+}

+ 48 - 41
bugu/bugu/Modules/IM/Session/ViewModel/SessionViewModel.swift

@@ -79,7 +79,10 @@ extension SessionViewModel {
     
     /// 更新会话列表通知
     public func pushConversationChange() {
-        observable.accept(())
+        mainQueueTask { [weak self] in
+            guard let `self` = self else { return }
+            observable.accept(())
+        }
     }
     
     /// 置顶会话
@@ -96,26 +99,32 @@ extension SessionViewModel {
     
     /// 清理未读
     public func cleanUnread(convId: String, convType: ConversationType) {
-        guard let index = sessions.firstIndex(where: { $0.conversation.id == convId }) else { return }
-        guard IMClient.cleanConversationUnread(convId: convId, convType: convType) else { return }
-        
-        let session = sessions[index]
-        session.conversation.unreadCount = 0
-        sessions[index] = session
-        
-        pushConversationChange()
+        messagesQueue.async { [weak self] in
+            guard let `self` = self else { return }
+            guard let index = sessions.firstIndex(where: { $0.conversation.id == convId }) else { return }
+            guard IMClient.cleanConversationUnread(convId: convId, convType: convType) else { return }
+            
+            let session = sessions[index]
+            session.conversation.unreadCount = 0
+            sessions[index] = session
+            
+            pushConversationChange()
+        }
     }
     
     /// 设置未读
     public func setUnread(convId: String, convType: ConversationType) {
-        guard let index = sessions.firstIndex(where: { $0.conversation.id == convId }) else { return }
-        guard IMClient.setConversationUnread(convId: convId, convType: convType) else { return }
-       
-        let session = sessions[index]
-        session.conversation.unreadCount = 1
-        sessions[index] = session
-        
-        pushConversationChange()
+        messagesQueue.async { [weak self] in
+            guard let `self` = self else { return }
+            guard let index = sessions.firstIndex(where: { $0.conversation.id == convId }) else { return }
+            guard IMClient.setConversationUnread(convId: convId, convType: convType) else { return }
+            
+            let session = sessions[index]
+            session.conversation.unreadCount = 1
+            sessions[index] = session
+            
+            pushConversationChange()
+        }
     }
     
     
@@ -180,9 +189,7 @@ extension SessionViewModel {
                 }
             }
             self.sessions = tops + normals
-            mainQueueTask {
-                self.pushConversationChange()
-            }
+            self.pushConversationChange()
         }
         
     }
@@ -211,38 +218,38 @@ extension SessionViewModel {
     }
     
     @objc private func sessionLastmessageChange(_ notification: Notification) {
-        guard let userInfo = notification.userInfo else { return }
-        guard let message = userInfo[NotificationUserInfoMacro.im.messageInfo] as? Message else { return }
-        
-        let index: Int
-        if let _index = sessions.firstIndex(where: { $0.conversation.lastmessage?.messageId == message.messageId }) {
-            index = _index
-        } else if let _index = sessions.firstIndex(where: { $0.conversation.id == message.conversationId }) {
-            index = _index
-        } else {
-            return
+        messagesQueue.async { [weak self] in
+            guard let `self` = self else { return }
+            guard let userInfo = notification.userInfo else { return }
+            guard let message = userInfo[NotificationUserInfoMacro.im.messageInfo] as? Message else { return }
+            
+            let index: Int
+            if let _index = sessions.firstIndex(where: { $0.conversation.lastmessage?.messageId == message.messageId }) {
+                index = _index
+            } else if let _index = sessions.firstIndex(where: { $0.conversation.id == message.conversationId }) {
+                index = _index
+            } else {
+                return
+            }
+            
+            let conv = sessions[index]
+            conv.conversation.lastmessage = message
+            sessions[index] = conv
+            pushConversationChange()
         }
-        
-        let conv = sessions[index]
-        conv.conversation.lastmessage = message
-        sessions[index] = conv
-        pushConversationChange()
     }
     
     @objc private func sessionMessageReadChange(_ notification: Notification) {
         messagesQueue.async { [weak self] in
-            guard let self = self else { return }
+            guard let `self` = self else { return }
             guard let userInfo = notification.userInfo else { return }
             guard let convId = userInfo[NotificationUserInfoMacro.im.convId] as? String else { return }
             guard let convType = userInfo[NotificationUserInfoMacro.im.convType] as? ConversationType else { return }
             guard let index = sessions.firstIndex(where: { $0.conversation.id == convId }) else { return }
             guard let conversation = IMClient.getConversation(convId: convId, convType: convType) else { return }
             let cellModel = SessionCellViewModel(conversation: conversation)
-            
-            mainQueueTask {
-                self.sessions[index] = cellModel
-                self.pushConversationChange()
-            }
+            self.sessions[index] = cellModel
+            self.pushConversationChange()
         }
     }
     

+ 10 - 2
bugu/bugu/Modules/Login/Controller/LoginViewController.swift

@@ -16,9 +16,18 @@ class LoginViewController: HXBaseViewController {
     
     private let viewModel = LogonViewModel()
     
+    override init(navigator: Navigator) {
+        super.init(navigator: navigator)
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
     override func viewDidLoad() {
         super.viewDidLoad()
-        
+        self.navigationBarAlpha = 0
+
         initializeView()
         initializeNotification()
         initializeBinder()
@@ -109,7 +118,6 @@ class LoginViewController: HXBaseViewController {
 
 extension LoginViewController {
     private func initializeView() {
-        self.navigationBarAlpha = 0
         
         self.view.addSubview(self.backgroundImage)
         self.backgroundImage.snp.makeConstraints { make in

+ 1 - 1
bugu/bugu/Modules/Login/Controller/RegisterViewController.swift

@@ -178,7 +178,7 @@ extension RegisterViewController {
         config.editImageConfiguration.tools = [.clip]
         config.editImageConfiguration.showClipDirectlyIfOnlyHasClipTool = true
         
-        let photoPicker = ZLPhotoPreviewSheet()
+        let photoPicker = ZLPhotoPicker()
         photoPicker.selectImageBlock = { [weak self] (models, _) in
             guard let self = self else { return }
             guard models.count > 0 else { return }

+ 1 - 1
bugu/bugu/Modules/Mine/Profile/Controller/MyAccountViewController.swift

@@ -425,7 +425,7 @@ extension MyAccountViewController {
         config.editImageConfiguration.tools = [.clip]
         config.editImageConfiguration.showClipDirectlyIfOnlyHasClipTool = true
         
-        let photoPicker = ZLPhotoPreviewSheet()
+        let photoPicker = ZLPhotoPicker()
         photoPicker.selectImageBlock = { (models, _) in
             guard models.count > 0 else { return }
             for i in 0..<models.count {

+ 2 - 1
bugu/bugu/Modules/Search/Controller/GlobalSearchContainerViewController.swift

@@ -17,7 +17,8 @@ class GlobalSearchContainerViewController: HXBaseViewController {
         .contact(contact: nil),
         .group(group: nil),
         .microServer(microServer: nil),
-        .moment(moment: nil)
+        .moment(moment: nil),
+        .robot(robot: nil)
     ]
     private var selectedIndex = 0
     private var filterText: String? = nil

+ 4 - 3
bugu/bugu/Modules/Search/Controller/GlobalSearchListViewController.swift

@@ -184,8 +184,9 @@ extension GlobalSearchListViewController: UICollectionViewDelegateFlowLayout {
         case .moment(moment: let moment):
             let controller = MomentDetailViewController(navigator: self.navigator, moment: moment)
             self.navigationController?.pushViewController(controller, animated: true)
-        case .robot:
-            break
+        case .robot(robot: let robot):
+            guard let robot = robot else { return }
+            self.navigator.show(segue: .groupRobotDetail(robot: robot), sender: self)
         }
         
     }
@@ -203,7 +204,7 @@ extension GlobalSearchListViewController: UICollectionViewDelegateFlowLayout {
         case .microServer:
             return CGSize(width: ScreenWidth, height: 70)
         case .robot:
-            return .zero
+            return CGSize(width: ScreenWidth, height: 70)
         }
     }
 }

+ 1 - 1
bugu/bugu/Modules/Search/Enum/GlobalSearchEnum.swift

@@ -14,5 +14,5 @@ enum GlobalSearchEnum {
     case group(group: Group? = nil)
     case microServer(microServer: MicroServer? = nil)
     case moment(moment: Moment? = nil)
-    case robot
+    case robot(robot: GroupRobot? = nil)
 }

+ 10 - 0
bugu/bugu/Modules/Search/ViewModel/GlobalSearchResultViewModel.swift

@@ -178,6 +178,16 @@ extension GlobalSearchResultViewModel {
     }
     
     private func searchRobot(text: String) {
+        let robots = IMClient.searchRobot(filterName: text)
         
+        var viewModels: [GlobalSearchViewModel] = []
+        robots.forEach { robot in
+            let group = IMClient.getGroupInfo(groupId: robot.groupId ?? -1)
+            let viewModel = GlobalSearchViewModel(avatarURL: robot.avatarURL?.absoluteString, title: robot.name ?? "", detailTitle: group?.name, timestamp: nil, type: .robot(robot: robot), fileterText: text)
+            viewModels.append(viewModel)
+        }
+        
+        searchResult = viewModels
+        searchResultChange.accept(())
     }
 }