LNOrderRoomUserSeatView.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. //
  2. // LNOrderRoomUserSeatView.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/3/23.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNOrderRoomUserSeatView: UIView {
  11. private let seatNum: LNOrderRoomSeatNum
  12. private let nameLabel = UILabel()
  13. private let emptyIc = UIImageView()
  14. private var emptyIcon: UIImage {
  15. switch seatNum {
  16. case .host: .icSeatNormal
  17. case .guest: .icSeatGuest
  18. default: .icSeatNormal
  19. }
  20. }
  21. private let userView = UIView()
  22. private let userAvatar = UIImageView()
  23. private let muteIc = UIImageView()
  24. private let speakingView = LNRoomSeatSpeakingView()
  25. private weak var roomSession: LNOrderRoomViewModel?
  26. private var curSeat: LNRoomSeatItem? {
  27. roomSession?.seatsInfo.first { $0.index == seatNum.rawValue }
  28. }
  29. private var isSpeaking: Bool {
  30. roomSession?.speakingUser.contains(curSeat?.uid ?? "") == true
  31. }
  32. init(seatNum: LNOrderRoomSeatNum) {
  33. self.seatNum = seatNum
  34. super.init(frame: .zero)
  35. setupViews()
  36. LNEventDeliver.addObserver(self)
  37. }
  38. func update(_ room: LNOrderRoomViewModel?) {
  39. roomSession = room
  40. if let room {
  41. onRoomSeatsChanged(session: room, changed: room.seatsInfo)
  42. speakingView.update(seatNum.rawValue, room: room)
  43. }
  44. }
  45. required init?(coder: NSCoder) {
  46. fatalError("init(coder:) has not been implemented")
  47. }
  48. }
  49. extension LNOrderRoomUserSeatView {
  50. private func handlerClick() {
  51. guard let room = roomSession, let seat = curSeat else { return }
  52. guard let orderSeatNum = LNOrderRoomSeatNum(rawValue: seat.index) else { return }
  53. let mySeatNum = LNOrderRoomSeatNum(rawValue: room.mySeatInfo?.index ?? -1) ?? .none
  54. let sheet = LNRoomSheetMenu()
  55. let invite = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00341"), handler: { [weak sheet, weak self] in
  56. guard let self, let sheet else { return }
  57. sheet.dismiss()
  58. let panel = LNOrderRoomInviteSeatPanel()
  59. panel.update(seat, room: room)
  60. panel.popup(self)
  61. })
  62. let closeMic = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00342"), handler: { [weak sheet] in
  63. guard let sheet else { return }
  64. room.lockSeat(num: seat.index) { [weak sheet] success in
  65. guard let sheet else { return }
  66. guard success else { return }
  67. sheet.dismiss()
  68. }
  69. })
  70. let openMic = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00348"), handler: { [weak sheet] in
  71. guard let sheet else { return }
  72. room.unlockSeat(num: seat.index) { [weak sheet] success in
  73. guard let sheet else { return }
  74. guard success else { return }
  75. sheet.dismiss()
  76. }
  77. })
  78. let muteMic = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00343"), handler: { [weak sheet] in
  79. guard let sheet else { return }
  80. sheet.dismiss()
  81. room.muteSeat(uid: seat.uid) { _ in }
  82. })
  83. let unmuteMic = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00349"), handler: { [weak sheet] in
  84. guard let sheet else { return }
  85. sheet.dismiss()
  86. room.unmuteSeat(uid: seat.uid) { _ in }
  87. })
  88. let profile = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00344"), handler: { [weak sheet] in
  89. guard let sheet else { return }
  90. sheet.dismiss()
  91. let panel = LNRoomProfileCardPanel()
  92. panel.load(seat.uid)
  93. panel.popup(self)
  94. })
  95. let kickOffMic = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00345"), handler: { [weak sheet] in
  96. guard let sheet else { return }
  97. room.kickUserOffSeat(uid: seat.uid) { [weak sheet] success in
  98. guard let sheet else { return }
  99. guard success else { return }
  100. sheet.dismiss()
  101. }
  102. })
  103. let offMic = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00346"), handler: { [weak sheet] in
  104. guard let sheet else { return }
  105. room.leaveSeat { [weak sheet] success in
  106. guard let sheet else { return }
  107. guard success else { return }
  108. sheet.dismiss()
  109. }
  110. })
  111. let toBePlaymate = LNRoomSheetMenu.buildMenuItem(title: .init(key: "A00347"), handler: { [weak sheet] in
  112. guard let sheet else { return }
  113. showLoading()
  114. LNGameMateManager.shared.getJoinGameMateInfo { [weak sheet] res in
  115. dismissLoading()
  116. guard let sheet else { return }
  117. guard let res else { return }
  118. sheet.dismiss()
  119. if res.step3Complete || res.underReview {
  120. sheet.pushToJoinUsReview(true)
  121. return
  122. }
  123. let config = LNJumpWebViewConfig(url: .joinUsUrl)
  124. sheet.pushToWebView(config)
  125. }
  126. })
  127. if seat.uid.isMyUid { // 自己
  128. sheet.update(title: nil, views: [
  129. offMic, profile
  130. ])
  131. } else if room.mySeatInfo?.index == LNOrderRoomSeatNum.host.rawValue { // 管理员
  132. if seat.uid.isEmpty { // 空麦位
  133. if seat.isLocked {
  134. sheet.update(title: orderSeatNum.title, views: [
  135. openMic
  136. ])
  137. } else {
  138. sheet.update(title: orderSeatNum.title, views: [
  139. invite, closeMic
  140. ])
  141. }
  142. } else { // 有人的麦位
  143. sheet.update(title: orderSeatNum.title, views: [
  144. profile, kickOffMic, closeMic, seat.isMute ? unmuteMic : muteMic
  145. ])
  146. }
  147. } else {
  148. if seat.isLocked { return } // 麦位被锁,仅管理员能操作
  149. if !seat.uid.isEmpty { // 有人的麦位
  150. let panel = LNRoomProfileCardPanel()
  151. panel.load(seat.uid)
  152. panel.popup(self)
  153. return
  154. }
  155. if myUserInfo.playmate { // 陪玩师
  156. if orderSeatNum == .host
  157. || orderSeatNum == .guest
  158. || !(.mic1 ... .mic8 ~= mySeatNum) {
  159. let panel = LNOrderRoomApplySeatPanel()
  160. panel.update(room, seat: orderSeatNum)
  161. panel.popup(self)
  162. return
  163. }
  164. return
  165. } else { // 普通用户
  166. if orderSeatNum == .host || orderSeatNum == .guest { // 主持人 和 嘉宾位
  167. let panel = LNOrderRoomApplySeatPanel()
  168. panel.update(room, seat: orderSeatNum)
  169. panel.popup(self)
  170. return
  171. } else {
  172. sheet.update(title: nil, views: [
  173. toBePlaymate
  174. ])
  175. }
  176. }
  177. }
  178. sheet.popup(self)
  179. }
  180. }
  181. extension LNOrderRoomUserSeatView: LNRoomViewModelNotify {
  182. func onRoomSeatsChanged(session: LNRoomSession, changed: [LNRoomSeatItem]) {
  183. guard roomSession == session else { return }
  184. guard let curSeat else { return }
  185. if curSeat.isLocked {
  186. userView.isHidden = true
  187. emptyIc.image = .icSeatLock
  188. nameLabel.text = seatNum.title
  189. } else if curSeat.uid.isEmpty {
  190. userView.isHidden = true
  191. emptyIc.image = emptyIcon
  192. nameLabel.text = seatNum.title
  193. } else {
  194. userView.isHidden = false
  195. if !curSeat.nickname.isEmpty {
  196. userAvatar.sd_setImage(with: URL(string: curSeat.avatar))
  197. nameLabel.text = curSeat.nickname
  198. } else {
  199. LNProfileManager.shared.getCachedProfileUserInfo(uid: curSeat.uid) { [weak self] info in
  200. guard let self, let info, info.uid == curSeat.uid else { return }
  201. userAvatar.sd_setImage(with: URL(string: info.avatar))
  202. nameLabel.text = info.name
  203. }
  204. }
  205. }
  206. muteIc.isHidden = !curSeat.isMute
  207. }
  208. }
  209. extension LNOrderRoomUserSeatView {
  210. private func setupViews() {
  211. onTap { [weak self] in
  212. guard let self else { return }
  213. handlerClick()
  214. }
  215. snp.makeConstraints { make in
  216. make.width.equalTo(76)
  217. make.height.equalTo(68)
  218. }
  219. let empty = buildEmpty()
  220. addSubview(empty)
  221. empty.snp.makeConstraints { make in
  222. make.centerX.equalToSuperview()
  223. make.top.equalToSuperview()
  224. }
  225. let userView = buildUserView()
  226. addSubview(userView)
  227. userView.snp.makeConstraints { make in
  228. make.centerX.equalToSuperview()
  229. make.top.equalToSuperview()
  230. }
  231. let bottom = buildBottom()
  232. addSubview(bottom)
  233. bottom.snp.makeConstraints { make in
  234. make.centerX.equalToSuperview()
  235. make.bottom.equalToSuperview()
  236. make.leading.greaterThanOrEqualToSuperview()
  237. }
  238. }
  239. private func buildEmpty() -> UIView {
  240. emptyIc.image = .icSeatNormal
  241. return emptyIc
  242. }
  243. private func buildUserView() -> UIView {
  244. userView.isHidden = true
  245. userView.snp.makeConstraints { make in
  246. make.width.height.equalTo(46)
  247. }
  248. userView.addSubview(speakingView)
  249. speakingView.snp.makeConstraints { make in
  250. make.edges.equalToSuperview()
  251. }
  252. let gradientBg = UIImageView()
  253. gradientBg.layer.cornerRadius = 23
  254. gradientBg.image = .primary_7
  255. gradientBg.clipsToBounds = true
  256. userView.addSubview(gradientBg)
  257. gradientBg.snp.makeConstraints { make in
  258. make.edges.equalToSuperview()
  259. }
  260. userAvatar.layer.cornerRadius = 22
  261. userAvatar.clipsToBounds = true
  262. userView.addSubview(userAvatar)
  263. userAvatar.snp.makeConstraints { make in
  264. make.center.equalToSuperview()
  265. make.width.height.equalTo(44)
  266. }
  267. muteIc.image = .icMicOffBlack70
  268. userView.addSubview(muteIc)
  269. muteIc.snp.makeConstraints { make in
  270. make.trailing.equalToSuperview()
  271. make.bottom.equalToSuperview()
  272. }
  273. return userView
  274. }
  275. private func buildBottom() -> UIView {
  276. let stackView = UIStackView()
  277. stackView.spacing = 2
  278. stackView.alignment = .center
  279. let hostIc = UIImageView(image: .icMicRoomHost)
  280. hostIc.isHidden = seatNum != .host
  281. stackView.addArrangedSubview(hostIc)
  282. nameLabel.font = .body_xs
  283. nameLabel.textColor = .text_1
  284. nameLabel.textAlignment = .center
  285. stackView.addArrangedSubview(nameLabel)
  286. return stackView
  287. }
  288. }