UserListCell.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. //
  2. // UserListCell.swift
  3. // TUIRoomKit
  4. //
  5. // Created by janejntang on 2024/5/7.
  6. //
  7. import Foundation
  8. import RTCRoomEngine
  9. import Factory
  10. import Combine
  11. class UserListCell: UITableViewCell {
  12. var attendeeModel: UserEntity
  13. var viewModel: UserListViewModel
  14. let avatarImageView: UIImageView = {
  15. let img = UIImageView()
  16. img.layer.cornerRadius = 20
  17. img.layer.masksToBounds = true
  18. return img
  19. }()
  20. let userLabel: UILabel = {
  21. let label = UILabel()
  22. label.textColor = UIColor(0xD5E0F2)
  23. label.backgroundColor = UIColor.clear
  24. label.textAlignment = isRTL ? .right : .left
  25. label.textAlignment = .left
  26. label.font = UIFont(name: "PingFangSC-Regular", size: 16)
  27. label.numberOfLines = 1
  28. return label
  29. }()
  30. let roleImageView: UIImageView = {
  31. let imageView = UIImageView()
  32. return imageView
  33. }()
  34. let roleLabel: UILabel = {
  35. let label = UILabel ()
  36. label.font = UIFont(name: "PingFangSC-Regular", size: 12)
  37. label.backgroundColor = UIColor.clear
  38. label.textColor = UIColor(0x4791FF)
  39. return label
  40. }()
  41. let muteAudioButton: UIButton = {
  42. let button = UIButton()
  43. button.setImage(UIImage(named: "room_unMute_audio", in: tuiRoomKitBundle(), compatibleWith: nil)?.checkOverturn(), for: .normal)
  44. button.setImage(UIImage(named: "room_mute_audio_red", in: tuiRoomKitBundle(), compatibleWith: nil)?.checkOverturn(), for: .selected)
  45. return button
  46. }()
  47. let muteVideoButton: UIButton = {
  48. let button = UIButton(type: .custom)
  49. button.setImage(UIImage(named: "room_unMute_video", in: tuiRoomKitBundle(), compatibleWith: nil)?.checkOverturn(), for: .normal)
  50. button.setImage(UIImage(named: "room_mute_video_red", in: tuiRoomKitBundle(), compatibleWith: nil)?.checkOverturn(), for: .selected)
  51. return button
  52. }()
  53. private lazy var screenSharingIcon: UIImageView = {
  54. let imageView = UIImageView()
  55. imageView.image = UIImage(named: "room_unmute_share_screen", in: tuiRoomKitBundle(), compatibleWith: nil)
  56. imageView.isHidden = true
  57. return imageView
  58. }()
  59. let inviteStageButton: UIButton = {
  60. let button = UIButton(type: .custom)
  61. button.backgroundColor = UIColor(0x0565FA)
  62. button.layer.cornerRadius = 6
  63. button.setTitle(.inviteSeatText, for: .normal)
  64. button.setTitleColor(UIColor(0xFFFFFF), for: .normal)
  65. button.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .regular)
  66. button.isHidden = true
  67. return button
  68. }()
  69. let callButton: UIButton = {
  70. let button = UIButton(type: .custom)
  71. button.backgroundColor = UIColor(0x6B758A)
  72. button.layer.cornerRadius = 6
  73. button.setTitle(.callText, for: .normal)
  74. button.setTitleColor(UIColor(0xB2BBD1), for: .normal)
  75. button.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 12)
  76. button.isHidden = true
  77. return button
  78. }()
  79. let callingLabel: UILabel = {
  80. let label = UILabel ()
  81. label.font = UIFont(name: "PingFangSC-Regular", size: 14)
  82. label.backgroundColor = UIColor.clear
  83. label.textColor = UIColor(0xD5E0F2)
  84. label.text = .callingText
  85. label.isHidden = true
  86. return label
  87. }()
  88. let notJoiningLabel: UILabel = {
  89. let label = UILabel ()
  90. label.font = UIFont(name: "PingFangSC-Regular", size: 14)
  91. label.backgroundColor = UIColor.clear
  92. label.textAlignment = .right
  93. label.textColor = UIColor(0xD5E0F2)
  94. label.text = .notJoinNowText
  95. label.isHidden = true
  96. return label
  97. }()
  98. let downLineView : UIView = {
  99. let view = UIView()
  100. view.backgroundColor = UIColor(0x4F586B,alpha: 0.3)
  101. return view
  102. }()
  103. init(attendeeModel: UserEntity ,viewModel: UserListViewModel) {
  104. self.attendeeModel = attendeeModel
  105. self.viewModel = viewModel
  106. super.init(style: .default, reuseIdentifier: "UserListCell")
  107. }
  108. private var isViewReady: Bool = false
  109. override func didMoveToWindow() {
  110. super.didMoveToWindow()
  111. guard !isViewReady else { return }
  112. isViewReady = true
  113. constructViewHierarchy()
  114. activateConstraints()
  115. bindInteraction()
  116. }
  117. required init?(coder: NSCoder) {
  118. fatalError("init(coder:) has not been implemented")
  119. }
  120. func constructViewHierarchy() {
  121. contentView.addSubview(avatarImageView)
  122. contentView.addSubview(userLabel)
  123. contentView.addSubview(roleImageView)
  124. contentView.addSubview(roleLabel)
  125. contentView.addSubview(muteAudioButton)
  126. contentView.addSubview(muteVideoButton)
  127. contentView.addSubview(inviteStageButton)
  128. contentView.addSubview(downLineView)
  129. contentView.addSubview(callButton)
  130. contentView.addSubview(callingLabel)
  131. contentView.addSubview(notJoiningLabel)
  132. contentView.addSubview(screenSharingIcon)
  133. }
  134. func activateConstraints() {
  135. avatarImageView.snp.makeConstraints { make in
  136. make.width.height.equalTo(40)
  137. make.leading.equalToSuperview()
  138. make.centerY.equalToSuperview()
  139. }
  140. muteVideoButton.snp.makeConstraints { make in
  141. make.width.height.equalTo(20.scale375())
  142. make.trailing.equalToSuperview()
  143. make.centerY.equalTo(self.avatarImageView)
  144. }
  145. muteAudioButton.snp.makeConstraints { make in
  146. make.width.height.equalTo(20.scale375())
  147. make.trailing.equalTo(self.muteVideoButton.snp.leading).offset(-20.scale375())
  148. make.centerY.equalTo(self.avatarImageView)
  149. }
  150. screenSharingIcon.snp.makeConstraints { make in
  151. make.width.height.equalTo(20.scale375())
  152. make.trailing.equalTo(self.muteAudioButton.snp.leading).offset(-20.scale375())
  153. make.centerY.equalTo(self.avatarImageView)
  154. }
  155. inviteStageButton.snp.makeConstraints { make in
  156. make.trailing.equalToSuperview()
  157. make.centerY.equalTo(self.avatarImageView)
  158. make.width.equalTo(80.scale375())
  159. make.height.equalTo(30.scale375Height())
  160. }
  161. userLabel.snp.makeConstraints { make in
  162. if attendeeModel.userRole == .generalUser {
  163. make.centerY.equalToSuperview()
  164. } else {
  165. make.top.equalToSuperview().offset(10.scale375Height())
  166. }
  167. make.leading.equalTo(avatarImageView.snp.trailing).offset(12.scale375())
  168. make.width.equalTo(150.scale375())
  169. make.height.equalTo(22.scale375())
  170. }
  171. roleImageView.snp.makeConstraints { make in
  172. make.top.equalTo(userLabel.snp.bottom).offset(2.scale375Height())
  173. make.leading.equalTo(avatarImageView.snp.trailing).offset(12.scale375())
  174. make.width.height.equalTo(14.scale375())
  175. }
  176. roleLabel.snp.makeConstraints { make in
  177. make.centerY.equalTo(roleImageView)
  178. make.leading.equalTo(roleImageView.snp.trailing).offset(2.scale375())
  179. make.trailing.equalTo(81.scale375())
  180. make.height.equalTo(16.scale375())
  181. }
  182. callButton.snp.makeConstraints { make in
  183. make.trailing.equalToSuperview()
  184. make.centerY.equalTo(self.avatarImageView)
  185. make.width.equalTo(48.scale375())
  186. make.height.equalTo(28.scale375Height())
  187. }
  188. callingLabel.snp.makeConstraints { make in
  189. make.trailing.equalToSuperview()
  190. make.centerY.equalTo(self.avatarImageView)
  191. make.width.equalTo(60.scale375())
  192. make.height.equalTo(28.scale375Height())
  193. }
  194. notJoiningLabel.snp.makeConstraints{ make in
  195. make.trailing.equalTo(callButton.snp.leading).offset(-12.scale375())
  196. make.centerY.equalTo(self.avatarImageView)
  197. make.width.equalTo(120.scale375())
  198. make.height.equalTo(28.scale375Height())
  199. }
  200. downLineView.snp.makeConstraints { make in
  201. make.leading.equalTo(userLabel)
  202. make.trailing.equalToSuperview()
  203. make.bottom.equalToSuperview()
  204. make.height.equalTo(1.scale375())
  205. }
  206. }
  207. func bindInteraction() {
  208. backgroundColor = UIColor(0x17181F)
  209. setupViewState(item: attendeeModel)
  210. inviteStageButton.addTarget(self, action: #selector(inviteStageAction(sender:)), for: .touchUpInside)
  211. muteAudioButton.addTarget(self, action: #selector(showUserManageAction(sender:)), for: .touchUpInside)
  212. muteVideoButton.addTarget(self, action: #selector(showUserManageAction(sender:)), for: .touchUpInside)
  213. callButton.addTarget(self, action: #selector(inviteEnterAction(sender:)), for: .touchUpInside)
  214. }
  215. func setupViewState(item: UserEntity) {
  216. let placeholder = UIImage(named: "room_default_user", in: tuiRoomKitBundle(), compatibleWith: nil)
  217. if let url = URL(string: item.avatarUrl) {
  218. avatarImageView.sd_setImage(with: url, placeholderImage: placeholder)
  219. } else {
  220. avatarImageView.image = placeholder
  221. }
  222. if item.userId == viewModel.currentUser.userId {
  223. userLabel.text = item.userName + "(" + .meText + ")"
  224. } else {
  225. userLabel.text = item.userName
  226. }
  227. switch item.userRole {
  228. case .roomOwner:
  229. roleImageView.image = UIImage(named: "room_role_owner", in: tuiRoomKitBundle(), compatibleWith: nil)
  230. roleLabel.text = .ownerText
  231. case .administrator:
  232. roleImageView.image = UIImage(named: "room_role_administrator", in: tuiRoomKitBundle(), compatibleWith: nil)
  233. roleLabel.text = .administratorText
  234. default: break
  235. }
  236. roleImageView.isHidden = item.userRole == .generalUser
  237. roleLabel.isHidden = item.userRole == .generalUser
  238. muteAudioButton.isSelected = !item.hasAudioStream
  239. muteVideoButton.isSelected = !item.hasVideoStream
  240. screenSharingIcon.isHidden = !item.hasScreenStream
  241. if viewModel.roomInfo.isSeatEnabled {
  242. muteAudioButton.isHidden = !attendeeModel.isOnSeat
  243. muteVideoButton.isHidden = !attendeeModel.isOnSeat
  244. if viewModel.checkSelfInviteAbility(invitee: attendeeModel) {
  245. inviteStageButton.isHidden = attendeeModel.isOnSeat
  246. } else {
  247. inviteStageButton.isHidden = true
  248. }
  249. }
  250. setupCallingViewState(item: item)
  251. }
  252. private func setupCallingViewState(item: UserEntity) {
  253. if let index = viewModel.invitationList.firstIndex(where: { $0.invitee.userId == item.userId }) {
  254. let invitation = viewModel.invitationList[index]
  255. muteAudioButton.isHidden = true
  256. muteVideoButton.isHidden = true
  257. inviteStageButton.isHidden = true
  258. let isPending = invitation.status == .pending
  259. callButton.isHidden = isPending
  260. callingLabel.isHidden = !isPending
  261. notJoiningLabel.isHidden = true
  262. }
  263. }
  264. func showNotJoiningLabel() {
  265. self.notJoiningLabel.isHidden = false
  266. DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
  267. self.notJoiningLabel.isHidden = true
  268. }
  269. }
  270. @objc func inviteStageAction(sender: UIButton) {
  271. viewModel.userId = attendeeModel.userId
  272. viewModel.inviteSeatAction(sender: sender)
  273. }
  274. @objc func showUserManageAction(sender: UIButton) {
  275. viewModel.showUserManageViewAction(userId: attendeeModel.userId, userName: attendeeModel.userName)
  276. }
  277. @objc func inviteEnterAction(sender: UIButton) {
  278. self.conferenceStore.dispatch(action: ConferenceInvitationActions.inviteUsers(payload: (viewModel.roomInfo.roomId, [attendeeModel.userId])))
  279. }
  280. deinit {
  281. debugPrint("deinit \(self)")
  282. }
  283. @Injected(\.conferenceStore) private var conferenceStore
  284. }
  285. private extension String {
  286. static var inviteSeatText: String {
  287. localized("Invite to stage")
  288. }
  289. static var meText: String {
  290. localized("Me")
  291. }
  292. static var ownerText: String {
  293. localized("Host")
  294. }
  295. static var administratorText: String {
  296. localized("Administrator")
  297. }
  298. static var callText: String {
  299. localized("Call")
  300. }
  301. static var callingText: String {
  302. localized("Calling...")
  303. }
  304. static var notJoinNowText: String {
  305. localized("Not joining for now")
  306. }
  307. }