Просмотр исходного кода

feat: 补充系统消息解析逻辑

陈文艺 2 недель назад
Родитель
Сommit
fdb0785db0

+ 2 - 1
Lanu.xcodeproj/project.pbxproj

@@ -386,9 +386,10 @@
 				Views/Room/Settings/LNRoomSettingMenuPanel.swift,
 				Views/Room/Top/LNRoomTopMenuView.swift,
 				Views/Room/ViewModel/LNRoomInfo.swift,
-				Views/Room/ViewModel/LNRoomMessageItem.swift,
 				Views/Room/ViewModel/LNRoomSeatItem.swift,
 				Views/Room/ViewModel/LNRoomViewModel.swift,
+				Views/Room/ViewModel/Message/LNRoomChatMessageItem.swift,
+				Views/Room/ViewModel/Message/LNRoomSystemMessageItem.swift,
 				Views/Search/LNUserSearchHistoryView.swift,
 				Views/Search/LNUserSearchOverviewListView.swift,
 				Views/Search/LNUserSearchRoomCardView.swift,

+ 3 - 3
Lanu/Manager/GameMate/Network/LNGameMateResponse.swift

@@ -538,7 +538,7 @@ class LNPotentialUsersResponse: Decodable {
 }
 
 @AutoCodable
-class LNSearchRoomResultVO: Decodable {
+class LNRoomItemVO: Decodable {
     var roomId: String = ""
     var roomTitle: String = ""
     var roomCover: String = ""
@@ -549,12 +549,12 @@ class LNSearchRoomResultVO: Decodable {
 
 @AutoCodable
 class LNSearchRoomResponse: Decodable {
-    var list: [LNSearchRoomResultVO] = []
+    var list: [LNRoomItemVO] = []
     var next: String = ""
 }
 
 @AutoCodable
 class LNMixSearchResponse: Decodable {
     var playmate: [LNGameMateSearchResultVO] = []
-    var rooms: [LNSearchRoomResultVO] = []
+    var rooms: [LNRoomItemVO] = []
 }

+ 17 - 1
Lanu/Manager/Room/LNRoomManager.swift

@@ -129,6 +129,7 @@ extension LNRoomManager {
     }
 }
 
+// MARK: 入口管理
 extension LNRoomManager {
     func reloadHallEntrance() {
         LNHttpManager.shared.getHallEntrance { res, err in
@@ -138,7 +139,7 @@ extension LNRoomManager {
         }
     }
     
-    func getRandomRoom(queue: DispatchQueue = .main, handler: @escaping (LNSearchRoomResultVO?) -> Void) {
+    func getRandomRoom(queue: DispatchQueue = .main, handler: @escaping (LNRoomItemVO?) -> Void) {
         LNHttpManager.shared.getRandomRoom { res, err in
             queue.asyncIfNotGlobal {
                 handler(res?.room)
@@ -153,6 +154,21 @@ extension LNRoomManager {
     }
 }
 
+// MARK: 用户
+extension LNRoomManager {
+    func getUserCurRoom(uid: String, queue: DispatchQueue = .main,
+                        handler: @escaping (LNUserCurRoomResponse?) -> Void) {
+        LNHttpManager.shared.getUserCurRoom(uid: uid) { res, err in
+            queue.asyncIfNotGlobal {
+                handler(res)
+            }
+            if let err {
+                showToast(err.errorDesc)
+            }
+        }
+    }
+}
+
 extension LNRoomManager: LNAccountManagerNotify, LNProfileManagerNotify {
     func onUserLogin() {
         getCreateRoomAbility(handler: nil)

+ 8 - 0
Lanu/Manager/Room/Network/LNHttpManager+Room.swift

@@ -26,6 +26,8 @@ private let kNetPath_Room_User_Category = "/live/viewer/category"
 private let kNetPath_Room_Entrance = "/live/room/alive"
 private let kNetPath_Room_Random = "/live/room/random"
 
+private let kNetPath_Room_Cur = "/live/view/watching"
+
 
 // MARK: 房间管理
 extension LNHttpManager {
@@ -149,6 +151,12 @@ extension LNHttpManager {
             "micIndex": index,
         ], completion: completion)
     }
+    
+    func getUserCurRoom(uid: String, completion: @escaping (LNUserCurRoomResponse?, LNHttpError?) -> Void) {
+        post(path: kNetPath_Room_Cur, params: [
+            "id": uid
+        ], completion: completion)
+    }
 }
 
 // MARK: 入口

+ 6 - 1
Lanu/Manager/Room/Network/LNRoomResponse.swift

@@ -116,5 +116,10 @@ class LNRoomEntranceResponse: Decodable {
 
 @AutoCodable
 class LNRandomRoomResponse: Decodable {
-    var room: LNSearchRoomResultVO? = nil
+    var room: LNRoomItemVO? = nil
+}
+
+@AutoCodable
+class LNUserCurRoomResponse: Decodable {
+    var room: LNRoomItemVO? = nil
 }

+ 33 - 39
Lanu/Views/Profile/Profile/LNProfileInRoomView.swift

@@ -11,6 +11,7 @@ import SnapKit
 
 
 class LNProfileInRoomView: UIView {
+    private var uid: String?
     private let waveView = UIView()
     private let borderColor: UIColor = .fill
     private let borderWidth: CGFloat = 1
@@ -21,7 +22,7 @@ class LNProfileInRoomView: UIView {
     private var borderLayers: [CAShapeLayer] = []
     
     private let roomCover = UIImageView()
-    private var roomInfo: LNRoomInfo?
+    private var roomInfo: LNRoomItemVO?
     
     override init(frame: CGRect) {
         super.init(frame: frame)
@@ -29,15 +30,9 @@ class LNProfileInRoomView: UIView {
         setupViews()
     }
     
-    func update(_ room: LNRoomInfo?) {
-        roomInfo = room
-        
-        if let room, !room.liveID.isEmpty {
-            isHidden = false
-            roomCover.sd_setImage(with: URL(string: room.coverURL))
-        } else {
-            isHidden = true
-        }
+    func update(_ uid: String) {
+        self.uid = uid
+        reloadRoomInfo()
     }
     
     override func layoutSubviews() {
@@ -53,17 +48,44 @@ class LNProfileInRoomView: UIView {
         }
     }
     
+    override func didMoveToWindow() {
+        super.didMoveToWindow()
+        
+        if window != nil {
+            reloadRoomInfo()
+        }
+    }
+    
     required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
 }
 
+extension LNProfileInRoomView {
+    private func reloadRoomInfo() {
+        guard let uid else { return }
+        
+        LNRoomManager.shared.getUserCurRoom(uid: uid) { [weak self] res in
+            guard let self else { return }
+            guard let res else { return }
+            roomInfo = res.room
+            
+            if let room = res.room, !room.roomId.isEmpty {
+                isHidden = false
+                roomCover.sd_setImage(with: URL(string: room.roomCover))
+            } else {
+                isHidden = true
+            }
+        }
+    }
+}
+
 private extension LNProfileInRoomView {
     func setupViews() {
         onTap { [weak self] in
             guard let self else { return }
             guard let roomInfo else { return }
-            pushToRoom(roomInfo.liveID)
+            pushToRoom(roomInfo.roomId)
         }
         snp.makeConstraints { make in
             make.width.equalTo(83)
@@ -192,31 +214,3 @@ private extension LNProfileInRoomView {
         return bottom
     }
 }
-
-
-#if DEBUG
-
-import SwiftUI
-
-struct LNProfileInRoomViewPreview: UIViewRepresentable {
-    func makeUIView(context: Context) -> some UIView {
-        let container = UIView()
-        container.backgroundColor = .lightGray
-        
-        let view = LNProfileInRoomView()
-        container.addSubview(view)
-        view.snp.makeConstraints { make in
-            make.center.equalToSuperview()
-        }
-        
-        return container
-    }
-    
-    func updateUIView(_ uiView: UIViewType, context: Context) { }
-}
-
-#Preview(body: {
-    LNProfileInRoomViewPreview()
-})
-#endif
-

+ 2 - 0
Lanu/Views/Profile/Profile/LNProfileUserInfoView.swift

@@ -38,6 +38,8 @@ class LNProfileUserInfoView: UIView {
         scoreLabel.text = "\(detail.star)"
         scoreLabel.superview?.isHidden = !detail.playmate
         
+        inRoomView.update(detail.userNo)
+        
         curDetail = detail
     }
     

+ 5 - 3
Lanu/Views/Room/Message/LNRoomChatMessageCell.swift

@@ -15,7 +15,7 @@ class LNRoomChatMessageCell: UITableViewCell {
     private let nameLabel = UILabel()
     private let contentLabel = UILabel()
     
-    private var curItem: LNRoomMessageItem?
+    private var curItem: LNRoomChatMessageItem?
     
     override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
         super.init(style: style, reuseIdentifier: reuseIdentifier)
@@ -23,7 +23,7 @@ class LNRoomChatMessageCell: UITableViewCell {
         setupViews()
     }
     
-    func update(_ message: LNRoomMessageItem) {
+    func update(_ message: LNRoomChatMessageItem) {
         contentLabel.text = message.text
         avatarView.sd_setImage(with: URL(string: message.avatar))
         nameLabel.text = message.name
@@ -43,7 +43,9 @@ extension LNRoomChatMessageCell {
         onTap { [weak self] in
             guard let self else { return }
             guard let curItem else { return }
-            pushToProfile(uid: curItem.sender)
+            let panel = LNRoomProfileCardPanel()
+            panel.load(curItem.sender)
+            panel.popup()
         }
         
         avatarView.layer.cornerRadius = 13

+ 7 - 3
Lanu/Views/Room/Message/LNRoomMessageView.swift

@@ -13,9 +13,10 @@ import Combine
 
 class LNRoomMessageView: UIView {
     private let tableView = UITableView()
+    private let maxMessageCount = 300
     private weak var roomSession: LNRoomViewModel?
     
-    private var items: [LNRoomMessageItem] = []
+    private var items: [LNRoomChatMessageItem] = []
     
     override init(frame: CGRect) {
         super.init(frame: frame)
@@ -34,8 +35,11 @@ class LNRoomMessageView: UIView {
 }
 
 extension LNRoomMessageView: LNRoomViewModelNotify {
-    func onRoomMessageChanged() {
-        items = roomSession?.curMessage ?? []
+    func onRoomMessageChanged(messages: [LNRoomChatMessageItem]) {
+        items.append(contentsOf: messages)
+        if items.count > maxMessageCount {
+            items.removeFirst(Int(Double(maxMessageCount) * 0.3))
+        }
         tableView.reloadData()
         tableView.scrollToBottom()
     }

+ 1 - 1
Lanu/Views/Room/Message/LNRoomSystemMessageCell.swift

@@ -19,7 +19,7 @@ class LNRoomSystemMessageCell: UITableViewCell {
         setupViews()
     }
     
-    func update(_ message: LNRoomMessageItem) {
+    func update(_ message: LNRoomChatMessageItem) {
         
     }
     

+ 83 - 17
Lanu/Views/Room/ViewModel/LNRoomViewModel.swift

@@ -11,20 +11,22 @@ import Combine
 
 
 protocol LNRoomViewModelNotify {
-    func onRoomMessageChanged()
+    func onRoomMessageChanged(messages: [LNRoomChatMessageItem])
     func onRoomSeatsChanged()
     func onRoomSpeakingUsersChanged()
     func onRoomSeatApplyChanged()
     func onRoomInfoChanged()
+    func onMySeatApplyChanged()
     
     func onRoomClosed()
 }
 extension LNRoomViewModelNotify {
-    func onRoomMessageChanged() { }
+    func onRoomMessageChanged(messages: [LNRoomChatMessageItem]) { }
     func onRoomSeatsChanged() { }
     func onRoomSpeakingUsersChanged() { }
     func onRoomSeatApplyChanged() { }
     func onRoomInfoChanged() { }
+    func onMySeatApplyChanged() { }
     
     func onRoomClosed() { }
 }
@@ -64,13 +66,22 @@ class LNRoomViewModel: NSObject {
     private let seatStore: LiveSeatStore
     private let guestStore: CoGuestStore
     private let messageStore: BarrageStore
+    private let audienceStore: LiveAudienceStore
+    
     private(set) var seatsInfo: [LNRoomSeatItem] = []
     private(set) var roomInfo = LNRoomInfo()
     private(set) var speakingUser: [String] = []
-    private(set) var curMessage: [LNRoomMessageItem] = []
+    private(set) var lastmessage: Barrage? = nil
     private let seatApplyCountKey = "mic_apply_num"
     private(set) var seatApplyCount: Int = 0
-    private let maxMessageCount = 300
+    private(set) var waitingForSeat = false {
+        didSet {
+            if oldValue != waitingForSeat {
+                LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onMySeatApplyChanged() }
+            }
+        }
+    }
+    private let systemHandlerQueue = DispatchQueue(label: "com.gami.room.system.message", attributes: .concurrent)
     
     init(roomId: String) {
         self.roomId = roomId
@@ -78,12 +89,14 @@ class LNRoomViewModel: NSObject {
         seatStore = LiveSeatStore.create(liveID: roomId)
         guestStore = CoGuestStore.create(liveID: roomId)
         messageStore = BarrageStore.create(liveID: roomId)
+        audienceStore = LiveAudienceStore.create(liveID: roomId)
         
         super.init()
         
         setupSeatObservers()
         setupMessageObservers()
         setupRoomInfoObserver()
+        setupAudienceEventObservers()
     }
     
     func closeRoom() {
@@ -165,12 +178,14 @@ extension LNRoomViewModel {
                 showToast(.init(key: "A00362"))
                 return
             }
-            LNHttpManager.shared.applySeat(roomId: roomId, index: index) { err in
+            LNHttpManager.shared.applySeat(roomId: roomId, index: index) { [weak self] err in
                 runOnMain {
                     handler(err == nil)
                 }
                 if let err {
                     showToast(err.errorDesc)
+                } else if let self {
+                    waitingForSeat = true
                 }
             }
         }
@@ -178,12 +193,14 @@ extension LNRoomViewModel {
     
     // 取消上麦申请
     func cancelSeatApply(handler: @escaping (Bool) -> Void) {
-        LNHttpManager.shared.cancelApplySeat(roomId: roomId) { err in
+        LNHttpManager.shared.cancelApplySeat(roomId: roomId) { [weak self] err in
             runOnMain {
                 handler(err == nil)
             }
             if let err {
                 showToast(err.errorDesc)
+            } else if let self {
+                waitingForSeat = false
             }
         }
     }
@@ -376,25 +393,42 @@ extension LNRoomViewModel {
     private func setupMessageObservers() {
         messageStore.state.subscribe().receive(on: DispatchQueue.main).sink { [weak self] state in
             guard let self else { return }
-            let lastId = curMessage.last?.id ?? 0
+            let lastId = lastmessage?.sequence ?? 0
+            var newMessage: [Barrage] = []
             if let index = state.messageList.lastIndex(where: { $0.sequence == lastId }) {
-                curMessage.append(contentsOf: state.messageList[(index + 1)...].map({
-                    LNRoomMessageItem(info: $0)
-                }))
+                newMessage.append(contentsOf: state.messageList[(index + 1)...])
             } else {
-                curMessage.append(contentsOf: state.messageList.map({
-                    LNRoomMessageItem(info: $0)
-                }))
+                newMessage.append(contentsOf: state.messageList)
             }
-            if curMessage.count > maxMessageCount {
-                curMessage.removeFirst(Int(Double(maxMessageCount) * 0.3))
+            lastmessage = newMessage.last
+            
+            var chatMessage: [LNRoomChatMessageItem] = []
+            var systemMessage: [LNRoomSystemMessageItem] = []
+            newMessage.forEach {
+                switch $0.messageType {
+                case .text:
+                    if let item = LNRoomChatMessageItem(info: $0) {
+                        chatMessage.append(item)
+                    }
+                case .custom:
+                    if let item = LNRoomSystemMessageItem(info: $0) {
+                        systemMessage.append(item)
+                    }
+                default:
+                    break
+                }
             }
-            LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomMessageChanged() }
+            if !chatMessage.isEmpty {
+                LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomMessageChanged(messages: chatMessage) }
+            }
+            handleSystemMessage(messages: systemMessage)
         }.store(in: &cancellables)
     }
     
     func sendMessage(text: String, handler: @escaping (Bool) -> Void) {
-        messageStore.sendTextMessage(text: text, extensionInfo: nil) { result in
+        messageStore.sendTextMessage(text: text, extensionInfo: [
+            LNRoomChatMessageTypeKey: LNRoomChatMessageType.chat.rawValue
+        ]) { result in
             switch result {
             case .success:
                 handler(true)
@@ -441,3 +475,35 @@ extension LNRoomViewModel {
         }
     }
 }
+
+// MARK: 观众事件
+extension LNRoomViewModel {
+    private func setupAudienceEventObservers() {
+        audienceStore.liveAudienceEventPublisher.receive(on: DispatchQueue.main).sink { [weak self] event in
+            guard let self else { return }
+            switch event {
+            case .onAudienceJoined(let info):
+                var welcomeTip = Barrage()
+                welcomeTip.messageType = .text
+                welcomeTip.textContent = "欢迎 \(info.userName) 进入直播间!"
+                welcomeTip.extensionInfo = [LNRoomChatMessageTypeKey: LNRoomChatMessageType.chat.rawValue]
+                messageStore.appendLocalTip(message: welcomeTip)
+            default:
+                break
+            }
+        }.store(in: &cancellables)
+    }
+}
+
+extension LNRoomViewModel {
+    private func handleSystemMessage(messages: [LNRoomSystemMessageItem]) {
+        systemHandlerQueue.async { [weak self] in
+            guard let self else { return }
+            messages.forEach {
+                if $0.cmd == .MicClear {
+                    self.waitingForSeat = false
+                }
+            }
+        }
+    }
+}

+ 13 - 12
Lanu/Views/Room/ViewModel/LNRoomMessageItem.swift → Lanu/Views/Room/ViewModel/Message/LNRoomChatMessageItem.swift

@@ -1,5 +1,5 @@
 //
-//  LNRoomMessageItem.swift
+//  LNRoomChatMessageItem.swift
 //  Gami
 //
 //  Created by OneeChan on 2026/3/12.
@@ -15,17 +15,12 @@ enum LNRoomMessageItemType {
     case system
 }
 
-private extension BarrageType {
-    var toMessageType: LNRoomMessageItemType {
-        switch self {
-        case .text: .chat
-        case .custom: .system
-        default: .unknown
-        }
-    }
+let LNRoomChatMessageTypeKey = "type"
+enum LNRoomChatMessageType: String {
+    case chat = "0"
 }
 
-class LNRoomMessageItem {
+class LNRoomChatMessageItem {
     let id: Int
     let type: LNRoomMessageItemType
     let sender: String
@@ -33,9 +28,15 @@ class LNRoomMessageItem {
     let name: String
     let text: String
     
-    init(info: Barrage) {
+    init?(info: Barrage) {
+        guard info.messageType == .text,
+              info.extensionInfo?[LNRoomChatMessageTypeKey] == LNRoomChatMessageType.chat.rawValue
+        else {
+            return nil
+        }
+        
         id = info.sequence
-        type = info.messageType.toMessageType
+        type = .chat
         sender = info.sender.userID
         avatar = info.sender.avatarURL
         name = info.sender.userName

+ 25 - 0
Lanu/Views/Room/ViewModel/Message/LNRoomSystemMessageItem.swift

@@ -0,0 +1,25 @@
+//
+//  LNRoomSystemMessageItem.swift
+//  Gami
+//
+//  Created by OneeChan on 2026/3/20.
+//
+
+import Foundation
+import AtomicXCore
+
+
+enum LNRoomSystemMessageType: String {
+    case MicClear = "mic.clear"
+}
+
+
+class LNRoomSystemMessageItem {
+    var cmd: LNRoomSystemMessageType = .MicClear
+    
+    init?(info: Barrage) {
+        guard info.messageType == .custom,
+              info.businessID == LNRoomSystemMessageType.MicClear.rawValue
+        else { return nil }
+    }
+}

+ 2 - 2
Lanu/Views/Search/LNUserSearchRoomCardView.swift

@@ -19,7 +19,7 @@ class LNUserSearchRoomCardView: UIView {
     private let titleLabel = UILabel()
     private let subtitleLabel = UILabel()
     
-    private var curItem: LNSearchRoomResultVO?
+    private var curItem: LNRoomItemVO?
     
     override init(frame: CGRect) {
         super.init(frame: frame)
@@ -31,7 +31,7 @@ class LNUserSearchRoomCardView: UIView {
         fatalError("init(coder:) has not been implemented")
     }
     
-    func update(_ item: LNSearchRoomResultVO) {
+    func update(_ item: LNRoomItemVO) {
         idLabel.text = "ID \(item.roomId)"
 //        hotCountLabel.text = "\(item.viewers)"
         cover.sd_setImage(with: URL(string: item.roomCover))

+ 2 - 2
Lanu/Views/Search/LNUserSearchRoomListView.swift

@@ -13,7 +13,7 @@ import MJRefresh
 
 final class LNUserSearchRoomListView: UIView {
     private let emptyView = LNNoMoreDataView()
-    private var items: [LNSearchRoomResultVO] = []
+    private var items: [LNRoomItemVO] = []
     private var curKeyword: String? = nil
     private var nextTag: String? = nil
     private let collectionView: UICollectionView
@@ -168,7 +168,7 @@ private final class LNUserSearchRoomCardCell: UICollectionViewCell {
         fatalError("init(coder:) has not been implemented")
     }
     
-    func update(_ item: LNSearchRoomResultVO) {
+    func update(_ item: LNRoomItemVO) {
         cardView.update(item)
     }
 }