Procházet zdrojové kódy

feat: 完善直播间邀请上麦逻辑

陈文艺 před 2 týdny
rodič
revize
95b68d88fa

+ 10 - 8
Lanu.xcodeproj/project.pbxproj

@@ -351,19 +351,21 @@
 				Views/Profile/Relation/LNUserRelationViewController.swift,
 				Views/Report/LNReportViewController.swift,
 				Views/Room/Bottom/Input/LNRoomMessageInputView.swift,
-				Views/Room/Bottom/Join/Apply/LNRoomApplySeatCell.swift,
-				Views/Room/Bottom/Join/Apply/LNRoomApplySeatListPanel.swift,
-				Views/Room/Bottom/Join/LNRoomApplySeatPanel.swift,
 				Views/Room/Bottom/Join/LNRoomJoinMenuView.swift,
-				Views/Room/Bottom/Join/Manage/LNRoomManageSeatCell.swift,
-				Views/Room/Bottom/Join/Manage/LNRoomManageSeatFilterPanel.swift,
-				Views/Room/Bottom/Join/Manage/LNRoomManageSeatListPanel.swift,
-				Views/Room/Bottom/Join/Manage/LNRoomManageSeatListView.swift,
-				Views/Room/Bottom/Join/Manage/LNRoomManageSeatTabView.swift,
 				Views/Room/Bottom/LNRoomBottomMenuView.swift,
 				Views/Room/Bottom/Mic/LNRoomBottomMicView.swift,
 				Views/Room/Create/LNCreateRoomPanel.swift,
 				Views/Room/Create/LNRoomNameInputPanel.swift,
+				Views/Room/Join/Apply/LNRoomApplySeatCell.swift,
+				Views/Room/Join/Apply/LNRoomApplySeatListPanel.swift,
+				Views/Room/Join/Invite/LNRoomInviteSeatCell.swift,
+				Views/Room/Join/Invite/LNRoomInviteSeatPanel.swift,
+				Views/Room/Join/LNRoomApplySeatPanel.swift,
+				Views/Room/Join/LNRoomGameCategoryFilterPanel.swift,
+				Views/Room/Join/Manage/LNRoomManageSeatCell.swift,
+				Views/Room/Join/Manage/LNRoomManageSeatListPanel.swift,
+				Views/Room/Join/Manage/LNRoomManageSeatListView.swift,
+				Views/Room/Join/Manage/LNRoomManageSeatTabView.swift,
 				"Views/Room/LNCommonAlertView+Room.swift",
 				Views/Room/LNRoomSheetMenu.swift,
 				Views/Room/LNRoomViewController.swift,

+ 24 - 1
Lanu/Localizable.xcstrings

@@ -8442,6 +8442,29 @@
         }
       }
     },
+    "A00369" : {
+      "extractionState" : "manual",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "in the room"
+          }
+        },
+        "id" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "di dalam room"
+          }
+        },
+        "zh-Hans" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "在房间内"
+          }
+        }
+      }
+    },
     "B00001" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -11756,4 +11779,4 @@
     }
   },
   "version" : "1.1"
-}
+}

+ 7 - 0
Lanu/Manager/GameMate/Network/LNGameMateResponse.swift

@@ -51,6 +51,13 @@ class LNGameCategoryItemVO: Decodable {
     var name: String = ""
     var icon: String = ""
     var categoryType: LNGameCategoryType = .normal
+    
+    init(code: String, name: String, icon: String, categoryType: LNGameCategoryType) {
+        self.code = code
+        self.name = name
+        self.icon = icon
+        self.categoryType = categoryType
+    }
 }
 
 @AutoCodable

+ 34 - 1
Lanu/Manager/Room/Network/LNHttpManager+Room.swift

@@ -18,6 +18,9 @@ private let kNetPath_Room_Mic_Apply_Clear = "/live/mic/apply/clear"
 private let kNetPath_Room_Mic_Apply_Cancel = "/live/mic/apply/cancel"
 private let kNetPath_Room_Mic_Apply_Audit = "/live/mic/apply/audit"
 private let kNetPath_Room_Mic_Apply_category = "/live/mic/apply/category"
+private let kNetPath_Room_User_List = "/live/viewer/list"
+private let kNetPath_Room_User_Category = "/live/viewer/category"
+private let kNetPath_Room_Invite = "/live/mic/invite/send"
 
 
 extension LNHttpManager {
@@ -80,7 +83,7 @@ extension LNHttpManager {
         post(path: kNetPath_Room_Mic_Apply_List, params: params, completion: completion)
     }
     
-    func getApplySeatCategory(roomId: String, completion: @escaping (LNRoomApplySeatCategoryResponse?, LNHttpError?) -> Void) {
+    func getApplySeatCategory(roomId: String, completion: @escaping (LNRoomGameCategoryResponse?, LNHttpError?) -> Void) {
         post(path: kNetPath_Room_Mic_Apply_category, params: [
             "id": roomId
         ], completion: completion)
@@ -112,4 +115,34 @@ extension LNHttpManager {
             "pass": accept
         ], completion: completion)
     }
+    
+    func getRoomUserList(roomId: String, size: Int, next: String, playmete: Bool,
+                         filter: String?, completion: @escaping (LNRoomUserListResponse?, LNHttpError?) -> Void) {
+        var param: [String: Any] = [
+            "roomId": roomId,
+            "page": [
+                "size": size,
+                "next": next
+            ],
+            "playmete": playmete
+        ]
+        if let filter, !filter.isEmpty {
+            param["skillCategories"] = [filter]
+        }
+        post(path: kNetPath_Room_User_List, params: param, completion: completion)
+    }
+    
+    func getRoomPlaymateCategory(roomId: String, completion: @escaping (LNRoomGameCategoryResponse?, LNHttpError?) -> Void) {
+        post(path: kNetPath_Room_User_Category, params: [
+            "roomId": roomId
+        ], completion: completion)
+    }
+    
+    func inviteUserToSeat(roomId: String, uid: String, index: Int, completion: @escaping (LNHttpError?) -> Void) {
+        post(path: kNetPath_Room_Invite, params: [
+            "roomId": roomId,
+            "userId": uid,
+            "micIndex": index,
+        ], completion: completion)
+    }
 }

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

@@ -92,6 +92,19 @@ class LNRoomMicApplyListResponse: Decodable {
 }
 
 @AutoCodable
-class LNRoomApplySeatCategoryResponse: Decodable {
+class LNRoomGameCategoryResponse: Decodable {
     var list: [LNGameTypeItemVO] = []
 }
+
+@AutoCodable
+class LNRoomUserListItemVO: Decodable {
+    var user: LNRoomUserVO = LNRoomUserVO()
+    
+    var hasInvited = false
+}
+
+@AutoCodable
+class LNRoomUserListResponse: Decodable {
+    var list: [LNRoomUserListItemVO] = []
+    var next: String = ""
+}

+ 0 - 0
Lanu/Views/Room/Bottom/Join/Apply/LNRoomApplySeatCell.swift → Lanu/Views/Room/Join/Apply/LNRoomApplySeatCell.swift


+ 0 - 0
Lanu/Views/Room/Bottom/Join/Apply/LNRoomApplySeatListPanel.swift → Lanu/Views/Room/Join/Apply/LNRoomApplySeatListPanel.swift


+ 157 - 0
Lanu/Views/Room/Join/Invite/LNRoomInviteSeatCell.swift

@@ -0,0 +1,157 @@
+//
+//  LNRoomInviteSeatCell.swift
+//  Gami
+//
+//  Created by OneeChan on 2026/3/20.
+//
+
+import Foundation
+import UIKit
+import SnapKit
+
+
+class LNRoomInviteSeatCell: UITableViewCell {
+    private let indexLabel = UILabel()
+    private let avatarView = UIImageView()
+    private let nameLabel = UILabel()
+    private let genderView = UIImageView()
+    private let inviteButton = UIButton()
+    private let separator = UIView()
+    
+    private weak var roomSession: LNRoomViewModel?
+    private var seatItem: LNRoomSeatItem?
+    private var curItem: LNRoomUserListItemVO?
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        setupViews()
+    }
+    
+    func update(room: LNRoomViewModel?, seat: LNRoomSeatItem?, item: LNRoomUserListItemVO, index: Int) {
+        indexLabel.text = "\(index)"
+        avatarView.showAvatar(item.user.avatar)
+        nameLabel.text = item.user.nickname
+        
+        genderView.image = switch item.user.gender {
+        case .unknow: nil
+        case .male: .icGenderMale
+        case .female: .icGenderFemale
+        }
+        genderView.isHidden = item.user.gender == .unknow
+        
+        roomSession = room
+        seatItem = seat
+        curItem = item
+        
+        updateInviteButton()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+private extension LNRoomInviteSeatCell {
+    func updateInviteButton() {
+        if curItem?.hasInvited == true {
+            inviteButton.isUserInteractionEnabled = false
+            inviteButton.backgroundColor = .white.withAlphaComponent(0.2)
+            inviteButton.setBackgroundImage(nil, for: .normal)
+            inviteButton.setTitleColor(.fill_7, for: .normal)
+            inviteButton.titleLabel?.font = .body_xs
+        } else {
+            inviteButton.isUserInteractionEnabled = true
+            inviteButton.backgroundColor = .clear
+            inviteButton.setBackgroundImage(.primary_8, for: .normal)
+            inviteButton.setTitleColor(.text_1, for: .normal)
+            inviteButton.titleLabel?.font = .heading_h5
+        }
+    }
+    
+    func setupViews() {
+        backgroundColor = .clear
+        contentView.backgroundColor = .clear
+        selectionStyle = .none
+        
+        indexLabel.font = .heading_h3
+        indexLabel.textColor = UIColor.text_2.withAlphaComponent(0.7)
+        indexLabel.textAlignment = .center
+        contentView.addSubview(indexLabel)
+        indexLabel.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(16)
+            make.centerY.equalToSuperview()
+            make.width.equalTo(10)
+        }
+        
+        avatarView.layer.cornerRadius = 16
+        avatarView.layer.borderWidth = 0.5
+        avatarView.layer.borderColor = UIColor.fill.cgColor
+        avatarView.clipsToBounds = true
+        avatarView.contentMode = .scaleAspectFill
+        contentView.addSubview(avatarView)
+        avatarView.snp.makeConstraints { make in
+            make.leading.equalTo(indexLabel.snp.trailing).offset(12)
+            make.verticalEdges.equalToSuperview().inset(14)
+            make.width.height.equalTo(32)
+        }
+        
+        inviteButton.backgroundColor = .white.withAlphaComponent(0.2)
+        inviteButton.setTitle(.init(key: "A00341"), for: .normal)
+        inviteButton.setTitleColor(.text_1, for: .normal)
+        inviteButton.titleLabel?.font = .heading_h5
+        inviteButton.layer.cornerRadius = 11
+        inviteButton.clipsToBounds = true
+        inviteButton.contentEdgeInsets = .init(top: 0, left: 7, bottom: 0, right: 7)
+        inviteButton.addAction(UIAction(handler: { [weak self] _ in
+            guard let self else { return }
+            guard let roomSession, let curItem, let seatItem else { return }
+            roomSession.inviteUserToSeat(uid: curItem.user.userNo,
+                                         index: seatItem.index)
+            { [weak self] success in
+                guard let self, success else { return }
+                curItem.hasInvited = true
+                updateInviteButton()
+            }
+        }), for: .touchUpInside)
+        contentView.addSubview(inviteButton)
+        inviteButton.snp.makeConstraints { make in
+            make.trailing.equalToSuperview().offset(-16)
+            make.centerY.equalToSuperview()
+            make.width.greaterThanOrEqualTo(56)
+            make.height.equalTo(22)
+        }
+        
+        let nameRow = UIView()
+        contentView.addSubview(nameRow)
+        nameRow.snp.makeConstraints { make in
+            make.leading.equalTo(avatarView.snp.trailing).offset(12)
+            make.trailing.lessThanOrEqualTo(inviteButton.snp.leading).offset(-12)
+            make.centerY.equalToSuperview()
+        }
+        
+        nameLabel.font = .heading_h4
+        nameLabel.textColor = .text_1
+        nameRow.addSubview(nameLabel)
+        nameLabel.snp.makeConstraints { make in
+            make.leading.top.bottom.equalToSuperview()
+        }
+        
+        genderView.contentMode = .scaleAspectFit
+        nameRow.addSubview(genderView)
+        genderView.snp.makeConstraints { make in
+            make.leading.equalTo(nameLabel.snp.trailing).offset(2)
+            make.centerY.equalTo(nameLabel)
+            make.trailing.lessThanOrEqualToSuperview()
+            make.width.height.equalTo(14)
+        }
+        
+        separator.backgroundColor = .fill.withAlphaComponent(0.08)
+        contentView.addSubview(separator)
+        separator.snp.makeConstraints { make in
+            make.horizontalEdges.equalToSuperview().inset(16)
+            make.bottom.equalToSuperview()
+            make.height.equalTo(0.5)
+        }
+    }
+}

+ 239 - 0
Lanu/Views/Room/Join/Invite/LNRoomInviteSeatPanel.swift

@@ -0,0 +1,239 @@
+//
+//  LNRoomInviteSeatPanel.swift
+//  Gami
+//
+//  Created by OneeChan on 2026/3/20.
+//
+
+import Foundation
+import UIKit
+import SnapKit
+import MJRefresh
+
+
+class LNRoomInviteSeatPanel: LNPopupView {
+    private let countLabel = UILabel()
+    private let filterButton = UIButton()
+    private let tableView = UITableView(frame: .zero, style: .plain)
+    private let emptyView = LNNoMoreDataView()
+    
+    private weak var roomSession: LNRoomViewModel?
+    private var curSeat: LNRoomSeatItem?
+    
+    private var items: [LNRoomUserListItemVO] = []
+    private var nextTag: String? = nil
+    
+    private var categories: [LNGameCategoryItemVO] = []
+    
+    private var curCategoryCode: String = ""
+    private var curCategoryTitle: String = .init(key: "A00361")
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        containerHeight = .percent(0.62)
+        
+        setupViews()
+    }
+    
+    func update(_ seat: LNRoomSeatItem, room: LNRoomViewModel?) {
+        curSeat = seat
+        roomSession = room
+        
+        loadList()
+        if seat.index == .guest {
+            filterButton.isHidden = true
+        } else {
+            filterButton.isHidden = false
+            loadCategory()
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension LNRoomInviteSeatPanel {
+    private func loadList() {
+        guard let roomSession else { return }
+        roomSession.getRoomUserList(next: nextTag, playmete: false, filter: nil) { [weak self] res in
+            guard let self else { return }
+            if let list = res?.list {
+                if nextTag == nil {
+                    items.removeAll()
+                }
+                items.append(contentsOf: list)
+                nextTag = res?.next
+                tableView.reloadData()
+                
+                if items.isEmpty {
+                    emptyView.showNoData()
+                } else {
+                    emptyView.hide()
+                }
+            } else {
+                if items.isEmpty {
+                    emptyView.showNetworkError()
+                }
+            }
+            
+            tableView.mj_header?.endRefreshing()
+            if nextTag?.isEmpty != false {
+                tableView.mj_footer?.endRefreshingWithNoMoreData()
+            } else {
+                tableView.mj_footer?.endRefreshing()
+            }
+            countLabel.text = .init(key: "A00334", items.count)
+        }
+    }
+    
+    private func loadCategory() {
+        roomSession?.getPlaymateCategory { [weak self] res in
+            guard let self else { return }
+            guard let res else { return }
+            res.list.forEach { $0.children.forEach {
+                self.categories.append($0)
+            } }
+        }
+    }
+}
+
+extension LNRoomInviteSeatPanel: UITableViewDataSource, UITableViewDelegate {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        items.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(
+            withIdentifier: LNRoomInviteSeatCell.className,
+            for: indexPath
+        ) as! LNRoomInviteSeatCell
+        cell.update(room: roomSession, seat: curSeat, item: items[indexPath.row], index: indexPath.row + 1)
+        return cell
+    }
+}
+
+private extension LNRoomInviteSeatPanel {
+    func setupViews() {
+        container.backgroundColor = .fill_7
+        
+        let countView = buildHeader()
+        container.addSubview(countView)
+        countView.snp.makeConstraints { make in
+            make.top.equalToSuperview().offset(16)
+            make.horizontalEdges.equalToSuperview().inset(16)
+        }
+        
+        let header = MJRefreshNormalHeader { [weak self] in
+            guard let self else { return }
+            nextTag = nil
+            loadList()
+        }
+        header.lastUpdatedTimeLabel?.isHidden = true
+        header.stateLabel?.isHidden = true
+        tableView.mj_header = header
+        
+        let footer = MJRefreshAutoNormalFooter { [weak self] in
+            guard let self else { return }
+            loadList()
+        }
+        footer.setTitle("", for: .noMoreData)
+        footer.setTitle(.init(key: "A00046"), for: .idle)
+        tableView.mj_footer = footer
+        
+        tableView.backgroundColor = .clear
+        tableView.separatorStyle = .none
+        tableView.showsVerticalScrollIndicator = false
+        tableView.showsHorizontalScrollIndicator = false
+        tableView.allowsSelection = false
+        tableView.dataSource = self
+        tableView.delegate = self
+        tableView.register(LNRoomInviteSeatCell.self, forCellReuseIdentifier: LNRoomInviteSeatCell.className)
+        container.addSubview(tableView)
+        tableView.snp.makeConstraints { make in
+            make.top.equalTo(countView.snp.bottom)
+            make.horizontalEdges.equalToSuperview()
+            make.bottom.equalToSuperview().offset(commonBottomInset)
+        }
+        
+        emptyView.isHidden = true
+        emptyView.tipsLabel.textColor = .text_2
+        tableView.addSubview(emptyView)
+        emptyView.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+        }
+        
+        updateFilterView()
+    }
+    
+    func buildHeader() -> UIView {
+        let container = UIView()
+        container.snp.makeConstraints { make in
+            make.height.equalTo(52)
+        }
+        
+        let stackView = UIStackView()
+        stackView.axis = .horizontal
+        stackView.spacing = 5
+        stackView.alignment = .center
+        container.addSubview(stackView)
+        stackView.snp.makeConstraints { make in
+            make.leading.centerY.equalToSuperview()
+        }
+        
+        countLabel.font = .body_m
+        countLabel.textColor = .text_6
+        container.addSubview(countLabel)
+        stackView.addArrangedSubview(countLabel)
+        
+        let countDescLabel = UILabel()
+        countDescLabel.font = .body_m
+        countDescLabel.textColor = .text_1
+        countDescLabel.text = .init(key: "A00369")
+        stackView.addArrangedSubview(countDescLabel)
+        
+        let config = UIImage.SymbolConfiguration(pointSize: 12)
+        filterButton.backgroundColor = .fill.withAlphaComponent(0.15)
+        filterButton.layer.cornerRadius = 15
+        filterButton.clipsToBounds = true
+        filterButton.titleLabel?.font = .body_s
+        filterButton.setTitleColor(.text_1, for: .normal)
+        filterButton.setImage(.init(systemName: "chevron.backward", withConfiguration: config), for: .normal)
+        filterButton.tintColor = .text_1
+        filterButton.semanticContentAttribute = .forceRightToLeft
+        filterButton.imageEdgeInsets = .init(top: 0, left: 4, bottom: 0, right: -4)
+        filterButton.contentEdgeInsets = .init(top: 0, left: 12, bottom: 0, right: 12)
+        filterButton.addAction(UIAction(handler: { [weak self] _ in
+            guard let self else { return }
+            let panel = LNRoomGameCategoryFilterPanel()
+            let options = buildCategoryFilterOptions()
+            panel.update(options: options, curSelectedCode: curCategoryCode)
+            panel.handler = { [weak self] option in
+                guard let self else { return }
+                curCategoryCode = option.code
+                curCategoryTitle = option.code.isEmpty ? .init(key: "A00361") : option.name
+                updateFilterView()
+            }
+            panel.popup()
+        }), for: .touchUpInside)
+        container.addSubview(filterButton)
+        filterButton.snp.makeConstraints { make in
+            make.centerY.trailing.equalToSuperview()
+            make.height.equalTo(30)
+        }
+        
+        return container
+    }
+    
+    func buildCategoryFilterOptions() -> [LNGameCategoryItemVO] {
+        var options: [LNGameCategoryItemVO] = []
+        options.append(.init(code: "", name: .init(key: "A00360"), icon: "", categoryType: .normal))
+        options.append(contentsOf: categories)
+        return options
+    }
+    
+    func updateFilterView() {
+        filterButton.setTitle(curCategoryTitle, for: .normal)
+    }
+}

+ 0 - 0
Lanu/Views/Room/Bottom/Join/LNRoomApplySeatPanel.swift → Lanu/Views/Room/Join/LNRoomApplySeatPanel.swift


+ 8 - 13
Lanu/Views/Room/Bottom/Join/Manage/LNRoomManageSeatFilterPanel.swift → Lanu/Views/Room/Join/LNRoomGameCategoryFilterPanel.swift

@@ -1,5 +1,5 @@
 //
-//  LNRoomManageSeatFilterPanel.swift
+//  LNRoomGameCategoryFilterPanel.swift
 //  Gami
 //
 //  Created by OneeChan on 2026/3/18.
@@ -10,17 +10,12 @@ import UIKit
 import SnapKit
 
 
-class LNRoomManageSeatFilterPanel: LNPopupView {
-    struct Option {
-        let code: String
-        let title: String
-    }
-    
+class LNRoomGameCategoryFilterPanel: LNPopupView {
     private let stackView = UIStackView()
-    private var options: [Option] = []
+    private var options: [LNGameCategoryItemVO] = []
     private var curSelectedCode: String = ""
     
-    var handler: ((Option) -> Void)?
+    var handler: ((LNGameCategoryItemVO) -> Void)?
     
     override init(frame: CGRect) {
         super.init(frame: frame)
@@ -28,7 +23,7 @@ class LNRoomManageSeatFilterPanel: LNPopupView {
         setupViews()
     }
     
-    func update(options: [Option], curSelectedCode: String) {
+    func update(options: [LNGameCategoryItemVO], curSelectedCode: String) {
         self.options = options
         self.curSelectedCode = curSelectedCode
         
@@ -40,7 +35,7 @@ class LNRoomManageSeatFilterPanel: LNPopupView {
     }
 }
 
-private extension LNRoomManageSeatFilterPanel {
+private extension LNRoomGameCategoryFilterPanel {
     func setupViews() {
         container.backgroundColor = .fill_7
         
@@ -81,7 +76,7 @@ private extension LNRoomManageSeatFilterPanel {
         }
     }
     
-    func buildItemView(_ option: Option) -> UIView {
+    func buildItemView(_ option: LNGameCategoryItemVO) -> UIView {
         let container = UIView()
         container.snp.makeConstraints { make in
             make.height.equalTo(54)
@@ -90,7 +85,7 @@ private extension LNRoomManageSeatFilterPanel {
         let titleLabel = UILabel()
         titleLabel.font = .heading_h4
         titleLabel.textColor = .text_1
-        titleLabel.text = option.title
+        titleLabel.text = option.name
         container.addSubview(titleLabel)
         titleLabel.snp.makeConstraints { make in
             make.leading.equalToSuperview().offset(16)

+ 0 - 0
Lanu/Views/Room/Bottom/Join/Manage/LNRoomManageSeatCell.swift → Lanu/Views/Room/Join/Manage/LNRoomManageSeatCell.swift


+ 0 - 0
Lanu/Views/Room/Bottom/Join/Manage/LNRoomManageSeatListPanel.swift → Lanu/Views/Room/Join/Manage/LNRoomManageSeatListPanel.swift


+ 8 - 8
Lanu/Views/Room/Bottom/Join/Manage/LNRoomManageSeatListView.swift → Lanu/Views/Room/Join/Manage/LNRoomManageSeatListView.swift

@@ -15,7 +15,6 @@ class LNRoomManageSeatListView: UIView {
     private let tabType: LNRoomApplySeatType
     
     private let countLabel = UILabel()
-    private let countDescLabel = UILabel()
     private let filterButton = UIButton()
     private let tableView = UITableView(frame: .zero, style: .plain)
     private let clearButton = UIButton()
@@ -24,7 +23,7 @@ class LNRoomManageSeatListView: UIView {
     private var nextTag: String? = nil
     private var items: [LNRoomMicApplyPageVO] = []
     
-    private var categories: [LNRoomManageSeatFilterPanel.Option] = []
+    private var categories: [LNGameCategoryItemVO] = []
     
     private var curCode: String = ""
     private var curTitle: String = .init(key: "A00361")
@@ -82,7 +81,7 @@ extension LNRoomManageSeatListView {
             guard let self else { return }
             guard let res else { return }
             res.list.forEach { $0.children.forEach {
-                self.categories.append(LNRoomManageSeatFilterPanel.Option(code: $0.code, title: $0.name))
+                self.categories.append($0)
             } }
         }
     }
@@ -198,6 +197,7 @@ extension LNRoomManageSeatListView {
         countLabel.textColor = .text_6
         stackView.addArrangedSubview(countLabel)
         
+        let countDescLabel = UILabel()
         countDescLabel.font = .body_m
         countDescLabel.textColor = .text_1
         countDescLabel.text = .init(key: "A00333")
@@ -218,13 +218,13 @@ extension LNRoomManageSeatListView {
         filterButton.addAction(UIAction(handler: { [weak self] _ in
             guard let self, tabType == .playmate else { return }
             
-            let panel = LNRoomManageSeatFilterPanel()
+            let panel = LNRoomGameCategoryFilterPanel()
             let options = buildPlaymateFilterOptions()
             panel.update(options: options, curSelectedCode: curCode)
             panel.handler = { [weak self] option in
                 guard let self else { return }
                 curCode = option.code
-                curTitle = option.code.isEmpty ? .init(key: "A00361") : option.title
+                curTitle = option.code.isEmpty ? .init(key: "A00361") : option.name
                 updateFilterView()
                 
                 nextTag = nil
@@ -241,9 +241,9 @@ extension LNRoomManageSeatListView {
         return container
     }
     
-    private func buildPlaymateFilterOptions() -> [LNRoomManageSeatFilterPanel.Option] {
-        var options: [LNRoomManageSeatFilterPanel.Option] = []
-        options.append(.init(code: "", title: .init(key: "A00360")))
+    private func buildPlaymateFilterOptions() -> [LNGameCategoryItemVO] {
+        var options: [LNGameCategoryItemVO] = []
+        options.append(.init(code: "", name: .init(key: "A00360"), icon: "", categoryType: .normal))
         options.append(contentsOf: categories)
         
         return options

+ 0 - 0
Lanu/Views/Room/Bottom/Join/Manage/LNRoomManageSeatTabView.swift → Lanu/Views/Room/Join/Manage/LNRoomManageSeatTabView.swift


+ 4 - 0
Lanu/Views/Room/Seats/LNRoomSeatViewProtocol.swift

@@ -18,6 +18,10 @@ extension LNRoomSeatViewProtocol {
         let invite = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00341"), handler: { [weak sheet] in
             guard let sheet else { return }
             sheet.dismiss()
+            
+            let panel = LNRoomInviteSeatPanel()
+            panel.update(seat, room: room)
+            panel.popup()
         })
         
         let closeMic = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00342"), handler: { [weak sheet] in

+ 41 - 46
Lanu/Views/Room/ViewModel/LNRoomViewModel.swift

@@ -146,7 +146,7 @@ extension LNRoomViewModel {
     }
     
     // 申请列表的技能类别
-    func getApplyListCategory(handler: @escaping (LNRoomApplySeatCategoryResponse?) -> Void) {
+    func getApplyListCategory(handler: @escaping (LNRoomGameCategoryResponse?) -> Void) {
         LNHttpManager.shared.getApplySeatCategory(roomId: roomId) { res, err in
             runOnMain {
                 handler(res)
@@ -188,39 +188,6 @@ extension LNRoomViewModel {
         }
     }
     
-//    // 接受邀请
-//    func acceptSeatInvite(uid: String, handler: @escaping (Bool) -> Void) {
-//        LNPermissionHelper.requestMicrophoneAccess { [weak self] success in
-//            guard let self else { return }
-//            guard success else {
-//                showToast(.init(key: "A00362"))
-//                return
-//            }
-//            guestStore.acceptInvitation(inviterID: uid) { result in
-//                switch result {
-//                case .success:
-//                    handler(true)
-//                case .failure(let err):
-//                    showToast(err.localizedDescription)
-//                    handler(false)
-//                }
-//            }
-//        }
-//    }
-    
-//    // 拒绝邀请
-//    func rejectSeatInvite(uid: String, handler: @escaping (Bool) -> Void) {
-//        guestStore.rejectInvitation(inviterID: uid) { result in
-//            switch result {
-//            case .success:
-//                handler(true)
-//            case .failure(let err):
-//                showToast(err.localizedDescription)
-//                handler(false)
-//            }
-//        }
-//    }
-    
     // 主动下麦
     func leaveSeat(handler: @escaping (Bool) -> Void) {
         seatStore.leaveSeat { result in
@@ -286,18 +253,17 @@ extension LNRoomViewModel {
         }
     }
     
-//    // 邀请上麦
-//    func inviteUserToSeat(uid: String, handler: @escaping (Bool) -> Void) {
-//        guestStore.inviteToSeat(userID: uid, timeout: 0, extraInfo: nil) { result in
-//            switch result {
-//            case .success:
-//                handler(true)
-//            case .failure(let err):
-//                showToast(err.localizedDescription)
-//                handler(false)
-//            }
-//        }
-//    }
+    // 邀请上麦
+    func inviteUserToSeat(uid: String, index: LNRoomSeatNum, handler: @escaping (Bool) -> Void) {
+        LNHttpManager.shared.inviteUserToSeat(roomId: roomId, uid: uid, index: index.rawValue) { err in
+            runOnMain {
+                handler(err == nil)
+            }
+            if let err {
+                showToast(err.errorDesc)
+            }
+        }
+    }
     
     // 关闭麦位
     func lockSeat(num: Int, handler: @escaping (Bool) -> Void) {
@@ -324,6 +290,35 @@ extension LNRoomViewModel {
             }
         }
     }
+    
+    // 用户列表
+    func getRoomUserList(next: String?, playmete: Bool, filter: String?,
+                         handler: @escaping (LNRoomUserListResponse?) -> Void) {
+        LNHttpManager.shared.getRoomUserList(
+            roomId: roomId, size: 30, next: next ?? "",
+            playmete: playmete, filter: filter)
+        { res, err in
+            runOnMain {
+                handler(res)
+            }
+            
+            if let err {
+                showToast(err.errorDesc)
+            }
+        }
+    }
+    
+    // 申请列表的技能类别
+    func getPlaymateCategory(handler: @escaping (LNRoomGameCategoryResponse?) -> Void) {
+        LNHttpManager.shared.getRoomPlaymateCategory(roomId: roomId) { res, err in
+            runOnMain {
+                handler(res)
+            }
+            if let err {
+                showToast(err.errorDesc)
+            }
+        }
+    }
 }
 
 // MARK: 麦克风管理 - 普通用户

+ 26 - 5
Lanu/Views/Search/LNUserSearchOverviewListView.swift

@@ -8,6 +8,7 @@
 import Foundation
 import UIKit
 import SnapKit
+import MJRefresh
 
 
 protocol LNUserSearchOverviewListViewDelegate: NSObject {
@@ -28,6 +29,7 @@ class LNUserSearchOverviewListView: UIView {
     private var roomViews: [LNUserSearchRoomCardView] = []
     private let roomSectionView = UIView()
     
+    private var curKeyword: String?
     weak var delegate: LNUserSearchOverviewListViewDelegate?
     
     override init(frame: CGRect) {
@@ -40,8 +42,24 @@ class LNUserSearchOverviewListView: UIView {
         userSectionView.isHidden = true
         roomSectionView.isHidden = true
         
-        LNGameMateManager.shared.mixSearch(keyword: keyword) { [weak self] res in
+        curKeyword = keyword
+        
+        reload()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension LNUserSearchOverviewListView {
+    private func reload() {
+        guard let curKeyword else { return }
+        
+        LNGameMateManager.shared.mixSearch(keyword: curKeyword) { [weak self] res in
             guard let self else { return }
+            scrollView.mj_header?.endRefreshing()
+            
             guard let res else { return }
             if res.playmate.isEmpty, res.rooms.isEmpty {
                 emptyView.showNoData(tips: .init(key: "A00244"))
@@ -78,16 +96,19 @@ class LNUserSearchOverviewListView: UIView {
             }
         }
     }
-    
-    required init?(coder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
 }
 
 extension LNUserSearchOverviewListView {
     private func setupViews() {
         backgroundColor = .white
         
+        let header = MJRefreshNormalHeader { [weak self] in
+            guard let self else { return }
+            reload()
+        }
+        header.lastUpdatedTimeLabel?.isHidden = true
+        header.stateLabel?.isHidden = true
+        scrollView.mj_header = header
         scrollView.showsVerticalScrollIndicator = false
         addSubview(scrollView)
         scrollView.snp.makeConstraints { make in