| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- //
- // UserListViewModel.swift
- // TUIRoomKit
- //
- // Created by janejntang on 2023/1/4.
- // Copyright © 2023 Tencent. All rights reserved.
- //
- import Foundation
- import RTCRoomEngine
- import Factory
- enum UserListType {
- case allUsers
- case onStageUsers
- case offStageUsers
- case notInRoomUsers
- }
- protocol UserListViewResponder: NSObject {
- func updateBottomControlView(isHidden: Bool)
- func reloadUserListView()
- func makeToast(text: String)
- func updateUserManagerViewDisplayStatus(isHidden: Bool)
- func updateMuteAllAudioButtonState(isSelect: Bool)
- func updateMuteAllVideoButtonState(isSelect: Bool)
- func showAlert(title: String?, message: String?, sureTitle:String?, declineTitle: String?, sureBlock: (() -> ())?, declineBlock: (() -> ())?)
- func updateListStateView()
- func updateMemberLabel(count: Int)
- func updateUserListTableView()
- }
- class UserListViewModel: NSObject {
- var userId: String = ""
- var userName: String = ""
- var attendeeList: [UserEntity] = []
- var invitationList: [TUIInvitation] = []
- var engineManager: EngineManager {
- EngineManager.shared
- }
- var store: RoomStore {
- engineManager.store
- }
- var currentUser: UserEntity {
- store.currentUser
- }
- var roomInfo: TUIRoomInfo {
- store.roomInfo
- }
- let timeoutNumber: Double = 60
- weak var viewResponder: UserListViewResponder?
- lazy var userListType: UserListType = isSeatEnabled ? .onStageUsers: .allUsers
- var onStageCount: Int {
- store.seatList.count
- }
- var offStageCount: Int {
- store.offSeatList.count
- }
- var allUserCount: Int {
- store.attendeeList.count
- }
- var isShownBottomControlView: Bool {
- return store.currentUser.userRole != .generalUser
- }
- lazy var isSeatEnabled: Bool = {
- return roomInfo.isSeatEnabled
- }()
- lazy var isShownNotificationView: Bool = {
- return false
- }()
- lazy var isSearching: Bool = {
- return false
- }()
- lazy var searchText: String = {
- return ""
- }()
- var invitationUserList: [TUIInvitation] {
- return conferenceStore.selectCurrent(ConferenceInvitationSelectors.getInvitationList)
- }
- lazy var userSorter: UserListSorter = {
- return UserListSorter(currentUserId: currentUser.userId)
- }()
-
- @Injected(\.conferenceStore) var conferenceStore
-
- override init() {
- super.init()
- updateAttendeeList()
- EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
- EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_RenewUserList, responder: self)
- EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_RenewSeatList, responder: self)
- EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_SomeoneSharing, responder: self)
- EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_RoomOwnerChanged, responder: self)
- EngineEventCenter.shared.subscribeEngine(event: .onAllUserMicrophoneDisableChanged, observer: self)
- EngineEventCenter.shared.subscribeEngine(event: .onAllUserCameraDisableChanged, observer: self)
- EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
- }
-
- deinit {
- EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
- EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_RenewUserList, responder: self)
- EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_RenewSeatList, responder: self)
- EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_SomeoneSharing, responder: self)
- EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_RoomOwnerChanged, responder: self)
- EngineEventCenter.shared.unsubscribeEngine(event: .onAllUserMicrophoneDisableChanged, observer: self)
- EngineEventCenter.shared.unsubscribeEngine(event: .onAllUserCameraDisableChanged, observer: self)
- EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, responder: self)
- debugPrint("deinit \(self)")
- }
-
- func updateAttendeeList() {
- var userList: [UserEntity] = []
- switch userListType {
- case .allUsers:
- userList = userSorter.sortUsers(store.attendeeList)
- case .onStageUsers:
- userList = userSorter.sortUsers(store.seatList)
- case .offStageUsers:
- userList = userSorter.sortUsers(store.offSeatList)
- case .notInRoomUsers:
- userList = invitationList.map{ UserEntity(invitation: $0) }
- }
- if isSearching, searchText.count > 0 {
- let searchArray = userList.filter({ model -> Bool in
- return (model.userName.contains(searchText))
- })
- attendeeList = searchArray
- } else {
- attendeeList = userList
- }
- }
-
- func muteAllAudioAction(sender: UIButton, view: UserListView) {
- let isSelected = sender.isSelected
- viewResponder?.showAlert(title: sender.isSelected ? .allUnmuteTitle : .allMuteTitle,
- message: sender.isSelected ? .allUnmuteMessage : .allMuteMessage,
- sureTitle: sender.isSelected ? .confirmReleaseText : .allMuteActionText,
- declineTitle: .cancelText, sureBlock: { [weak self] in
- guard let self = self else { return }
- if self.roomInfo.isMicrophoneDisableForAllUser != !isSelected {
- self.engineManager.muteAllAudioAction(isMute: !isSelected) {
- } onError: { [weak self] _, message in
- guard let self = self else { return }
- self.viewResponder?.makeToast(text:message)
- }
- } else {
- let text: String = isSelected ? .allUnMuteAudioText : .allMuteAudioText
- self.viewResponder?.makeToast(text: text)
- }
- }, declineBlock: nil)
- }
-
- func muteAllVideoAction(sender: UIButton, view: UserListView) {
- let isSelected = sender.isSelected
- viewResponder?.showAlert(title: sender.isSelected ? .allUnmuteVideoTitle : .allMuteVideoTitle,
- message: sender.isSelected ? .allUnmuteVideoMessage : .allMuteVideoMessage,
- sureTitle: sender.isSelected ? .confirmReleaseText : .allMuteVideoActionText,
- declineTitle: .cancelText, sureBlock: { [weak self] in
- guard let self = self else { return }
- if self.roomInfo.isCameraDisableForAllUser != !isSelected {
- self.engineManager.muteAllVideoAction(isMute: !isSelected) {
- } onError: { [weak self] _, message in
- guard let self = self else { return }
- self.viewResponder?.makeToast(text:message)
- }
- } else {
- let text: String = isSelected ? .allUnMuteVideoText : .allMuteVideoText
- self.viewResponder?.makeToast(text: text)
- }
- }, declineBlock: nil)
- }
-
- func showUserManageViewAction(userId: String, userName: String) {
- self.userId = userId
- self.userName = userName
- guard checkShowManagerView() else { return }
- viewResponder?.updateUserManagerViewDisplayStatus(isHidden: false)
- }
-
- private func checkShowManagerView() -> Bool {
- guard let userInfo = engineManager.store.attendeeList.first(where: { $0.userId == userId }) else { return false }
- switch currentUser.userRole {
- case .roomOwner:
- return true
- case .administrator:
- return userInfo.userRole == .generalUser || currentUser.userId == userId
- case .generalUser:
- return currentUser.userId == userId
- @unknown default:
- return false
- }
- }
-
- func inviteSeatAction(sender: UIButton) {
- guard let userInfo = attendeeList.first(where: { $0.userId == userId }) else { return }
- guard store.seatList.count < roomInfo.maxSeatCount else {
- RoomRouter.makeToastInCenter(toast: .theStageIsFullText, duration: 0.5)
- return
- }
- engineManager.takeUserOnSeatByAdmin(userId: userId, timeout: timeoutNumber) { _,_ in
- let text: String = localizedReplace(.onStageText, replace: userInfo.userName)
- RoomRouter.makeToastInCenter(toast: text, duration: 0.5)
- } onRejected: { requestId, userId, message in
- let text: String = localizedReplace(.refusedTakeSeatInvitationText, replace: userInfo.userName)
- RoomRouter.makeToastInCenter(toast: text, duration: 0.5)
- } onCancelled: { _, _ in
- } onTimeout: { _, _ in
- let text: String = localizedReplace(.takeSeatInvitationTimeoutText, replace: userInfo.userName)
- RoomRouter.makeToastInCenter(toast: text, duration: 0.5)
- } onError: { _, _, code, message in
- let text: String = code == .requestIdRepeat ? .receivedSameRequestText : message
- RoomRouter.makeToastInCenter(toast: text, duration: 0.5)
- }
- viewResponder?.makeToast(text: .invitedTakeSeatText)
- }
-
- func checkSelfInviteAbility(invitee: UserEntity) -> Bool {
- if currentUser.userRole == .roomOwner {
- return true
- } else if currentUser.userRole == .administrator, invitee.userRole == .generalUser {
- return true
- } else {
- return false
- }
- }
-
- func changeListState(type: UserListType) {
- userListType = type
- updateAttendeeList()
- viewResponder?.reloadUserListView()
- }
-
- func compareLists(oldList: [TUIInvitation], newList: [TUIInvitation]) -> (added: [TUIInvitation], removed: [TUIInvitation], changed: [TUIInvitation]) {
- var added = [TUIInvitation]()
- var removed = [TUIInvitation]()
- var changed = [TUIInvitation]()
- let oldDict = Dictionary(uniqueKeysWithValues: oldList.map { ($0.invitee.userId, $0) })
- let newDict = Dictionary(uniqueKeysWithValues: newList.map { ($0.invitee.userId, $0) })
-
- for newInvitation in newList {
- if oldDict[newInvitation.invitee.userId] == nil {
- added.append(newInvitation)
- }
- }
- for oldInvitation in oldList {
- if newDict[oldInvitation.invitee.userId] == nil {
- removed.append(oldInvitation)
- }
- }
- for newInvitation in newList {
- if let oldInvitation = oldDict[newInvitation.invitee.userId], oldInvitation != newInvitation {
- changed.append(newInvitation)
- }
- }
- return (added, removed, changed)
- }
-
- func reportUserListPanelShow() {
- RoomKitReport.reportData(.metricsUserListPanelShow)
- }
-
- func reportUserListSearch() {
- RoomKitReport.reportData(.metricsUserListSearch)
- }
- }
- extension UserListViewModel: RoomKitUIEventResponder {
- func onNotifyUIEvent(key: EngineEventCenter.RoomUIEvent, Object: Any?, info: [AnyHashable : Any]?) {
- switch key {
- case .TUIRoomKitService_RenewUserList, .TUIRoomKitService_RenewSeatList, .TUIRoomKitService_SomeoneSharing:
- viewResponder?.updateMemberLabel(count: allUserCount)
- updateAttendeeList()
- viewResponder?.reloadUserListView()
- viewResponder?.updateListStateView()
- case .TUIRoomKitService_CurrentUserRoleChanged:
- guard let userRole = info?["userRole"] as? TUIRole else { return }
- viewResponder?.updateBottomControlView(isHidden: userRole == .generalUser)
- case .TUIRoomKitService_RoomOwnerChanged:
- viewResponder?.reloadUserListView()
- default: break
- }
- }
- }
- extension UserListViewModel: RoomEngineEventResponder {
- func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String : Any]?) {
- switch name {
- case .onAllUserCameraDisableChanged:
- guard let isDisable = param?["isDisable"] as? Bool else { return }
- viewResponder?.updateMuteAllVideoButtonState(isSelect: isDisable)
- case .onAllUserMicrophoneDisableChanged:
- guard let isDisable = param?["isDisable"] as? Bool else { return }
- viewResponder?.updateMuteAllAudioButtonState(isSelect: isDisable)
- default: break
- }
- }
- }
- extension UserListViewModel: RaiseHandApplicationNotificationViewListener {
- func onShown() {
- isShownNotificationView = true
- viewResponder?.updateUserListTableView()
- }
-
- func onHidden() {
- isShownNotificationView = false
- viewResponder?.updateUserListTableView()
- }
- }
- class UserListSorter {
- private let currentUserId: String
-
- init(currentUserId: String) {
- self.currentUserId = currentUserId
- }
-
- func sortUsers(_ users: [UserEntity]) -> [UserEntity] {
- guard needSorting(users) else { return users }
-
- return users.sorted { first, second in
- getUserPriority(for: first) < getUserPriority(for: second)
- }
- }
-
- private func needSorting(_ users: [UserEntity]) -> Bool {
- guard users.count > 1 else { return false }
-
- for i in 0..<(users.count - 1) {
- let currentPriority = getUserPriority(for: users[i])
- let nextPriority = getUserPriority(for: users[i + 1])
-
- if currentPriority > nextPriority {
- return true
- }
- }
-
- return false
- }
-
- private func getUserPriority(for user: UserEntity) -> UserPriority {
- if user.userId == currentUserId {
- return .currentUser
- }
- if user.userRole == .roomOwner {
- return .roomOwner
- }
- if user.userRole == .administrator {
- return .administrator
- }
- if user.hasScreenStream {
- return .screenSharing
- }
-
- switch (user.hasAudioStream, user.hasVideoStream) {
- case (true, true): return .videoAndAudio
- case (false, true): return .videoOnly
- case (true, false): return .audioOnly
- case (false, false): return .none
- }
- }
- }
- enum UserPriority: Comparable {
- case currentUser
- case roomOwner
- case administrator
- case screenSharing
- case videoAndAudio
- case videoOnly
- case audioOnly
- case none
-
- var priority: Int {
- switch self {
- case .currentUser: return 0
- case .roomOwner: return 1
- case .administrator: return 2
- case .screenSharing: return 3
- case .videoAndAudio: return 4
- case .videoOnly: return 5
- case .audioOnly: return 6
- case .none: return 7
- }
- }
-
- static func < (lhs: UserPriority, rhs: UserPriority) -> Bool {
- return lhs.priority < rhs.priority
- }
- }
- private extension String {
- static var invitedTakeSeatText: String {
- localized("The audience has been invited to the stage")
- }
- static var refusedTakeSeatInvitationText: String {
- localized("xx refused to go on stage")
- }
- static var takeSeatInvitationTimeoutText: String {
- localized("The invitation to xx to go on stage has timed out")
- }
- static var allMuteTitle: String {
- localized("All current and incoming members will be muted")
- }
- static var allMuteVideoTitle: String {
- localized("All current and incoming members will be restricted from video")
- }
- static var allMuteMessage: String {
- localized("Members will unable to turn on the microphone")
- }
- static var allMuteVideoMessage: String {
- localized("Members will unable to turn on video")
- }
- static var allUnmuteMessage: String {
- localized("Members will be able to turn on the microphone")
- }
- static var allUnmuteVideoMessage: String {
- localized("Members will be able to turn on video")
- }
- static var allUnmuteTitle: String {
- localized("All members will be unmuted")
- }
- static var allUnmuteVideoTitle: String {
- localized("All members will not be restricted from video")
- }
- static var cancelText: String {
- localized("Cancel")
- }
- static var allMuteActionText: String {
- localized("Mute All")
- }
- static var allMuteVideoActionText: String {
- localized("Stop all video")
- }
- static var confirmReleaseText: String {
- localized("Confirm release")
- }
- static var allMuteAudioText: String {
- localized("All audios disabled")
- }
- static var allUnMuteAudioText: String {
- localized("All audios enabled")
- }
- static var allMuteVideoText: String {
- localized("All videos disabled")
- }
- static var allUnMuteVideoText: String {
- localized("All videos enabled")
- }
- static var receivedSameRequestText: String {
- localized("This member has already received the same request, please try again later")
- }
- static var onStageText: String {
- localized("xx is on stage")
- }
- static var theStageIsFullText: String {
- localized("The stage is full")
- }
- }
|