ConferenceMainViewModel.swift 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. //
  2. // ConferenceMainViewModel.swift
  3. // TUIRoomKit
  4. //
  5. // Created by aby on 2022/12/27.
  6. // Copyright © 2022 Tencent. All rights reserved.
  7. //
  8. import Foundation
  9. import TUICore
  10. import RTCRoomEngine
  11. import Factory
  12. import Combine
  13. protocol ConferenceMainViewResponder: AnyObject {
  14. func makeToast(text: String)
  15. func changeToolBarHiddenState()
  16. func setToolBarDelayHidden(isDelay: Bool)
  17. func showExitRoomView()
  18. func showAlert(title: String?, message: String?, sureTitle:String?, declineTitle: String?, sureBlock: (() -> ())?, declineBlock: (() -> ())?)
  19. func showAlertWithAutoConfirm(title: String?, message: String?, sureTitle:String?, declineTitle: String?, sureBlock: (() -> ())?, declineBlock: (() -> ())?, autoConfirmSeconds: Int?)
  20. func showRaiseHandNoticeView()
  21. func updateRoomInfo(roomInfo: TUIRoomInfo)
  22. func showPasswordView(roomId: String)
  23. func hidePasswordView()
  24. func showRepeatJoinRoomAlert()
  25. func updateWaterMarkLayer(text: String)
  26. }
  27. class ConferenceMainViewModel: NSObject {
  28. weak var viewResponder: ConferenceMainViewResponder? = nil
  29. var engineManager: EngineManager {
  30. EngineManager.shared
  31. }
  32. var store: RoomStore {
  33. engineManager.store
  34. }
  35. var roomInfo: TUIRoomInfo {
  36. store.roomInfo
  37. }
  38. var currentUser: UserEntity {
  39. store.currentUser
  40. }
  41. let roomRouter: RoomRouter = RoomRouter.shared
  42. private var isShownOpenCameraInviteAlert = false
  43. private var isShownOpenMicrophoneInviteAlert = false
  44. private var isShownTakeSeatInviteAlert = false
  45. private var selfRole: TUIRole?
  46. var joinConferenceParams: JoinConferenceParams?
  47. var startConferenceParams: StartConferenceParams?
  48. var isShownWaterMark: Bool = ConferenceSession.sharedInstance.implementation.isEnableWaterMark
  49. lazy var isSelfDisableMessage: Bool = {
  50. return conferenceStore.selectCurrent(UserSelectors.getDisableMessageUsers).contains(currentUser.userId)
  51. }()
  52. override init() {
  53. super.init()
  54. selfRole = currentUser.userRole
  55. subscribeEngine()
  56. subLogoutNotification()
  57. }
  58. private func subscribeEngine() {
  59. EngineEventCenter.shared.subscribeEngine(event: .onRoomDismissed, observer: self)
  60. EngineEventCenter.shared.subscribeEngine(event: .onKickedOutOfRoom, observer: self)
  61. EngineEventCenter.shared.subscribeEngine(event: .onRequestReceived, observer: self)
  62. EngineEventCenter.shared.subscribeEngine(event: .onAllUserMicrophoneDisableChanged, observer: self)
  63. EngineEventCenter.shared.subscribeEngine(event: .onAllUserCameraDisableChanged, observer: self)
  64. EngineEventCenter.shared.subscribeEngine(event: .onKickedOffSeat, observer: self)
  65. EngineEventCenter.shared.subscribeEngine(event: .onKickedOffLine, observer: self)
  66. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
  67. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_CurrentUserMuteMessage, responder: self)
  68. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_SetToolBarDelayHidden, responder: self)
  69. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_ChangeToolBarHiddenState, responder: self)
  70. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_ShowExitRoomView, responder: self)
  71. EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, responder: self)
  72. EngineEventCenter.shared.subscribeEngine(event: .onStartedRoom, observer: self)
  73. EngineEventCenter.shared.subscribeEngine(event: .onJoinedRoom, observer: self)
  74. EngineEventCenter.shared.subscribeEngine(event: .onGetUserListFinished, observer: self)
  75. EngineEventCenter.shared.subscribeEngine(event: .onUserInfoChanged, observer: self)
  76. EngineEventCenter.shared.subscribeEngine(event: .onUserVideoStateChanged, observer: self)
  77. }
  78. private func subLogoutNotification() {
  79. NotificationCenter.default.addObserver(self,
  80. selector: #selector(dismissConferenceViewForLogout),
  81. name: NSNotification.Name.TUILogoutSuccess, object: nil)
  82. }
  83. private func unsubLogoutNotification() {
  84. NotificationCenter.default.removeObserver(self, name: NSNotification.Name.TUILogoutSuccess, object: nil)
  85. }
  86. private func unsubscribeEngine() {
  87. EngineEventCenter.shared.unsubscribeEngine(event: .onRoomDismissed, observer: self)
  88. EngineEventCenter.shared.unsubscribeEngine(event: .onKickedOutOfRoom, observer: self)
  89. EngineEventCenter.shared.unsubscribeEngine(event: .onRequestReceived, observer: self)
  90. EngineEventCenter.shared.unsubscribeEngine(event: .onAllUserMicrophoneDisableChanged, observer: self)
  91. EngineEventCenter.shared.unsubscribeEngine(event: .onAllUserCameraDisableChanged, observer: self)
  92. EngineEventCenter.shared.unsubscribeEngine(event: .onKickedOffSeat, observer: self)
  93. EngineEventCenter.shared.unsubscribeEngine(event: .onKickedOffLine, observer: self)
  94. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
  95. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_CurrentUserMuteMessage, responder: self)
  96. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_SetToolBarDelayHidden, responder: self)
  97. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_ChangeToolBarHiddenState, responder: self)
  98. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_ShowExitRoomView, responder: self)
  99. EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, responder: self)
  100. EngineEventCenter.shared.unsubscribeEngine(event: .onStartedRoom, observer: self)
  101. EngineEventCenter.shared.unsubscribeEngine(event: .onJoinedRoom, observer: self)
  102. EngineEventCenter.shared.unsubscribeEngine(event: .onGetUserListFinished, observer: self)
  103. EngineEventCenter.shared.unsubscribeEngine(event: .onUserInfoChanged, observer: self)
  104. EngineEventCenter.shared.unsubscribeEngine(event: .onUserVideoStateChanged, observer: self)
  105. }
  106. func onViewDidLoadAction() {
  107. if store.isEnteredRoom {
  108. let roomId = startConferenceParams?.roomId ?? joinConferenceParams?.roomId
  109. if let roomId = roomId, store.roomInfo.roomId != roomId {
  110. viewResponder?.showRepeatJoinRoomAlert()
  111. }
  112. return
  113. }
  114. if startConferenceParams != nil {
  115. quickStartConference()
  116. return
  117. }
  118. if joinConferenceParams != nil {
  119. joinConference()
  120. }
  121. }
  122. func quickStartConference() {
  123. guard let startParams = startConferenceParams, !startParams.roomId.isEmpty else {
  124. return
  125. }
  126. ConferenceOptions.quickStart(startConferenceParams: startParams) { [weak self] roomInfo in
  127. guard let self = self else { return }
  128. guard !self.viewStore.isInternalCreation else { return }
  129. self.notifySuccess(roomInfo: roomInfo, event: .onStartedRoom)
  130. } onError: { [weak self] code, message in
  131. guard let self = self else { return }
  132. self.handleOperateConferenceFailedResult(roomId: startParams.roomId, event: .onStartedRoom, error: code, message: message)
  133. }
  134. }
  135. func joinConference() {
  136. guard let joinParams = joinConferenceParams, !joinParams.roomId.isEmpty else {
  137. return
  138. }
  139. ConferenceOptions.join(joinConferenParams: joinParams) { [weak self] roomInfo in
  140. guard let self = self else { return }
  141. self.viewResponder?.hidePasswordView()
  142. self.notifySuccess(roomInfo: roomInfo, event: .onJoinedRoom)
  143. } onError: { [weak self] code, message in
  144. guard let self = self else { return }
  145. if code == .needPassword {
  146. self.viewResponder?.showPasswordView(roomId: joinParams.roomId)
  147. } else if code == .wrongPassword {
  148. self.viewResponder?.makeToast(text: .wrongPasswordText)
  149. } else {
  150. self.handleOperateConferenceFailedResult(roomId: joinParams.roomId, event: .onJoinedRoom, error: code, message: message)
  151. }
  152. }
  153. }
  154. func notifySuccess(roomInfo: TUIRoomInfo?,
  155. event: EngineEventCenter.RoomEngineEvent) {
  156. let param = [
  157. "roomInfo" : roomInfo ?? TUIRoomInfo(),
  158. "error" : TUIError.success,
  159. "mesasge" : ""
  160. ] as [String : Any]
  161. EngineEventCenter.shared.notifyEngineEvent(event: event, param: param)
  162. }
  163. func notifyError(roomId: String,
  164. event: EngineEventCenter.RoomEngineEvent,
  165. error: TUIError,
  166. message: String) {
  167. let roomInfo = TUIRoomInfo()
  168. roomInfo.roomId = roomId
  169. let param = [
  170. "roomInfo" : roomInfo,
  171. "error" : error,
  172. "mesasge" : message
  173. ] as [String : Any]
  174. EngineEventCenter.shared.notifyEngineEvent(event: event, param: param)
  175. }
  176. func setJoinConferenceParams(params: JoinConferenceParams) {
  177. joinConferenceParams = params
  178. store.setCameraOpened(params.isOpenCamera)
  179. store.setSoundOnSpeaker(params.isOpenSpeaker)
  180. }
  181. func setStartConferenceParams(params: StartConferenceParams) {
  182. startConferenceParams = params
  183. store.setCameraOpened(params.isOpenCamera)
  184. store.setSoundOnSpeaker(params.isOpenSpeaker)
  185. }
  186. @objc func dismissConferenceViewForLogout() {
  187. viewResponder?.showAlertWithAutoConfirm(title: .logoutText, message: nil, sureTitle: .alertOkText, declineTitle: nil, sureBlock: {
  188. EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, param: [:])
  189. }, declineBlock: nil, autoConfirmSeconds: 5)
  190. }
  191. func handleWrongPasswordFault(roomId: String) {
  192. handleOperateConferenceFailedResult(roomId: roomId, event: .onJoinedRoom, error: .failed, message: "")
  193. }
  194. private func handleOperateConferenceFailedResult(roomId: String, event: EngineEventCenter.RoomEngineEvent, error: TUIError, message: String) {
  195. let errorMessage = error.description ?? message
  196. if !viewStore.isInternalCreation {
  197. notifyError(roomId: roomId, event: event, error: error, message: message)
  198. } else {
  199. EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, param: [:])
  200. if error.isCommon, !errorMessage.isEmpty {
  201. conferenceStore.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: errorMessage)))
  202. }
  203. }
  204. debugPrint("TUIRoomKit error: \(event) error, \(errorMessage)")
  205. }
  206. func setVideoResolutionMode(isLandscape: Bool) {
  207. let resolutionMode: TUIResolutionMode = isLandscape ? .landscape : .portrait
  208. engineManager.setVideoResolutionMode(streamType: .cameraStream, resolutionMode: resolutionMode)
  209. engineManager.setVideoResolutionMode(streamType: .cameraStreamLow, resolutionMode: resolutionMode)
  210. }
  211. deinit {
  212. unsubscribeEngine()
  213. unsubLogoutNotification()
  214. debugPrint("deinit \(self)")
  215. }
  216. @Injected(\.conferenceStore) var conferenceStore: ConferenceStore
  217. @Injected(\.conferenceMainViewStore) var viewStore: ConferenceMainViewStore
  218. }
  219. extension ConferenceMainViewModel: RoomEngineEventResponder {
  220. func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String : Any]?) {
  221. switch name {
  222. case .onRoomDismissed:
  223. handleRoomDismissed()
  224. case .onKickedOutOfRoom:
  225. handleKickedOutOfRoom()
  226. case .onAllUserMicrophoneDisableChanged:
  227. guard let isDisable = param?["isDisable"] as? Bool else { return }
  228. handleAllUserMicrophoneDisableChanged(isDisable: isDisable)
  229. case .onAllUserCameraDisableChanged:
  230. guard let isDisable = param?["isDisable"] as? Bool else { return }
  231. handleAllUserCameraDisableChanged(isDisable: isDisable)
  232. case .onKickedOffSeat:
  233. viewResponder?.makeToast(text: .kickedOffSeat)
  234. case .onRequestReceived:
  235. guard let request = param?["request"] as? TUIRequest else { return }
  236. handleReceivedRequest(request: request)
  237. case .onKickedOffLine:
  238. handleKickedOffLine()
  239. case .onStartedRoom:
  240. guard let roomInfo = param?["roomInfo"] as? TUIRoomInfo else { return }
  241. guard let error = param?["error"] as? TUIError else { return }
  242. guard error == .success else { return }
  243. handleStartRoom(roomInfo: roomInfo)
  244. var roomState = RoomInfo(with: roomInfo)
  245. roomState.isEnteredRoom = true
  246. conferenceStore.dispatch(action: RoomActions.updateRoomState(payload: roomState))
  247. viewResponder?.updateWaterMarkLayer(text: getWaterMarkText())
  248. case .onJoinedRoom:
  249. guard let roomInfo = param?["roomInfo"] as? TUIRoomInfo else { return }
  250. guard let error = param?["error"] as? TUIError else { return }
  251. guard error == .success else { return }
  252. handleJoinRoom(roomInfo: roomInfo)
  253. var roomState = RoomInfo(with: roomInfo)
  254. roomState.isEnteredRoom = true
  255. conferenceStore.dispatch(action: RoomActions.updateRoomState(payload: roomState))
  256. viewResponder?.updateWaterMarkLayer(text: getWaterMarkText())
  257. case .onGetUserListFinished:
  258. let allUsers = self.store.attendeeList.map{ UserInfo(userEntity: $0) }
  259. conferenceStore.dispatch(action: UserActions.updateAllUsers(payload: allUsers))
  260. conferenceStore.dispatch(action: ConferenceInvitationActions.getInvitationList(payload: (store.roomInfo.roomId, "", [])))
  261. case .onUserInfoChanged:
  262. guard let userInfo = param?["userInfo"] as? TUIUserInfo else { return }
  263. guard let modifyFlag = param?["modifyFlag"] as? TUIUserInfoModifyFlag else { return }
  264. guard modifyFlag.contains(.nameCard) else { return }
  265. guard userInfo.userId == currentUser.userId else { return }
  266. viewResponder?.updateWaterMarkLayer(text: getWaterMarkText())
  267. case .onUserVideoStateChanged:
  268. guard let userId = param?["userId"] as? String else { return }
  269. guard let streamType = param?["streamType"] as? TUIVideoStreamType else { return }
  270. guard let hasVideo = param?["hasVideo"] as? Bool else { return }
  271. guard let reason = param?["reason"] as? TUIChangeReason else { return }
  272. handleUserVideoStateChanged(userId: userId, streamType: streamType, hasVideo: hasVideo, reason: reason)
  273. default: break
  274. }
  275. }
  276. private func handleRoomDismissed() {
  277. #if RTCube_APPSTORE
  278. if currentUser.userRole == .roomOwner {
  279. let selector = NSSelectorFromString("showAlertUserLiveTimeOut")
  280. if UIViewController.responds(to: selector) {
  281. UIViewController.perform(selector)
  282. }
  283. EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, param: [:])
  284. engineManager.destroyEngineManager()
  285. return
  286. }
  287. #endif
  288. engineManager.destroyEngineManager()
  289. viewResponder?.showAlertWithAutoConfirm(title: .destroyAlertText, message: nil, sureTitle: .alertOkText, declineTitle: nil, sureBlock: {
  290. EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, param: [:])
  291. }, declineBlock: nil, autoConfirmSeconds: 5)
  292. }
  293. private func handleKickedOutOfRoom() {
  294. engineManager.destroyEngineManager()
  295. viewResponder?.showAlertWithAutoConfirm(title: .kickOffTitleText, message: nil, sureTitle: .alertOkText, declineTitle: nil , sureBlock: {
  296. EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, param: [:])
  297. }, declineBlock: nil, autoConfirmSeconds: 5)
  298. conferenceStore.dispatch(action: RoomResponseActions.onExitSuccess())
  299. }
  300. private func handleAllUserMicrophoneDisableChanged(isDisable: Bool) {
  301. if isDisable {
  302. RoomRouter.makeToastInCenter(toast: .allMuteAudioText, duration: 1.5)
  303. } else {
  304. RoomRouter.makeToastInCenter(toast: .allUnMuteAudioText, duration: 1.5)
  305. }
  306. }
  307. private func handleAllUserCameraDisableChanged(isDisable: Bool) {
  308. if isDisable {
  309. RoomRouter.makeToastInCenter(toast: .allMuteVideoText, duration: 1.5)
  310. } else {
  311. RoomRouter.makeToastInCenter(toast: .allUnMuteVideoText, duration: 1.5)
  312. }
  313. }
  314. private func handleReceivedRequest(request: TUIRequest) {
  315. switch request.requestAction {
  316. case .openRemoteCamera:
  317. handleOpenCameraRequest(request: request)
  318. case .openRemoteMicrophone:
  319. handleOpenMicrophoneRequest(request: request)
  320. case .invalidAction:
  321. break
  322. case .remoteUserOnSeat:
  323. handleOnSeatRequest(request: request)
  324. default: break
  325. }
  326. }
  327. private func handleOpenCameraRequest(request: TUIRequest) {
  328. guard !isShownOpenCameraInviteAlert else { return }
  329. guard let userInfo = store.attendeeList.first(where: { $0.userId == request.userId }) else { return }
  330. let nameText: String = userInfo.userRole == .roomOwner ? .hostText : .administratorText
  331. let title = localizedReplace(.inviteTurnOnVideoText, replace: nameText)
  332. viewResponder?.showAlert(title: title, message: nil, sureTitle: .agreeText, declineTitle: .declineText, sureBlock: { [weak self] in
  333. guard let self = self else { return }
  334. self.isShownOpenCameraInviteAlert = false
  335. self.agreeOpenLocalCamera(request: request)
  336. }, declineBlock: { [weak self] in
  337. guard let self = self else { return }
  338. self.isShownOpenCameraInviteAlert = false
  339. self.engineManager.responseRemoteRequest(request.requestId, agree: false)
  340. })
  341. isShownOpenCameraInviteAlert = true
  342. }
  343. private func agreeOpenLocalCamera(request: TUIRequest) {
  344. engineManager.setLocalVideoView(nil)
  345. if RoomCommon.checkAuthorCamaraStatusIsDenied() {
  346. engineManager.responseRemoteRequest(request.requestId, agree: true)
  347. } else {
  348. RoomCommon.cameraStateActionWithPopCompletion { [weak self] granted in
  349. guard let self = self else { return }
  350. self.engineManager.responseRemoteRequest(request.requestId, agree: granted)
  351. }
  352. }
  353. }
  354. private func handleOpenMicrophoneRequest(request: TUIRequest) {
  355. guard !isShownOpenMicrophoneInviteAlert else { return }
  356. guard let userInfo = store.attendeeList.first(where: { $0.userId == request.userId }) else { return }
  357. let nameText: String = userInfo.userRole == .roomOwner ? .hostText : .administratorText
  358. let title = localizedReplace(.inviteTurnOnAudioText, replace: nameText)
  359. viewResponder?.showAlert(title: title, message: nil, sureTitle: .agreeText, declineTitle: .declineText, sureBlock: { [weak self] in
  360. guard let self = self else { return }
  361. self.isShownOpenMicrophoneInviteAlert = false
  362. self.agreeOpenLocalMic(request: request)
  363. }, declineBlock: { [weak self] in
  364. guard let self = self else { return }
  365. self.isShownOpenMicrophoneInviteAlert = false
  366. self.engineManager.responseRemoteRequest(request.requestId, agree: false)
  367. })
  368. isShownOpenMicrophoneInviteAlert = true
  369. }
  370. private func agreeOpenLocalMic(request: TUIRequest) {
  371. if RoomCommon.checkAuthorMicStatusIsDenied() {
  372. self.engineManager.responseRemoteRequest(request.requestId, agree: true)
  373. } else {
  374. RoomCommon.micStateActionWithPopCompletion { [weak self] granted in
  375. guard let self = self else { return }
  376. self.engineManager.responseRemoteRequest(request.requestId, agree: granted)
  377. }
  378. }
  379. }
  380. private func handleOnSeatRequest(request: TUIRequest) {
  381. guard roomInfo.isSeatEnabled && !isShownTakeSeatInviteAlert else { return }
  382. guard let userInfo = store.attendeeList.first(where: { $0.userId == request.userId }) else { return }
  383. let nameText: String = userInfo.userRole == .roomOwner ? .hostText : .administratorText
  384. let title = localizedReplace(.inviteSpeakOnStageTitle, replace: nameText)
  385. viewResponder?.showAlert(title: title, message: .inviteSpeakOnStageMessage, sureTitle: .agreeSeatText, declineTitle: .declineText, sureBlock: { [weak self] in
  386. guard let self = self else { return }
  387. self.isShownTakeSeatInviteAlert = false
  388. self.agreeOnSeatRequest(requestId: request.requestId)
  389. }, declineBlock: { [weak self] in
  390. guard let self = self else { return }
  391. self.isShownTakeSeatInviteAlert = false
  392. self.disagreeOnSeatRequest(requestId: request.requestId)
  393. })
  394. isShownTakeSeatInviteAlert = true
  395. }
  396. private func agreeOnSeatRequest(requestId: String) {
  397. engineManager.responseRemoteRequest(requestId, agree: true) {
  398. } onError: { [weak self] code, message in
  399. guard let self = self else { return }
  400. switch code {
  401. case .failed:
  402. self.viewResponder?.makeToast(text: .goOnStageTimedOutText)
  403. case .allSeatOccupied:
  404. self.viewResponder?.makeToast(text: .onStageNumberReachedLimitText)
  405. default: break
  406. }
  407. }
  408. }
  409. private func disagreeOnSeatRequest(requestId: String) {
  410. engineManager.responseRemoteRequest(requestId, agree: false) {
  411. } onError: { code, message in
  412. debugPrint("responseRemoteRequest:code:\(code),message:\(message)")
  413. }
  414. }
  415. private func handleKickedOffLine() {
  416. viewResponder?.showAlertWithAutoConfirm(title: .kieckedOffLineText, message: nil, sureTitle: .alertOkText, declineTitle: nil, sureBlock: {
  417. EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, param: [:])
  418. }, declineBlock: nil, autoConfirmSeconds: 5)
  419. conferenceStore.dispatch(action: RoomResponseActions.onExitSuccess())
  420. }
  421. private func handleStartRoom(roomInfo: TUIRoomInfo) {
  422. viewResponder?.updateRoomInfo(roomInfo: roomInfo)
  423. }
  424. private func handleJoinRoom(roomInfo: TUIRoomInfo) {
  425. if roomInfo.isSeatEnabled, store.isShownRaiseHandNotice {
  426. viewResponder?.showRaiseHandNoticeView()
  427. }
  428. viewResponder?.updateRoomInfo(roomInfo: roomInfo)
  429. }
  430. private func handleUserVideoStateChanged(userId: String, streamType: TUIVideoStreamType, hasVideo: Bool, reason: TUIChangeReason) {
  431. guard streamType == .screenStream, !hasVideo, reason == .byAdmin else { return }
  432. RoomRouter.presentAlert(title: .screenSharingHasStoppedAlertTitle, message: .screenSharingHasStoppedAlertMessage, sureTitle: .iSee, declineTitle: nil, sureBlock: nil, declineBlock: nil)
  433. }
  434. }
  435. extension ConferenceMainViewModel: ConferenceMainViewFactory {
  436. func makeConferencePasswordView() -> ConferencePasswordView {
  437. let passwordView = ConferencePasswordView()
  438. passwordView.isHidden = true
  439. passwordView.viewModel = self
  440. return passwordView
  441. }
  442. func makeTopView() -> TopView {
  443. let viewModel = TopViewModel()
  444. let topView = TopView(viewModel: viewModel)
  445. topView.backgroundColor = UIColor(0x0F1014)
  446. return topView
  447. }
  448. func makeBottomView() -> BottomView {
  449. let viewModel = BottomViewModel()
  450. let bottomView = BottomView(viewModel: viewModel)
  451. return bottomView
  452. }
  453. func makeVideoSeatView() -> UIView {
  454. let videoSeatView = ConferenceStreamContainer()
  455. videoSeatView.backgroundColor = UIColor(0x0F1014)
  456. return videoSeatView
  457. }
  458. func makeRaiseHandNoticeView() -> UIView {
  459. let raiseHandNoticeView = RaiseHandNoticeView()
  460. raiseHandNoticeView.isHidden = true
  461. return raiseHandNoticeView
  462. }
  463. func makeLocalAudioView() -> LocalAudioView {
  464. return LocalAudioView()
  465. }
  466. func makeWaterMarkLayer() -> WaterMarkLayer {
  467. let layer = WaterMarkLayer()
  468. layer.backgroundColor = UIColor.clear.cgColor
  469. layer.anchorPoint = CGPointZero
  470. layer.text = getWaterMarkText()
  471. layer.lineStyle = .multiLine
  472. layer.cornerRadius = 16
  473. return layer
  474. }
  475. func makeFloatChatButton() -> FloatChatButton {
  476. let floatchatButton = FloatChatButton()
  477. floatchatButton.isHidden = !store.shouldShowFloatChatView
  478. if store.isEnteredRoom {
  479. floatchatButton.updateRoomId(roomId: store.roomInfo.roomId)
  480. }
  481. return floatchatButton
  482. }
  483. func makeFloatChatDisplayView() -> FloatChatDisplayView {
  484. let view = FloatChatDisplayView()
  485. view.isHidden = !store.shouldShowFloatChatView
  486. view.delegate = self
  487. return view
  488. }
  489. func makeRaiseHandApplicationNotificationView() -> RaiseHandApplicationNotificationView {
  490. let viewModel = RaiseHandApplicationNotificationViewModel()
  491. let notificationView = RaiseHandApplicationNotificationView(viewModel: viewModel)
  492. return notificationView
  493. }
  494. private func getWaterMarkText() -> String {
  495. let customizeText = ConferenceSession.sharedInstance.implementation.waterMarkText
  496. if !customizeText.isEmpty {
  497. return customizeText
  498. }
  499. if !store.isEnteredRoom {
  500. return ""
  501. }
  502. var defaultText = currentUser.userId
  503. let userName = engineManager.getSelfInfo().userName
  504. if !userName.isEmpty {
  505. defaultText = defaultText + "(\(userName))"
  506. }
  507. return defaultText
  508. }
  509. }
  510. extension ConferenceMainViewModel: RoomKitUIEventResponder {
  511. func onNotifyUIEvent(key: EngineEventCenter.RoomUIEvent, Object: Any?, info: [AnyHashable : Any]?) {
  512. switch key{
  513. case .TUIRoomKitService_CurrentUserRoleChanged:
  514. guard let userRole = info?["userRole"] as? TUIRole else { return }
  515. handleSelfRoleChanged(userRole: userRole)
  516. case .TUIRoomKitService_CurrentUserMuteMessage:
  517. guard let isMute = info?["isMute"] as? Bool else { return }
  518. viewResponder?.makeToast(text: isMute ? .messageTurnedOffText : .messageTurnedOnText)
  519. case .TUIRoomKitService_ChangeToolBarHiddenState:
  520. viewResponder?.changeToolBarHiddenState()
  521. case .TUIRoomKitService_SetToolBarDelayHidden:
  522. guard let isDelay = info?["isDelay"] as? Bool else { return }
  523. viewResponder?.setToolBarDelayHidden(isDelay: isDelay)
  524. case .TUIRoomKitService_ShowExitRoomView:
  525. viewResponder?.showExitRoomView()
  526. case .TUIRoomKitService_DismissConferenceViewController:
  527. conferenceStore.dispatch(action: ConferenceInvitationActions.clearInvitationList())
  528. conferenceStore.dispatch(action: RoomActions.clearRoomState())
  529. default: break
  530. }
  531. }
  532. private func handleSelfRoleChanged(userRole: TUIRole) {
  533. switch userRole {
  534. case .roomOwner:
  535. viewResponder?.makeToast(text: .haveBecomeMasterText)
  536. case .administrator:
  537. viewResponder?.makeToast(text: .haveBecomeAdministratorText)
  538. case .generalUser:
  539. if selfRole == .administrator {
  540. viewResponder?.makeToast(text: .revokedAdministratorText)
  541. }
  542. default: break
  543. }
  544. selfRole = userRole
  545. }
  546. }
  547. extension ConferenceMainViewModel: FloatChatDisplayViewDelegate {
  548. func getTheLatestUserName(userId: String) -> String {
  549. return store.attendeeList.first(where: { $0.userId == userId })?.userName ?? ""
  550. }
  551. }
  552. private extension String {
  553. static var kickOffTitleText: String {
  554. localized("You were removed by the host.")
  555. }
  556. static var destroyAlertText: String {
  557. localized("The conference was closed.")
  558. }
  559. static var inviteTurnOnAudioText: String {
  560. localized("xx invites you to turn on the microphone")
  561. }
  562. static var inviteTurnOnVideoText: String {
  563. localized("xx invites you to turn on the camera")
  564. }
  565. static var inviteSpeakOnStageTitle: String {
  566. localized("xx invites you to speak on stage")
  567. }
  568. static var inviteSpeakOnStageMessage: String {
  569. localized("You can turn on the camera and unmute it once you are on stage")
  570. }
  571. static var messageTurnedOffText: String {
  572. localized("You were muted message by the host.")
  573. }
  574. static var messageTurnedOnText: String {
  575. localized("You were unmuted message by the host.")
  576. }
  577. static var haveBecomeMasterText: String {
  578. localized("You are now a host")
  579. }
  580. static var haveBecomeAdministratorText: String {
  581. localized("You have become a conference admin")
  582. }
  583. static var kickedOffLineText: String {
  584. localized("You are already logged in elsewhere")
  585. }
  586. static var alertOkText: String {
  587. localized("OK")
  588. }
  589. static var declineText: String {
  590. localized("Decline")
  591. }
  592. static var agreeText: String {
  593. localized("Agree")
  594. }
  595. static var agreeSeatText: String {
  596. localized("Approve")
  597. }
  598. static var allMuteAudioText: String {
  599. localized("All audios disabled")
  600. }
  601. static var allMuteVideoText: String {
  602. localized("All videos disabled")
  603. }
  604. static var allUnMuteAudioText: String {
  605. localized("All audios enabled")
  606. }
  607. static var allUnMuteVideoText: String {
  608. localized("All videos enabled")
  609. }
  610. static var kickedOffSeat: String {
  611. localized("You have been invited by the host to step down, please raise your hand if you need to speak")
  612. }
  613. static var hostText: String {
  614. localized("Host")
  615. }
  616. static var administratorText: String {
  617. localized("Administrator")
  618. }
  619. static var revokedAdministratorText: String {
  620. localized("Your conference admin status has been revoked")
  621. }
  622. static var onStageNumberReachedLimitText: String {
  623. localized("The stage is full, please contact the host")
  624. }
  625. static var goOnStageTimedOutText: String {
  626. localized("Failed to go on stage, invitation has timed out")
  627. }
  628. static var kieckedOffLineText: String {
  629. localized("You are already logged in elsewhere")
  630. }
  631. static var logoutText: String {
  632. localized("You are logged out")
  633. }
  634. static let wrongPasswordText = localized("Wrong password")
  635. static let iSee = localized("I see")
  636. static let screenSharingHasStoppedAlertTitle = localized("Your screen sharing has stopped")
  637. static let screenSharingHasStoppedAlertMessage = localized("Your screen sharing has stopped, Now only the host/admin can share the screen")
  638. }