BottomViewModel.swift 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814
  1. //
  2. // BottomViewModel.swift
  3. //
  4. // Created by aby on 2022/12/22.
  5. // Copyright © 2022 Tencent. All rights reserved.
  6. //
  7. import Foundation
  8. import RTCRoomEngine
  9. import TUICore
  10. import Factory
  11. import Combine
  12. protocol BottomViewModelResponder: AnyObject {
  13. func updateButtonView(item: ButtonItemData)
  14. func makeToast(text: String)
  15. func updataBottomView(isUp:Bool)
  16. func updateStackView(items: [ButtonItemData])
  17. func showAlert(title: String?, message: String?, sureTitle:String?, declineTitle: String?, sureBlock: (() -> ())?, declineBlock: (() -> ())?)
  18. }
  19. class BottomViewModel: NSObject {
  20. private(set) var viewItems: [ButtonItemData] = []
  21. weak var viewResponder: BottomViewModelResponder?
  22. var engineManager: EngineManager {
  23. EngineManager.shared
  24. }
  25. var engineEventCenter: EngineEventCenter {
  26. EngineEventCenter.shared
  27. }
  28. var roomInfo: TUIRoomInfo {
  29. engineManager.store.roomInfo
  30. }
  31. var currentUser: UserEntity {
  32. engineManager.store.currentUser
  33. }
  34. var attendeeList: [UserEntity] {
  35. engineManager.store.attendeeList
  36. }
  37. var inviteSeatList: [RequestEntity] {
  38. engineManager.store.inviteSeatList
  39. }
  40. var isCalledFromShareScreen = false
  41. var cancellableSet = Set<AnyCancellable>()
  42. private lazy var memberItem: ButtonItemData = {
  43. let memberItem = ButtonItemData()
  44. memberItem.normalTitle = String(format: .memberText, attendeeList.count)
  45. memberItem.normalIcon = "room_member"
  46. memberItem.resourceBundle = tuiRoomKitBundle()
  47. memberItem.action = { [weak self] sender in
  48. guard let self = self, let button = sender as? UIButton else { return }
  49. self.memberAction(sender: button)
  50. }
  51. return memberItem
  52. }()
  53. private lazy var muteAudioItem: ButtonItemData = {
  54. let muteAudioItem = ButtonItemData()
  55. muteAudioItem.normalTitle = .muteAudioText
  56. muteAudioItem.selectedTitle = .unMuteAudioText
  57. muteAudioItem.normalIcon = "room_unMute_audio"
  58. muteAudioItem.selectedIcon = "room_mic_off"
  59. muteAudioItem.resourceBundle = tuiRoomKitBundle()
  60. muteAudioItem.buttonType = .muteAudioItemType
  61. muteAudioItem.isSelect = !currentUser.hasAudioStream
  62. muteAudioItem.action = { [weak self] sender in
  63. guard let self = self, let button = sender as? UIButton else { return }
  64. self.muteAudioAction(sender: button)
  65. }
  66. return muteAudioItem
  67. }()
  68. private lazy var muteVideoItem: ButtonItemData = {
  69. let muteVideoItem = ButtonItemData()
  70. muteVideoItem.normalTitle = .muteVideoText
  71. muteVideoItem.selectedTitle = .unMuteVideoText
  72. muteVideoItem.normalIcon = "room_camera_on"
  73. muteVideoItem.selectedIcon = "room_camera_off"
  74. muteVideoItem.resourceBundle = tuiRoomKitBundle()
  75. muteVideoItem.buttonType = .muteVideoItemType
  76. muteVideoItem.isSelect = !currentUser.hasVideoStream
  77. muteVideoItem.action = { [weak self] sender in
  78. guard let self = self, let button = sender as? UIButton else { return }
  79. self.muteVideoAction(sender: button)
  80. }
  81. return muteVideoItem
  82. }()
  83. private lazy var shareScreenItem: ButtonItemData = {
  84. let shareScreenItem = ButtonItemData()
  85. shareScreenItem.normalTitle = .shareScreenOnText
  86. shareScreenItem.selectedTitle = .shareScreenOffText
  87. shareScreenItem.normalIcon = "room_shareScreen_on"
  88. shareScreenItem.selectedIcon = "room_shareScreen_off"
  89. shareScreenItem.resourceBundle = tuiRoomKitBundle()
  90. shareScreenItem.buttonType = .shareScreenItemType
  91. shareScreenItem.action = { [weak self] sender in
  92. guard let self = self, let button = sender as? UIButton else { return }
  93. self.shareScreenAction(sender: button)
  94. }
  95. return shareScreenItem
  96. }()
  97. private lazy var chatItem: ButtonItemData = {
  98. let chatItem = ButtonItemData()
  99. chatItem.normalIcon = "room_chat"
  100. chatItem.normalTitle = .chatText
  101. chatItem.resourceBundle = tuiRoomKitBundle()
  102. chatItem.action = { [weak self] sender in
  103. guard let self = self, let button = sender as? UIButton else { return }
  104. self.chatAction(sender: button)
  105. }
  106. return chatItem
  107. }()
  108. private lazy var moreItem: ButtonItemData = {
  109. let moreItem = ButtonItemData()
  110. moreItem.normalTitle = .unfoldText
  111. moreItem.normalIcon = "room_more"
  112. moreItem.selectedTitle = .dropText
  113. moreItem.selectedIcon = "room_drop"
  114. moreItem.resourceBundle = tuiRoomKitBundle()
  115. moreItem.buttonType = .moreItemType
  116. moreItem.action = { [weak self] sender in
  117. guard let self = self, let button = sender as? UIButton else { return }
  118. self.moreAction(sender: button)
  119. }
  120. return moreItem
  121. }()
  122. private lazy var floatItem: ButtonItemData = {
  123. let floatItem = ButtonItemData()
  124. floatItem.normalTitle = .floatText
  125. floatItem.normalIcon = "room_float"
  126. floatItem.resourceBundle = tuiRoomKitBundle()
  127. floatItem.action = { [weak self] sender in
  128. guard let self = self, let button = sender as? UIButton else { return }
  129. self.floatAction(sender: button)
  130. }
  131. return floatItem
  132. }()
  133. private lazy var setupItem: ButtonItemData = {
  134. let setupItem = ButtonItemData()
  135. setupItem.normalTitle = .setupText
  136. setupItem.normalIcon = "room_setting"
  137. setupItem.resourceBundle = tuiRoomKitBundle()
  138. setupItem.action = { [weak self] sender in
  139. guard let self = self, let button = sender as? UIButton else { return }
  140. self.setupAction(sender: button)
  141. }
  142. return setupItem
  143. }()
  144. private lazy var inviteItem: ButtonItemData = {
  145. let inviteItem = ButtonItemData()
  146. inviteItem.normalTitle = .inviteText
  147. inviteItem.normalIcon = "room_invite"
  148. inviteItem.resourceBundle = tuiRoomKitBundle()
  149. inviteItem.action = { [weak self] sender in
  150. guard let self = self, let button = sender as? UIButton else { return }
  151. self.inviteAction(sender: button)
  152. }
  153. return inviteItem
  154. }()
  155. private lazy var raiseHandApplyItem: ButtonItemData = {
  156. let item = ButtonItemData()
  157. item.normalTitle = .stageManagementText
  158. item.normalIcon = "room_hand_raise_list"
  159. item.resourceBundle = tuiRoomKitBundle()
  160. item.buttonType = .raiseHandApplyItemType
  161. item.noticeText = String(inviteSeatList.count)
  162. item.hasNotice = inviteSeatList.count > 0
  163. item.action = { [weak self] sender in
  164. guard let self = self, let button = sender as? UIButton else { return }
  165. self.raiseHandApplyAction(sender: button)
  166. }
  167. return item
  168. }()
  169. private lazy var raiseHandItem: ButtonItemData = {
  170. let item = ButtonItemData()
  171. item.normalTitle = currentUser.userRole == .generalUser ? .applyJoinStageText : .joinStageText
  172. item.normalIcon = "room_apply_join_stage"
  173. item.selectedIcon = "room_cancel_request"
  174. item.selectedTitle = .cancelStageText
  175. item.resourceBundle = tuiRoomKitBundle()
  176. item.buttonType = .raiseHandItemType
  177. item.isSelect = engineManager.store.selfTakeSeatRequestId != nil
  178. item.action = { [weak self] sender in
  179. guard let self = self, let button = sender as? UIButton else { return }
  180. self.raiseHandAction(sender: button)
  181. }
  182. return item
  183. }()
  184. private lazy var leaveSeatHandItem: ButtonItemData = {
  185. let item = ButtonItemData()
  186. item.normalIcon = "room_leave_seat"
  187. item.selectedIcon = "room_apply_join_stage"
  188. item.normalTitle = .leaveSeatText
  189. item.selectedTitle = .applyJoinStageText
  190. item.buttonType = .leaveSeatItemType
  191. item.resourceBundle = tuiRoomKitBundle()
  192. item.action = { [weak self] sender in
  193. guard let self = self, let button = sender as? UIButton else { return }
  194. self.leaveSeatAction(sender: button)
  195. }
  196. return item
  197. }()
  198. private lazy var invitationPopupPublisher = {
  199. conferenceStore.select(ViewSelectors.getShowinvitationPopupView)
  200. }()
  201. override init() {
  202. super.init()
  203. createBottomData()
  204. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_UserOnSeatChanged, responder: self)
  205. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
  206. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_CurrentUserHasAudioStream, responder: self)
  207. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_CurrentUserHasVideoStream, responder: self)
  208. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_SomeoneSharing, responder: self)
  209. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_RenewUserList, responder: self)
  210. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_RenewSeatList, responder: self)
  211. EngineEventCenter.shared.subscribeEngine(event: .onAllUserCameraDisableChanged, observer: self)
  212. EngineEventCenter.shared.subscribeEngine(event: .onAllUserMicrophoneDisableChanged, observer: self)
  213. EngineEventCenter.shared.subscribeEngine(event: .onInitialSelfUserInfo, observer: self)
  214. EngineEventCenter.shared.subscribeEngine(event: .onRemoteUserEnterRoom, observer: self)
  215. NotificationCenter.default.addObserver(self,
  216. selector: #selector(onUserScreenCaptureStarted),
  217. name: UIScreen.capturedDidChangeNotification, object: nil)
  218. invitationPopupPublisher
  219. .receive(on: DispatchQueue.mainQueue)
  220. .sink { [weak self] showInvitationPopupView in
  221. guard let self = self else { return }
  222. if showInvitationPopupView {
  223. showMemberSelectViewAction()
  224. }
  225. }
  226. .store(in: &cancellableSet)
  227. }
  228. func createBottomData() {
  229. creatBaseBottomData()
  230. createMoreBottomData()
  231. }
  232. func creatBaseBottomData(){
  233. viewItems.append(memberItem)
  234. viewItems.append(muteAudioItem)
  235. viewItems.append(muteVideoItem)
  236. if roomInfo.isSeatEnabled {
  237. if currentUser.userRole == .roomOwner {
  238. viewItems.append(raiseHandApplyItem)
  239. } else {
  240. if currentUser.isOnSeat {
  241. viewItems.append(leaveSeatHandItem)
  242. } else {
  243. viewItems.append(raiseHandItem)
  244. }
  245. }
  246. if currentUser.userRole == .administrator {
  247. viewItems.append(raiseHandApplyItem)
  248. }
  249. }
  250. viewItems.append(shareScreenItem)
  251. if hasTUIChatItem() {
  252. viewItems.append(chatItem)
  253. }
  254. viewItems.append(moreItem)
  255. updateAudioItem()
  256. updateVideoItem()
  257. }
  258. func createMoreBottomData(){
  259. viewItems.append(inviteItem)
  260. viewItems.append(floatItem)
  261. viewItems.append(setupItem)
  262. reorderTheMoreItem()
  263. }
  264. func memberAction(sender: UIButton) {
  265. RoomRouter.shared.presentPopUpViewController(viewType: .userListViewType, height: 720.scale375Height(), backgroundColor: UIColor(0x17181F))
  266. }
  267. func muteAudioAction(sender: UIButton) {
  268. engineEventCenter.notifyUIEvent(key: .TUIRoomKitService_SetToolBarDelayHidden, param: ["isDelay": true])
  269. if currentUser.hasAudioStream {
  270. engineManager.muteLocalAudio()
  271. return
  272. }
  273. //If all hosts are muted, ordinary members of the room cannot turn on their microphones.
  274. if self.roomInfo.isMicrophoneDisableForAllUser && currentUser.userRole == .generalUser {
  275. viewResponder?.makeToast(text: .muteAudioRoomReasonText)
  276. return
  277. }
  278. //If you are speaking in a room with your hand raised and you are not on the microphone, you cannot turn on the microphone.
  279. if roomInfo.isSeatEnabled, !currentUser.isOnSeat {
  280. viewResponder?.makeToast(text: .muteSeatReasonText)
  281. return
  282. }
  283. engineManager.unmuteLocalAudio()
  284. guard !engineManager.store.audioSetting.isMicOpened else { return }
  285. engineManager.openLocalMicrophone()
  286. }
  287. func muteVideoAction(sender: UIButton) {
  288. engineEventCenter.notifyUIEvent(key: .TUIRoomKitService_SetToolBarDelayHidden, param: ["isDelay": true])
  289. if currentUser.hasVideoStream {
  290. engineManager.closeLocalCamera()
  291. return
  292. }
  293. //If the entire host bans paintings, ordinary members of the room cannot turn on the camera.
  294. if self.roomInfo.isCameraDisableForAllUser && self.currentUser.userRole == .generalUser {
  295. viewResponder?.makeToast(text: .muteVideoRoomReasonText)
  296. return
  297. }
  298. //If you are speaking in a room with your hands raised and you are not on the mic, you cannot turn on the camera.
  299. if roomInfo.isSeatEnabled, !currentUser.isOnSeat {
  300. viewResponder?.makeToast(text: .muteSeatReasonText)
  301. return
  302. }
  303. engineManager.setLocalVideoView(nil)
  304. engineManager.openLocalCamera()
  305. }
  306. func raiseHandApplyAction(sender: UIButton) {
  307. RoomRouter.shared.presentPopUpViewController(viewType: .raiseHandApplicationListViewType, height: 720.scale375Height(), backgroundColor: UIColor(0x22262E))
  308. }
  309. func raiseHandAction(sender: UIButton) {
  310. sender.isSelected = !sender.isSelected
  311. engineEventCenter.notifyUIEvent(key: .TUIRoomKitService_SetToolBarDelayHidden, param: ["isDelay": true])
  312. if sender.isSelected {
  313. handleRaiseHandAction()
  314. } else {
  315. handleCancelRaiseHandAction()
  316. }
  317. }
  318. func handleRaiseHandAction() {
  319. _ = engineManager.takeSeat() { [weak self] _,_ in
  320. guard let self = self else { return }
  321. self.viewResponder?.makeToast(text: .takenSeatText)
  322. } onRejected: { [weak self] _, _, _ in
  323. guard let self = self else { return }
  324. self.viewResponder?.makeToast(text: .rejectedTakeSeatText)
  325. self.changeItemSelectState(type: .raiseHandItemType, isSelected: false)
  326. } onTimeout: { [weak self] requestId, userId in
  327. guard let self = self else { return }
  328. self.viewResponder?.makeToast(text: .joinStageApplicationTimedOutText)
  329. self.changeItemSelectState(type: .raiseHandItemType, isSelected: false)
  330. } onError: { [weak self] _, _, code, message in
  331. guard let self = self else { return }
  332. self.changeItemSelectState(type: .raiseHandItemType, isSelected: false)
  333. }
  334. changeItemSelectState(type: .raiseHandItemType)
  335. guard currentUser.userRole == .generalUser else { return }
  336. viewResponder?.makeToast(text: .applicationHasSentText)
  337. }
  338. func handleCancelRaiseHandAction() {
  339. engineManager.cancelTakeSeatRequest()
  340. changeItemSelectState(type: .raiseHandItemType)
  341. viewResponder?.makeToast(text: .joinStageApplicationCancelledText)
  342. }
  343. func leaveSeatAction(sender: UIButton) {
  344. engineEventCenter.notifyUIEvent(key: .TUIRoomKitService_SetToolBarDelayHidden, param: ["isDelay": true])
  345. if currentUser.userRole == .administrator {
  346. engineManager.leaveSeat()
  347. } else {
  348. viewResponder?.showAlert(title: .leaveSeatTitle, message: .leaveSeatMessage, sureTitle: .leaveSeatText, declineTitle: .toastCancelText, sureBlock: { [weak self] in
  349. guard let self = self else { return }
  350. self.engineManager.leaveSeat()
  351. }, declineBlock: nil)
  352. }
  353. }
  354. func shareScreenAction(sender: UIButton) {
  355. if #available(iOS 12.0, *) {
  356. guard let item = viewItems.first(where: { $0.buttonType == .shareScreenItemType })
  357. else { return }
  358. if !item.isSelect {
  359. //If you are in a room where you are raising your hand to speak, and you are not on the mic, you cannot share your screen.
  360. guard !(roomInfo.isSeatEnabled && !currentUser.isOnSeat) else {
  361. viewResponder?.makeToast(text: .muteSeatReasonText)
  362. return
  363. }
  364. //If someone else is screen sharing, you can no longer screen share yourself
  365. guard engineManager.store.attendeeList.first(where: {$0.hasScreenStream}) == nil else {
  366. viewResponder?.makeToast(text: .othersScreenSharingText)
  367. return
  368. }
  369. guard !(currentUser.userRole == .generalUser && roomInfo.isScreenShareDisableForAllUser) else {
  370. viewResponder?.makeToast(text: .failedShareOnlyHostOrAdminCanShare)
  371. return
  372. }
  373. if TUICore.callService(TUICore_PrivacyService,
  374. method: TUICore_PrivacyService_ScreenShareAntifraudReminderMethod,
  375. param: nil, resultCallback: { [weak self] code, message, param in
  376. guard let self = self else { return }
  377. if code == TUICore_PrivacyService_EnableScreenShareAntifraudReminderMethod_Continue {
  378. self.isCalledFromShareScreen = true
  379. BroadcastLauncher.launch()
  380. }
  381. }) == nil {
  382. isCalledFromShareScreen = true
  383. BroadcastLauncher.launch()
  384. }
  385. } else {
  386. viewResponder?.showAlert(title: .toastTitleText, message: .toastMessageText, sureTitle: .toastStopText, declineTitle: .toastCancelText, sureBlock: { [weak self] in
  387. guard let self = self else { return }
  388. self.engineManager.stopScreenCapture()
  389. }, declineBlock: nil)
  390. }
  391. } else {
  392. viewResponder?.makeToast(text: .versionLowToastText)
  393. }
  394. }
  395. func chatAction(sender: UIButton) {
  396. let user = engineManager.store.currentUser
  397. let roomInfo = engineManager.store.roomInfo
  398. RoomRouter.shared.pushToChatController(user: user, roomInfo: roomInfo)
  399. }
  400. func moreAction(sender: UIButton) {
  401. sender.isSelected = !sender.isSelected
  402. engineEventCenter.notifyUIEvent(key: .TUIRoomKitService_SetToolBarDelayHidden, param: ["isDelay": !sender.isSelected])
  403. viewResponder?.updataBottomView(isUp: sender.isSelected)
  404. changeItemSelectState(type: .moreItemType)
  405. }
  406. func inviteAction(sender: UIButton) {
  407. RoomRouter.shared.presentPopUpViewController(viewType: .inviteViewType, height: 158.scale375Height())
  408. }
  409. func floatAction(sender: UIButton) {
  410. EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_ShowRoomVideoFloatView, param: [:])
  411. }
  412. func setupAction(sender: UIButton) {
  413. RoomRouter.shared.presentPopUpViewController(viewType: .mediaSettingViewType, height: 709.scale375Height())
  414. }
  415. func showMemberSelectViewAction() {
  416. conferenceStore.dispatch(action: InvitationViewActions.resetPopupViewFlag())
  417. let inRoomUsers = attendeeList.map{ UserInfo(userEntity: $0).convertToUser() }
  418. // TODO: @jeremiawang Use ConferenceRouter to push ContactVC
  419. let participants = ConferenceParticipants(unSelectableList: inRoomUsers)
  420. var contactViewController: (ContactViewProtocol & UIViewController)?
  421. if ConferenceSession.sharedInstance.implementation.hasCustomContacts {
  422. contactViewController = Container.shared.contactViewController.resolve(participants) as? (ContactViewProtocol & UIViewController)
  423. } else {
  424. contactViewController = SelectMemberViewController(participants: participants)
  425. }
  426. guard let contactViewController = contactViewController else { return }
  427. contactViewController.delegate = self
  428. RoomRouter.shared.push(viewController: contactViewController)
  429. }
  430. @objc func onUserScreenCaptureStarted(notification:Notification)
  431. {
  432. guard let screen = notification.object as? UIScreen else {return}
  433. if screen.isCaptured,isCalledFromShareScreen {
  434. engineManager.startScreenCapture()
  435. }
  436. }
  437. deinit {
  438. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_UserOnSeatChanged, responder: self)
  439. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
  440. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_CurrentUserHasAudioStream, responder: self)
  441. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_CurrentUserHasVideoStream, responder: self)
  442. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_SomeoneSharing, responder: self)
  443. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_RenewUserList, responder: self)
  444. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_RenewSeatList, responder: self)
  445. EngineEventCenter.shared.unsubscribeEngine(event: .onAllUserCameraDisableChanged, observer: self)
  446. EngineEventCenter.shared.unsubscribeEngine(event: .onAllUserMicrophoneDisableChanged, observer: self)
  447. EngineEventCenter.shared.unsubscribeEngine(event: .onInitialSelfUserInfo, observer: self)
  448. EngineEventCenter.shared.unsubscribeEngine(event: .onRemoteUserEnterRoom, observer: self)
  449. NotificationCenter.default.removeObserver(self, name: UIScreen.capturedDidChangeNotification, object: nil)
  450. debugPrint("deinit \(self)")
  451. }
  452. @Injected(\.conferenceStore) private var conferenceStore: ConferenceStore
  453. }
  454. // MARK: - Private
  455. extension BottomViewModel {
  456. private func hasTUIChatItem() -> Bool {
  457. return TUICore.getService(TUICore_TUIChatService) != nil
  458. }
  459. private func changeItemSelectState(type: ButtonItemData.ButtonType, isSelected: Bool? = nil) {
  460. guard let item = viewItems.first(where: { $0.buttonType == type })
  461. else { return }
  462. if let isSelected = isSelected {
  463. item.isSelect = isSelected
  464. } else {
  465. item.isSelect = !item.isSelect
  466. }
  467. viewResponder?.updateButtonView(item: item)
  468. }
  469. private func updateRaiseHandItem() {
  470. guard roomInfo.isSeatEnabled else { return }
  471. raiseHandItem.normalTitle = currentUser.userRole == .generalUser ? .applyJoinStageText : .joinStageText
  472. leaveSeatHandItem.isSelect = false
  473. raiseHandItem.isSelect = false
  474. if currentUser.userRole == .roomOwner {
  475. guard let index = viewItems.firstIndex(where:{ $0.buttonType == .leaveSeatItemType || $0.buttonType == .raiseHandItemType }) else { return }
  476. viewItems.remove(at: index)
  477. } else if let index = viewItems.firstIndex(where:{ $0.buttonType == .leaveSeatItemType || $0.buttonType == .raiseHandItemType }) {
  478. if currentUser.isOnSeat {
  479. viewItems[index] = leaveSeatHandItem
  480. } else {
  481. viewItems[index] = raiseHandItem
  482. }
  483. } else {
  484. if currentUser.isOnSeat {
  485. addViewItem(buttonItem: leaveSeatHandItem, index: 3)
  486. } else {
  487. addViewItem(buttonItem: raiseHandItem, index: 1)
  488. }
  489. }
  490. }
  491. private func reorderTheMoreItem() {
  492. guard viewItems.count > 6 else { return }
  493. guard let index = viewItems.firstIndex(where: { $0.buttonType == .moreItemType }), index != 5 else { return }
  494. viewItems.remove(at: index)
  495. viewItems.insert(moreItem, at: 5)
  496. }
  497. private func removeViewItem(buttonType: ButtonItemData.ButtonType) {
  498. viewItems.removeAll(where: { $0.buttonType == buttonType })
  499. }
  500. private func addViewItem(buttonItem: ButtonItemData, index: Int) {
  501. guard !isContainedViewItem(buttonType: buttonItem.buttonType) else { return }
  502. if viewItems.count > index + 1 {
  503. viewItems.insert(buttonItem, at: index)
  504. } else {
  505. viewItems.append(buttonItem)
  506. }
  507. }
  508. private func isContainedViewItem(buttonType: ButtonItemData.ButtonType) -> Bool {
  509. return viewItems.contains(where: { $0.buttonType == buttonType })
  510. }
  511. private func updateAudioItem() {
  512. if roomInfo.isSeatEnabled, currentUser.userRole == .generalUser, !currentUser.isOnSeat {
  513. //If the audience in the room who raises their hand to speak is not on the microphone, the microphone button will not be displayed.
  514. removeViewItem(buttonType: .muteAudioItemType)
  515. } else if !isContainedViewItem(buttonType: .muteAudioItemType) {
  516. addViewItem(buttonItem: muteAudioItem, index: 1)
  517. }
  518. muteAudioItem.isSelect = !currentUser.hasAudioStream
  519. muteAudioItem.alpha = checkMicAuthority() || currentUser.hasAudioStream ? 1 : 0.5
  520. }
  521. private func updateVideoItem() {
  522. if roomInfo.isSeatEnabled, currentUser.userRole == .generalUser, !currentUser.isOnSeat {
  523. removeViewItem(buttonType: .muteVideoItemType)
  524. } else if !isContainedViewItem(buttonType: .muteVideoItemType) {
  525. addViewItem(buttonItem: muteVideoItem, index: 2)
  526. }
  527. muteVideoItem.isSelect = !currentUser.hasVideoStream
  528. muteVideoItem.alpha = checkCameraAuthority() || currentUser.hasVideoStream ? 1 : 0.5
  529. }
  530. private func checkMicAuthority() -> Bool {
  531. if self.roomInfo.isMicrophoneDisableForAllUser && currentUser.userRole == .generalUser {
  532. return false
  533. }
  534. if roomInfo.isSeatEnabled, !currentUser.isOnSeat {
  535. return false
  536. }
  537. return true
  538. }
  539. private func checkCameraAuthority() -> Bool {
  540. if self.roomInfo.isCameraDisableForAllUser && currentUser.userRole == .generalUser {
  541. return false
  542. }
  543. if roomInfo.isSeatEnabled, !currentUser.isOnSeat {
  544. return false
  545. }
  546. return true
  547. }
  548. private func updateRaiseHandApplyItem() {
  549. guard roomInfo.isSeatEnabled else { return }
  550. raiseHandItem.normalTitle = currentUser.userRole == .generalUser ? .applyJoinStageText : .joinStageText
  551. if currentUser.userRole == .roomOwner {
  552. addViewItem(buttonItem: raiseHandApplyItem, index: 3)
  553. } else if currentUser.userRole == .administrator {
  554. addViewItem(buttonItem: raiseHandApplyItem, index: 4)
  555. } else {
  556. removeViewItem(buttonType: .raiseHandApplyItemType)
  557. }
  558. }
  559. }
  560. extension BottomViewModel: RoomKitUIEventResponder {
  561. func onNotifyUIEvent(key: EngineEventCenter.RoomUIEvent, Object: Any?, info: [AnyHashable : Any]?) {
  562. switch key {
  563. case .TUIRoomKitService_UserOnSeatChanged:
  564. guard roomInfo.isSeatEnabled else { return }
  565. updateRaiseHandItem()
  566. updateAudioItem()
  567. updateVideoItem()
  568. reorderTheMoreItem()
  569. viewResponder?.updateStackView(items: viewItems)
  570. case .TUIRoomKitService_CurrentUserRoleChanged:
  571. updateAudioItem()
  572. updateVideoItem()
  573. updateRaiseHandApplyItem()
  574. updateRaiseHandItem()
  575. reorderTheMoreItem()
  576. viewResponder?.updateStackView(items: viewItems)
  577. case .TUIRoomKitService_CurrentUserHasAudioStream:
  578. guard let hasAudio = info?["hasAudio"] as? Bool else { return }
  579. guard let reason = info?["reason"] as? TUIChangeReason else { return }
  580. if !hasAudio, reason == .byAdmin, !roomInfo.isMicrophoneDisableForAllUser {
  581. if !roomInfo.isSeatEnabled {
  582. viewResponder?.makeToast(text: .noticeMicrophoneOffTitleText)
  583. } else if currentUser.isOnSeat {
  584. viewResponder?.makeToast(text: .noticeMicrophoneOffTitleText)
  585. }
  586. }
  587. updateAudioItem()
  588. viewResponder?.updateButtonView(item: muteAudioItem)
  589. case .TUIRoomKitService_CurrentUserHasVideoStream:
  590. guard let hasVideo = info?["hasVideo"] as? Bool else { return }
  591. guard let reason = info?["reason"] as? TUIChangeReason else { return }
  592. if !hasVideo, reason == .byAdmin, !roomInfo.isCameraDisableForAllUser {
  593. if !roomInfo.isSeatEnabled {
  594. viewResponder?.makeToast(text: .noticeCameraOffTitleText)
  595. } else if currentUser.isOnSeat {
  596. viewResponder?.makeToast(text: .noticeCameraOffTitleText)
  597. }
  598. }
  599. updateVideoItem()
  600. viewResponder?.updateButtonView(item: muteVideoItem)
  601. case .TUIRoomKitService_SomeoneSharing:
  602. guard let userId = info?["userId"] as? String else { return }
  603. guard let hasVideo = info?["hasVideo"] as? Bool else { return }
  604. guard userId == currentUser.userId else { return }
  605. changeItemSelectState(type: .shareScreenItemType, isSelected: hasVideo)
  606. if !hasVideo {
  607. isCalledFromShareScreen = false
  608. }
  609. case .TUIRoomKitService_RenewUserList:
  610. memberItem.normalTitle = String(format: .memberText, attendeeList.count)
  611. viewResponder?.updateButtonView(item: memberItem)
  612. case .TUIRoomKitService_RenewSeatList:
  613. raiseHandApplyItem.noticeText = String(inviteSeatList.count)
  614. raiseHandApplyItem.hasNotice = inviteSeatList.count > 0
  615. viewResponder?.updateButtonView(item: raiseHandApplyItem)
  616. default: break
  617. }
  618. }
  619. }
  620. extension BottomViewModel: RoomEngineEventResponder {
  621. func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String : Any]?) {
  622. switch name {
  623. case .onAllUserCameraDisableChanged:
  624. updateVideoItem()
  625. viewResponder?.updateButtonView(item: muteVideoItem)
  626. case .onAllUserMicrophoneDisableChanged:
  627. updateAudioItem()
  628. viewResponder?.updateButtonView(item: muteAudioItem)
  629. case .onInitialSelfUserInfo:
  630. updateAudioItem()
  631. updateVideoItem()
  632. updateRaiseHandApplyItem()
  633. updateRaiseHandItem()
  634. reorderTheMoreItem()
  635. viewResponder?.updateStackView(items: viewItems)
  636. case .onRemoteUserEnterRoom:
  637. guard let userInfo = param?["userInfo"] as? TUIUserInfo else { return }
  638. conferenceStore.dispatch(action: ConferenceInvitationActions.removeInvitation(payload: userInfo.userId))
  639. default: break
  640. }
  641. }
  642. }
  643. extension BottomViewModel: ContactViewSelectDelegate {
  644. func onMemberSelected(_ viewController: any ContactViewProtocol, invitees: [User]) {
  645. let userIdList = invitees.map{ $0.userId }
  646. self.conferenceStore.dispatch(action: ConferenceInvitationActions.inviteUsers(payload: (roomInfo.roomId, userIdList)))
  647. RoomRouter.shared.pop()
  648. if !invitees.isEmpty{
  649. viewResponder?.makeToast(text: .inviteEnterRoomSuccesstext)
  650. }
  651. }
  652. }
  653. private extension String {
  654. static var memberText: String {
  655. localized("Users(%lld)")
  656. }
  657. static var muteAudioText: String {
  658. localized("Mute")
  659. }
  660. static var unMuteAudioText: String {
  661. localized("Unmute")
  662. }
  663. static var muteVideoText: String {
  664. localized("Stop video")
  665. }
  666. static var unMuteVideoText: String {
  667. localized("Start video")
  668. }
  669. static var stageManagementText: String {
  670. localized("Applies")
  671. }
  672. static var cancelStageText: String {
  673. localized("Cancel")
  674. }
  675. static var applyJoinStageText: String {
  676. localized("Join stage")
  677. }
  678. static var leaveSeatText: String {
  679. localized("Step down")
  680. }
  681. static var muteSeatReasonText: String {
  682. localized("Can be turned on after taking the stage")
  683. }
  684. static var muteAudioRoomReasonText: String {
  685. localized("All on mute audio, unable to turn on microphone")
  686. }
  687. static var muteVideoRoomReasonText: String {
  688. localized("All on mute video, unable to turn on camera")
  689. }
  690. static var noticeCameraOffTitleText: String {
  691. localized("The conference owner disabled your video.")
  692. }
  693. static var noticeMicrophoneOffTitleText: String {
  694. localized("You were muted by the host.")
  695. }
  696. static var shareScreenOnText: String {
  697. localized("Share")
  698. }
  699. static var shareScreenOffText: String {
  700. localized("Stop")
  701. }
  702. static var versionLowToastText: String {
  703. localized("Your system version is below 12.0. Please update.")
  704. }
  705. static var chatText: String {
  706. localized("Chat")
  707. }
  708. static var unfoldText: String {
  709. localized("More")
  710. }
  711. static var inviteText: String {
  712. localized("Invite")
  713. }
  714. static var floatText: String {
  715. localized("Floating")
  716. }
  717. static var setupText: String {
  718. localized("Settings")
  719. }
  720. static var dropText: String {
  721. localized("Close")
  722. }
  723. static var rejectedTakeSeatText: String {
  724. localized("Application to go on stage was rejected")
  725. }
  726. static var takenSeatText: String {
  727. localized("Succeed on stage")
  728. }
  729. static var othersScreenSharingText: String {
  730. localized("Another user is sharing the screen.")
  731. }
  732. static var toastTitleText: String {
  733. localized("Share Screen")
  734. }
  735. static var toastMessageText: String {
  736. localized("Stop TUIRoom screen sharing screen live?")
  737. }
  738. static var toastCancelText: String {
  739. localized("Cancel")
  740. }
  741. static var toastStopText: String {
  742. localized("Stop")
  743. }
  744. static var applicationHasSentText: String {
  745. localized("Application has been sent, please wait for the owner/administrator to approve")
  746. }
  747. static var joinStageText: String {
  748. localized("Join stage")
  749. }
  750. static var leaveSeatTitle: String {
  751. localized("Are you sure you want to step down?")
  752. }
  753. static var leaveSeatMessage: String {
  754. localized("To get on stage again, you need to resend the application and wait for the owner/administrator to approve it.")
  755. }
  756. static var joinStageApplicationCancelledText: String {
  757. localized("Application for stage has been cancelled")
  758. }
  759. static var joinStageApplicationTimedOutText: String {
  760. localized("The request to go on stage has timed out")
  761. }
  762. static var inviteEnterRoomSuccesstext: String {
  763. localized("Invitation has been sent, waiting for users to join")
  764. }
  765. static let failedShareOnlyHostOrAdminCanShare = localized("Failed to initiate share, currently only host/admin can share")
  766. }