增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

View File

@@ -0,0 +1,86 @@
//
// InviteToJoinRoomManager.swift
// TUIRoomKit
//
// Created by janejntang on 2023/7/3.
// You can choose to invite members and send invitations.
import Foundation
import RTCRoomEngine
import TUICore
class InviteToJoinRoomManager {
class func startInviteToJoinRoom(message: RoomMessageModel, inviter: TUILoginUserInfo) {
let inviteJoinModel = InviteJoinModel(message: message, inviter: inviter)
pushSelectGroupMemberViewController(groupId: message.groupId) { responseData in
guard let modelList =
responseData[TUICore_TUIGroupObjectFactory_SelectGroupMemberVC_ResultUserList] as? [TUIUserModel]
else { return }
var invitedList: [String] = []
for userModel in modelList {
invitedList.append(userModel.userId)
}
inviteToJoinRoom(inviteJoinModel: inviteJoinModel, invitedList: invitedList)
}
}
class func pushSelectGroupMemberViewController(groupId: String, callback: @escaping TUIValueResultCallback) {
let param = [TUICore_TUIGroupObjectFactory_SelectGroupMemberVC_GroupID: groupId]
if let navigateController = RoomMessageManager.shared.navigateController {
navigateController.push(TUICore_TUIGroupObjectFactory_SelectGroupMemberVC_Classic, param: param) { responseData in
callback(responseData)
}
} else {
let nav = UINavigationController()
let currentViewController = getCurrentWindowViewController()
currentViewController?.present(TUICore_TUIGroupObjectFactory_SelectGroupMemberVC_Classic,
param: param, embbedIn: nav,
forResult: { responseData in
callback(responseData)
})
}
}
class func inviteToJoinRoom(inviteJoinModel: InviteJoinModel, invitedList: [String]) {
guard invitedList.count > 0 else { return }
let dataDict = inviteJoinModel.getDicFromInviteJoinModel(inviteJoinModel: inviteJoinModel)
guard let dataString = dataDict.convertToString() else { return }
let pushInfo = V2TIMOfflinePushInfo()
invitedList.forEach { userId in
V2TIMManager.sharedInstance().invite(userId,
data: dataString,
onlineUserOnly: true,
offlinePushInfo: pushInfo,
timeout: 60) {
debugPrint("invite,success")
} fail: { code, message in
debugPrint("invite,code:\(code),message:\(String(describing: message))")
}
}
}
class private func getCurrentWindowViewController() -> UIViewController? {
var keyWindow: UIWindow?
for window in UIApplication.shared.windows {
if window.isMember(of: UIWindow.self), window.isKeyWindow {
keyWindow = window
break
}
}
guard let rootController = keyWindow?.rootViewController else {
return nil
}
func findCurrentController(from vc: UIViewController?) -> UIViewController? {
if let nav = vc as? UINavigationController {
return findCurrentController(from: nav.topViewController)
} else if let tabBar = vc as? UITabBarController {
return findCurrentController(from: tabBar.selectedViewController)
} else if let presented = vc?.presentedViewController {
return findCurrentController(from: presented)
}
return vc
}
let viewController = findCurrentController(from: rootController)
return viewController
}
}

View File

@@ -0,0 +1,125 @@
//
// RoomManager.swift
// TUIRoomKit
//
// Created by janejntang on 2023/7/3.
// Manage rooms, including room creation, entry, exit, destruction and conversion operations of the host
import Foundation
import RTCRoomEngine
import TUICore
class RoomManager {
static let shared = RoomManager()
private var engineManager: EngineManager {
EngineManager.shared
}
private var roomInfo: TUIRoomInfo {
engineManager.store.roomInfo
}
private lazy var userId: String = {
return TUILogin.getUserID() ?? engineManager.store.currentUser.userId
}()
private let messageManager = RoomMessageManager.shared
let roomObserver: RoomObserver = RoomObserver()
var roomId: String?
deinit {
debugPrint("deinit \(self)")
}
func isEnteredOtherRoom(roomId: String) -> Bool {
return roomInfo.roomId != roomId && roomInfo.roomId != ""
}
func createRoom(roomInfo: TUIRoomInfo) {
roomId = roomInfo.roomId
roomObserver.registerObserver()
engineManager.createRoom(roomInfo: roomInfo) { [weak self] in
guard let self = self else { return }
self.roomObserver.createdRoom()
self.enterRoom(roomId: roomInfo.roomId, isShownConferenceViewController: false)
} onError: { _, message in
RoomRouter.makeToast(toast: message)
}
}
func enterRoom(roomId: String, isShownConferenceViewController: Bool = true) {
roomObserver.registerObserver()
engineManager.store.isImAccess = true
self.roomId = roomId
engineManager.enterRoom(roomId: roomId, enableAudio: engineManager.store.isOpenMicrophone, enableVideo: engineManager.store.isOpenCamera, isSoundOnSpeaker: true) { [weak self] roomInfo in
guard let self = self else { return }
self.roomObserver.enteredRoom()
guard isShownConferenceViewController else { return }
let vc = ConferenceMainViewController()
RoomRouter.shared.push(viewController: vc)
} onError: { _, message in
RoomRouter.makeToast(toast: message)
}
}
func exitRoom(onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
engineManager.exitRoom { [weak self] in
guard let self = self else { return }
self.refreshSource()
self.messageManager.isReadyToSendMessage = true
onSuccess()
} onError: { [weak self] code, message in
guard let self = self else { return }
self.refreshSource()
if code.rawValue == -2_102 {
self.destroyRoom(onSuccess: onSuccess, onError: onError)
} else {
onError(code, message)
}
}
}
func destroyRoom(onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
engineManager.destroyRoom { [weak self] in
guard let self = self else { return }
self.roomObserver.messageModel.roomState = .destroyed
self.refreshSource()
self.messageManager.isReadyToSendMessage = true
onSuccess()
} onError: { [weak self] code, message in
guard let self = self else { return }
self.refreshSource()
onError(code, message)
}
}
private func refreshSource() {
roomId = nil
TUILogin.setCurrentBusinessScene(.None)
roomObserver.userList = []
roomObserver.unregisterObserver()
}
private func changeUserRole(userId: String, role: TUIRole, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
engineManager.changeUserRole(userId: userId, role: .roomOwner) {
onSuccess()
} onError: { code, message in
onError(code, message)
}
}
func exitOrDestroyPreviousRoom(onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
if roomInfo.ownerId == userId {
if let userModel = engineManager.store.attendeeList.first(where: { $0.userId != userId }) {
changeUserRole(userId: userModel.userId, role: .roomOwner) { [weak self] in
guard let self = self else { return }
self.exitRoom(onSuccess: onSuccess, onError: onError)
} onError: { [weak self] code, message in
guard let self = self else { return }
self.destroyRoom(onSuccess: onSuccess, onError: onError)
}
} else {
destroyRoom(onSuccess: onSuccess, onError: onError)
}
} else {
exitRoom(onSuccess: onSuccess, onError: onError)
}
}
}

View File

@@ -0,0 +1,138 @@
//
// RoomMessageManager.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/8.
// Copyright © 2023 Tencent. All rights reserved.
// Manage messages, including sending messages and modifying messages
//
import Foundation
import TUICore
import RTCRoomEngine
class RoomMessageManager: NSObject {
static let shared = RoomMessageManager()
private var engineManager: EngineManager {
EngineManager.shared
}
private lazy var userId: String = {
return TUILogin.getUserID() ?? engineManager.store.currentUser.userId
}()
weak var navigateController: UINavigationController?
var isReadyToSendMessage: Bool = true
var groupId: String = ""
private override init() {
super.init()
TUICore.registerEvent(TUICore_TUIChatNotify, subKey: TUICore_TUIChatNotify_SendMessageSubKey, object: self)
}
func sendRoomMessageToGroup() {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard BusinessSceneUtil.canJoinRoom() else { return }
if self.engineManager.store.isEnteredRoom {
RoomManager.shared.exitOrDestroyPreviousRoom() { [weak self] in
guard let self = self else { return }
self.sendMessage()
} onError: { [weak self] code, message in
guard let self = self else { return }
self.sendMessage()
debugPrint("exitRoom,code:\(code),message:\(message)")
}
} else {
self.sendMessage()
}
}
}
private func sendMessage() {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard self.isReadyToSendMessage else {
self.changeReadyToSendMessage()
return
}
self.isReadyToSendMessage = false
FetchRoomId.getRoomId { [weak self] roomId in
guard let self = self else { return }
let messageModel = RoomMessageModel()
messageModel.groupId = self.groupId
messageModel.roomId = roomId
messageModel.ownerName = TUILogin.getNickName() ?? ""
messageModel.owner = self.userId
let messageDic = messageModel.getDictFromMessageModel()
guard let jsonString = messageDic.convertToString() else { return }
let jsonData = jsonString.data(using: String.Encoding.utf8)
let message = V2TIMManager.sharedInstance().createCustomMessage(jsonData)
message?.supportMessageExtension = true
let param = [TUICore_TUIChatService_SendMessageMethod_MsgKey: message]
TUICore.callService(TUICore_TUIChatService, method: TUICore_TUIChatService_SendMessageMethod, param: param as [AnyHashable : Any])
RoomManager.shared.roomId = roomId
}
}
}
func resendRoomMessage(message: RoomMessageModel,dic:[String: Any]) {
if message.messageId == "" {
self.modifyMessage(message: message, dic: dic)
return
}
V2TIMManager.sharedInstance().findMessages([message.messageId]) { [weak self] messageArray in
guard let self = self else { return }
guard let array = messageArray else { return }
for previousMessage in array where previousMessage.msgID == message.messageId {
self.modifyMessage(message: message, dic: dic)
}
} fail: { [weak self] code, messageString in
guard let self = self else { return }
self.modifyMessage(message: message, dic: dic)
}
}
private func modifyMessage(message: RoomMessageModel, dic:[String: Any]) {
guard let message = message.getMessage() else { return }
guard var customElemDic = TUITool.jsonData2Dictionary(message.customElem.data) as? [String: Any] else { return }
for (key, value) in dic {
customElemDic[key] = value
}
guard let jsonString = customElemDic.convertToString() else { return }
let jsonData = jsonString.data(using: String.Encoding.utf8)
message.customElem.data = jsonData
V2TIMManager.sharedInstance().modifyMessage(message) { code, desc, msg in
if code == 0 {
debugPrint("modifyMessage,success")
} else {
debugPrint("modifyMessage,code:\(code),message:\(String(describing: desc))")
}
}
}
deinit {
debugPrint("deinit \(self)")
}
}
extension RoomMessageManager {
private func changeReadyToSendMessage() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 20) { [weak self] in
guard let self = self else { return }
self.isReadyToSendMessage = true
}
}
}
extension RoomMessageManager: TUINotificationProtocol {
func onNotifyEvent(_ key: String, subKey: String, object anObject: Any?, param: [AnyHashable : Any]?) {
guard key == TUICore_TUIChatNotify, subKey == TUICore_TUIChatNotify_SendMessageSubKey else { return }
guard let code = param?[TUICore_TUIChatNotify_SendMessageSubKey_Code] as? Int, code == 0 else { return }
guard let message = param?[TUICore_TUIChatNotify_SendMessageSubKey_Message] as? V2TIMMessage else { return }
let messageModel = RoomMessageModel()
messageModel.updateMessage(message: message)
guard messageModel.messageId.count > 0, messageModel.roomState == .creating, messageModel.roomId == RoomManager.shared.roomId else { return }
let roomInfo = TUIRoomInfo()
roomInfo.roomId = messageModel.roomId
RoomManager.shared.createRoom(roomInfo: roomInfo)
}
}

View File

@@ -0,0 +1,56 @@
//
// InviteJoinModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/7/3.
// Signaling Model for inviting others into the room
import Foundation
import TUICore
import RTCRoomEngine
class InviteJoinModel {
private var message: RoomMessageModel
private var inviter: TUILoginUserInfo
var platform: String = "iOS"
var version: Int = 1
var businessID: String = "ROOM_INVITE_ACTION"
var roomId: String = ""
var extInfo: String = ""
var data: [String:Any] = [:]
init(message: RoomMessageModel, inviter: TUILoginUserInfo) {
self.message = message
self.inviter = inviter
self.roomId = message.roomId
self.data = getInviteJoinModelDataDic()
}
func getDicFromInviteJoinModel(inviteJoinModel: InviteJoinModel) -> [String: Any] {
guard let dict = ["platform": inviteJoinModel.platform,
"version": inviteJoinModel.version,
"businessID": inviteJoinModel.businessID,
"roomId": inviteJoinModel.roomId,
"extInfo": inviteJoinModel.extInfo,
"data": inviteJoinModel.data,
] as? [String: Any] else { return [:] }
return dict
}
private func getInviteJoinModelDataDic() -> [String: Any] {
let messageDic = message.getDictFromMessageModel()
let inviterDic = getInviterUserInfoDic(inviter: inviter)
return ["inviter":inviterDic, "roomInfo": messageDic]
}
private func getInviterUserInfoDic(inviter: TUILoginUserInfo) -> [String: Any] {
return ["avatarUrl": inviter.avatarUrl,
"userId": inviter.userId,
"userName": inviter.userName,
]
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,90 @@
//
// RoomMessageModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/10.
//
import Foundation
import TUICore
class RoomMessageModel {
enum RoomState: String {
case creating
case created
case destroying
case destroyed
}
private var message: V2TIMMessage?
var version: Int = 1
var businessID: String = BussinessID_GroupRoomMessage
var groupId: String = ""
var messageId: String = ""
var roomId: String = ""
var owner: String = ""
var ownerName: String = ""
var roomState: RoomState = .creating
var memberCount: Int = 0
var userList: [[String:Any]] = []
init() {}
func updateMessage(message: V2TIMMessage) {
self.message = message
self.messageId = message.msgID ?? ""
guard var dict = getMessageCustomElemDic(message: message) else { return }
self.version = dict["version"] as? Int ?? 1
self.businessID = dict["businessID"] as? String ?? BussinessID_GroupRoomMessage
self.groupId = dict["groupId"] as? String ?? ""
self.roomId = dict["roomId"] as? String ?? ""
self.owner = dict["owner"] as? String ?? ""
self.ownerName = dict["ownerName"] as? String ?? ""
if let roomState = dict["roomState"] as? String {
self.roomState = RoomState(rawValue: roomState) ?? .creating
} else {
self.roomState = .creating
}
self.userList = dict["userList"] as? [[String:Any]] ?? []
self.memberCount = dict["memberCount"] as? Int ?? userList.count
dict["messageId"] = self.messageId
setMessageCustomElemData(dict: dict, message: message)
}
private func getMessageCustomElemDic(message: V2TIMMessage) -> [String: Any]? {
guard let customElem = message.customElem else { return nil}
guard let customData = customElem.data else { return nil}
guard let dataString = String(data: customData, encoding: String.Encoding.utf8) else { return nil }
guard let data = dataString.data(using: String.Encoding.utf8) else { return nil }
guard let dict = try? JSONSerialization.jsonObject(with: data,
options: .mutableContainers) as? [String : Any] else { return nil }
return dict
}
private func setMessageCustomElemData(dict: [String: Any], message: V2TIMMessage) {
guard let jsonString = dict.convertToString() else { return }
let jsonData = jsonString.data(using: String.Encoding.utf8)
message.customElem.data = jsonData
}
func getDictFromMessageModel() -> [String: Any] {
let dict = ["version": version,
"businessID": businessID,
"groupId": groupId,
"roomId": roomId,
"owner": owner,
"ownerName": ownerName,
"roomState": roomState.rawValue,
"memberCount": memberCount,
"messageId": messageId,
"userList": userList,
] as [String : Any]
return dict
}
func getMessage() -> V2TIMMessage? {
return message
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,210 @@
//
// RoomObserver.swift
// TUIRoomKit
//
// Created by janejntang on 2023/7/3.
import Foundation
import RTCRoomEngine
import TUICore
@objc public protocol RoomObserverListener {
@objc optional func onRoomEnter(messageId: String, code: Int, message: String) -> Void
@objc optional func onRoomExit(messageId: String) -> Void
}
class RoomObserver: NSObject {
var messageModel = RoomMessageModel()
private let messageManager = RoomMessageManager.shared
var engineManager: EngineManager {
EngineManager.shared
}
var roomEngine: TUIRoomEngine {
engineManager.roomEngine
}
lazy var userList: [[String: Any]] = {
return messageModel.userList
}()
private lazy var userId: String = {
return TUILogin.getUserID() ?? EngineManager.shared.store.currentUser.userId
}()
typealias Weak<T> = () -> T?
private var listenerArray: [Weak<RoomObserverListener>] = []
override init() {
super.init()
EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_RoomOwnerChanged, responder: self)
EngineEventCenter.shared.subscribeEngine(event: .onExitedRoom, observer: self)
EngineEventCenter.shared.subscribeEngine(event: .onDestroyedRoom, observer: self)
}
func registerObserver() {
roomEngine.addObserver(self)
}
func unregisterObserver() {
roomEngine.removeObserver(self)
}
func addListener(listener: RoomObserverListener) {
let weakObserver = { [weak listener] in return listener }
self.listenerArray.append(weakObserver)
}
func removeListener(listener: RoomObserverListener) {
listenerArray.removeAll(where: {$0() === listener})
}
private func refreshSource() {
RoomManager.shared.roomId = nil
TUILogin.setCurrentBusinessScene(.None)
engineManager.roomEngine.removeObserver(self)
userList = []
unregisterObserver()
}
func createdRoom() {
TUILogin.setCurrentBusinessScene(.InMeetingRoom)
messageModel.roomState = .created
let userInfo = TUIUserInfo()
userInfo.userId = userId
userInfo.avatarUrl = TUILogin.getFaceUrl() ?? ""
userInfo.userName = TUILogin.getNickName() ?? ""
addUserList(userInfo: userInfo)
let prefixUserList = Array(userList.prefix(5))
messageManager.resendRoomMessage(message: messageModel, dic: ["userList":prefixUserList,
"memberCount":userList.count,
"roomState":RoomMessageModel.RoomState.created.rawValue,])
}
func enteredRoom() {
TUILogin.setCurrentBusinessScene(.InMeetingRoom)
getUserList(nextSequence: 0)
}
func exitedRoom() {
RoomVideoFloatView.dismiss()
userList = userList.filter { [weak self] userDic in
guard let self = self, let userId = userDic["userId"] as? String else { return false }
return userId != self.userId
}
if messageModel.owner == userId {
let prefixUserList = Array(userList.prefix(5))
messageManager.resendRoomMessage(message: messageModel, dic: ["userList":prefixUserList, "memberCount":userList.count])
}
for weakObserver in listenerArray {
if let listener = weakObserver() {
listener.onRoomExit?(messageId: self.messageModel.messageId)
}
}
messageManager.isReadyToSendMessage = true
refreshSource()
}
func destroyedRoom() {
RoomVideoFloatView.dismiss()
messageModel.roomState = .destroyed
if messageModel.owner == userId {
messageManager.resendRoomMessage(message: messageModel, dic: ["roomState":RoomMessageModel.RoomState.destroyed.rawValue])
}
messageManager.isReadyToSendMessage = true
refreshSource()
}
deinit {
EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_RoomOwnerChanged, responder: self)
EngineEventCenter.shared.unsubscribeEngine(event: .onExitedRoom, observer: self)
EngineEventCenter.shared.unsubscribeEngine(event: .onDestroyedRoom, observer: self)
debugPrint("deinit \(self)")
}
}
extension RoomObserver: TUIRoomObserver {
func onRemoteUserEnterRoom(roomId: String, userInfo: TUIUserInfo) {
addUserList(userInfo: userInfo)
guard userList.count > 1 else { return }
if messageModel.owner == userId {
let prefixUserList = Array(userList.prefix(5))
messageManager.resendRoomMessage(message: messageModel, dic: ["userList":prefixUserList,"memberCount":userList.count])
}
}
func onRemoteUserLeaveRoom(roomId: String, userInfo: TUIUserInfo) {
userList = userList.filter { userDic in
if let userId = userDic["userId"] as? String, userId != userInfo.userId {
return true
}
return false
}
if messageModel.owner == userId {
let prefixUserList = Array(userList.prefix(5))
messageManager.resendRoomMessage(message: messageModel, dic: ["memberCount":userList.count,"userList":prefixUserList])
}
}
func onRoomDismissed(roomId: String, reason: TUIRoomDismissedReason) {
RoomVideoFloatView.dismiss()
messageManager.isReadyToSendMessage = true
refreshSource()
}
func onKickedOutOfRoom(roomId: String, reason: TUIKickedOutOfRoomReason, message: String) {
RoomVideoFloatView.dismiss()
messageManager.isReadyToSendMessage = true
refreshSource()
}
}
extension RoomObserver {
private func getUserList(nextSequence: Int) {
engineManager.roomEngine.getUserList(nextSequence: nextSequence) { [weak self] list, nextSequence in
guard let self = self else { return }
list.forEach { userInfo in
self.addUserList(userInfo: userInfo)
}
if nextSequence != 0 {
self.getUserList(nextSequence: nextSequence)
}
} onError: { code, message in
debugPrint("getUserList:code:\(code),message:\(message)")
}
}
private func addUserList(userInfo: TUIUserInfo) {
if getUserItem(userInfo.userId) == nil {
let userDic: [String: Any] = ["userId":userInfo.userId,"userName":userInfo.userName,"faceUrl": userInfo.avatarUrl]
userList.append(userDic)
}
}
private func getUserItem(_ userId: String) -> String? {
for userDic in userList {
if let userIdString = userDic["userId"] as? String, userIdString == userId {
return userIdString
}
}
return nil
}
}
extension RoomObserver: RoomKitUIEventResponder {
func onNotifyUIEvent(key: EngineEventCenter.RoomUIEvent, Object: Any?, info: [AnyHashable : Any]?) {
switch key {
case .TUIRoomKitService_RoomOwnerChanged:
guard let userId = info?["owner"] as? String else { return }
messageManager.resendRoomMessage(message: messageModel, dic: ["owner": userId])
default: break
}
}
}
extension RoomObserver: RoomEngineEventResponder {
func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String : Any]?) {
switch name {
case .onExitedRoom:
exitedRoom()
case .onDestroyedRoom:
destroyedRoom()
default: break
}
}
}

View File

@@ -0,0 +1,86 @@
//
// RoomMessageExtensionObserver.swift
// TUIRoomKit
//
// Created by janejntang on 2023/6/2.
//
import Foundation
import TUICore
import TIMCommon
class RoomMessageExtensionObserver: NSObject {
static let shared = RoomMessageExtensionObserver()
let roomMessageManager: RoomMessageManager = RoomMessageManager.shared
var settingMenuNavigationController: UINavigationController?
private override init() {
super.init()
}
@objc private func pushChatExtensionRoomSettingsViewController() {
if let nav = settingMenuNavigationController {
let vc = ChatExtensionRoomSettingsViewController(isOpenMicrophone: EngineManager.shared.store.isOpenMicrophone,
isOpenCamera: EngineManager.shared.store.isOpenCamera)
nav.pushViewController(vc, animated: true)
}
}
deinit {
debugPrint("deinit \(self)")
}
}
extension RoomMessageExtensionObserver: TUIExtensionProtocol {
func onGetExtension(_ key: String, param: [AnyHashable : Any]?) -> [TUIExtensionInfo]? {
if key == TUICore_TUIChatExtension_InputViewMoreItem_ClassicExtensionID {
guard let groupID = param?[TUICore_TUIChatExtension_InputViewMoreItem_GroupID] as? String, groupID != "" else { return nil }
guard let isNeedRoom = param?[TUICore_TUIChatExtension_InputViewMoreItem_FilterRoom] as? Bool, !isNeedRoom else { return nil }
let info = TUIExtensionInfo()
info.weight = 200
info.text = .meetingText
let defaultImage = UIImage(named: "room_quick_meeting", in: tuiRoomKitBundle(), compatibleWith: nil) ?? UIImage()
info.icon = TUICoreThemeConvert.getTUIDynamicImage(imageKey: "room_quick_meeting_image", defaultImage: defaultImage)
info.onClicked = { [weak self] param in
guard let self = self else { return }
if let vc = param[TUICore_TUIChatExtension_InputViewMoreItem_PushVC] as? UINavigationController {
self.roomMessageManager.navigateController = vc
}
if let groupId = param[TUICore_TUIChatExtension_InputViewMoreItem_GroupID] as? String {
self.roomMessageManager.groupId = groupId
}
self.roomMessageManager.sendRoomMessageToGroup()
}
return [info]
}
if key == TUICore_TUIContactExtension_MeSettingMenu_ClassicExtensionID {
if let nav = param?[TUICore_TUIContactExtension_MeSettingMenu_Nav] as? UINavigationController {
self.settingMenuNavigationController = nav
}
let data = TUICommonTextCellData()
data.key = .roomDeviceSetText
data.showAccessory = true
let cell = TUICommonTextCell()
cell.fill(with: data)
cell.mm_h = 60
cell.mm_w = UIScreen.main.bounds.width
let tap = UITapGestureRecognizer(target: self, action: #selector(pushChatExtensionRoomSettingsViewController))
cell.addGestureRecognizer(tap)
let info = TUIExtensionInfo()
let param = [TUICore_TUIContactExtension_MeSettingMenu_Weight: 460,
TUICore_TUIContactExtension_MeSettingMenu_View: cell,
TUICore_TUIContactExtension_MeSettingMenu_Data: data,
] as [String : Any]
info.data = param
return [info]
}
return nil
}
}
private extension String {
static var meetingText: String {
localized("Quick meeting")
}
static var roomDeviceSetText: String {
localized("Meeting Settings")
}
}

View File

@@ -0,0 +1,14 @@
//
// RoomSetListItemData.swift
// TUIRoomKit
//
// Created by janejntang on 2023/6/27.
//
import Foundation
class RoomSetListItemData {
var titleText: String = ""
var isSwitchOn: Bool = false
var action: ((Any)->Void)?
}

View File

@@ -0,0 +1,35 @@
//
// TUIRoomImAccessFactory.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/10.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import TUICore
class TUIRoomImAccessFactory: NSObject {
static let shared = TUIRoomImAccessFactory()
private override init() {
super.init()
}
deinit {
debugPrint("deinit \(self)")
}
}
extension TUIRoomImAccessFactory: TUIObjectProtocol {
func onCreateObject(_ method: String, param: [AnyHashable : Any]?) -> Any? {
if method == TUICore_TUIRoomImAccessFactory_GetRoomMessageViewMethod {
guard let message = param?[TUICore_TUIRoomImAccessFactory_GetRoomMessageViewMethod_Message] as?
V2TIMMessage else { return UIView(frame: .zero) }
let roomMessageModel = RoomMessageModel()
roomMessageModel.updateMessage(message: message)
let viewModel = RoomMessageViewModel(message: roomMessageModel)
let view = RoomMessageView(viewModel: viewModel)
return view
}
return nil
}
}

View File

@@ -0,0 +1,68 @@
//
// TUIRoomImAccessService.swift
// TUIRoomKit
//
// Created by janejntang on 2023/6/2.
//
import Foundation
import TUICore
class TUIRoomImAccessService: NSObject, TUIServiceProtocol {
static let shared = TUIRoomImAccessService()
var isShownInvitedToJoinRoomView: Bool = false
weak var inviteViewController: UIViewController?
var inviteWindow: UIWindow?
private override init() {
super.init()
initRoomMessage()
initSignalingListener()
initThemeResource()
}
func initRoomMessage() {
TUICore.callService(TUICore_TUIChatService, method: TUICore_TUIChatService_AppendCustomMessageMethod, param:
[BussinessID: BussinessID_GroupRoomMessage, TMessageCell_Name: "RoomMessageBubbleCell",
TMessageCell_Data_Name:"RoomMessageBubbleCellData",])
}
func initSignalingListener() {
V2TIMManager.sharedInstance().addSignalingListener(listener: self)
}
func initThemeResource() {
TUICoreThemeConvert.initThemeResource()
}
}
extension TUIRoomImAccessService: V2TIMSignalingListener {
func onReceiveNewInvitation(_ inviteID: String, inviter: String, groupID: String, inviteeList: [String], data: String?) {
guard let data = data else { return }
let dict = data.convertToDic()
guard let businessID = dict?["businessID"] as? String else { return }
guard businessID == "ROOM_INVITE_ACTION" else { return }
guard let roomId = dict?["roomId"] as? String else { return }
guard let userId = TUILogin.getUserID() else { return }
let dataDic = dict?["data"] as? NSDictionary ?? [:]
let inviter = dataDic["inviter"] as? NSDictionary ?? [:]
let avatarUrl = inviter["avatarUrl"] as? String ?? ""
let inviterUserName = inviter["userName"] as? String ?? ""
guard inviteeList.contains(userId) else { return }
let businessScene = TUILogin.getCurrentBusinessScene()
guard businessScene == .None || businessScene == .InMeetingRoom else { return }
showInvitedToJoinRoomView(inviterUserName: inviterUserName, inviteUserAvatarUrl: avatarUrl, roomId: roomId)
}
private func showInvitedToJoinRoomView(inviterUserName: String, inviteUserAvatarUrl: String, roomId: String) {
if isShownInvitedToJoinRoomView {
return
}
isShownInvitedToJoinRoomView = true
let inviteViewController = InvitedToJoinRoomViewController(inviteUserName: inviterUserName,inviteUserAvatarUrl:
inviteUserAvatarUrl, roomId: roomId)
let nav = UINavigationController(rootViewController: inviteViewController)
nav.setNavigationBarHidden(true, animated: true)
inviteWindow = UIWindow(frame: UIScreen.main.bounds)
inviteWindow?.windowLevel = .alert - 1
guard let inviteWindow = inviteWindow else { return }
inviteWindow.rootViewController = nav
inviteWindow.isHidden = false
inviteWindow.makeKeyAndVisible()
}
}

View File

@@ -0,0 +1,24 @@
//
// BusinessSceneUtil.swift
// TUIRoomKit
//
// Created by janejntang on 2023/7/3.
// Determine the current scene and set the current scene
import Foundation
import TUICore
class BusinessSceneUtil {
class func canJoinRoom() -> Bool {
let businessScene = TUILogin.getCurrentBusinessScene()
return businessScene == .InMeetingRoom || businessScene == .None
}
class func setJoinRoomFlag() {
TUILogin.setCurrentBusinessScene(.InMeetingRoom)
}
class func clearJoinRoomFlag() {
TUILogin.setCurrentBusinessScene(.None)
}
}

View File

@@ -0,0 +1,25 @@
//
// TUICoreThemeConvert.swift
// TUIRoomKit
//
// Created by janejntang on 2024/1/29.
//
import Foundation
import TUICore
class TUICoreThemeConvert {
static func initThemeResource() {
TUIThemeManager.share().registerThemeResourcePath(getTUIRoomKitThemePath(), for: .roomKit)
}
static func getTUIRoomKitThemePath() -> String {
return getTUIGetBundlePath("TUIRoomKitTheme", TUICore_TUIRoomImAccessService)
}
static func getTUIDynamicImage(imageKey: String, defaultImage: UIImage) -> UIImage? {
return TUITheme.dynamicImage(imageKey, module: .roomKit, defaultImage: defaultImage)
}
}

View File

@@ -0,0 +1,17 @@
//
// NSObject+TUIRoomMessageExtensionObserver.h
// TUIRoomKit
//
// Created by janejntang on 2023/5/8.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (TUIRoomMessageExtensionObserver)
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,20 @@
//
// NSObject+TUIRoomMessageExtensionObserver.m
// TUIRoomKit
//
// Created by janejntang on 2023/5/8.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "NSObject+TUIRoomMessageExtensionObserver.h"
#import "TUICore.h"
@implementation NSObject (TUIRoomMessageExtensionObserver)
+ (void) load {
if ([self respondsToSelector:@selector(tuiRoomKitExtensionLoad)]) {
[self performSelector:@selector(tuiRoomKitExtensionLoad)];
}
}
@end

View File

@@ -0,0 +1,111 @@
//
// ServiceInitializer.swift
// TUILiveKit
//
// Created by WesleyLei on 2023/10/19.
//
import Foundation
import TUICore
import RTCRoomEngine
extension NSObject {
@objc class func liveKitExtensionLoad() {
ServiceInitializer.shared.registerObserver()
}
}
class ServiceInitializer {
private var isLoginEngine: Bool = false
static let shared = ServiceInitializer()
private var invitationManager: TUIConferenceInvitationManager?
private var invitationObserver: InvitationObserverService?
private init() {
}
func registerObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(logoutSuccess(_:)),
name: NSNotification.Name(rawValue: NSNotification.Name.TUILogoutSuccess.rawValue),
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(loginSuccess(_:)),
name: NSNotification.Name(rawValue: NSNotification.Name.TUILoginSuccess.rawValue),
object: nil)
}
@objc private func logoutSuccess(_ notification: Notification) {
logout(onSuccess: nil, onError: nil)
removeInvitationObserver()
}
@objc private func loginSuccess(_ notification: Notification) {
login(onSuccess: nil, onError: nil)
addInvitationObserver()
}
private func addInvitationObserver() {
invitationObserver = InvitationObserverService.shared
invitationManager = TUIRoomEngine.sharedInstance().getExtension(extensionType: .conferenceInvitationManager) as? TUIConferenceInvitationManager
guard let invitationObserver = invitationObserver else { return }
invitationManager?.addObserver(invitationObserver)
}
private func removeInvitationObserver() {
guard let invitationObserver = invitationObserver else { return }
invitationManager?.removeObserver(invitationObserver)
}
func login(onSuccess: TUISuccessBlock?, onError: TUIErrorBlock?) {
if isLoginEngine {
onSuccess?()
} else {
let sdkAppId = Int(TUILogin.getSdkAppID())
let userId = TUILogin.getUserID() ?? ""
let userSig = TUILogin.getUserSig() ?? ""
ServiceInitializer.login(sdkAppId: sdkAppId, userId: userId, userSig: userSig) { [weak self] in
guard let self = self else { return }
self.isLoginEngine = true
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
}
func logout(onSuccess: TUISuccessBlock?, onError: TUIErrorBlock?) {
if isLoginEngine {
ServiceInitializer.logout {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
self.isLoginEngine = false
} else {
onSuccess?()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
extension ServiceInitializer {
static func login(sdkAppId:Int,userId:String,userSig:String,onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
TUIRoomEngine.login(sdkAppId: sdkAppId, userId: userId, userSig: userSig) {
onSuccess()
} onError: { code, message in
onError(code, message)
}
}
static func logout(onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
TUIRoomEngine.logout {
onSuccess()
} onError: { code, message in
onError(code, message)
}
}
}

View File

@@ -0,0 +1,20 @@
//
// TUIRoomMessageExtensionObserver.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/8.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import TUICore
extension NSObject {
@objc class func tuiRoomKitExtensionLoad() {
TUICore.registerExtension(TUICore_TUIChatExtension_InputViewMoreItem_ClassicExtensionID, object: RoomMessageExtensionObserver.shared)
TUICore.registerService(TUICore_TUIRoomImAccessService, object: TUIRoomImAccessService.shared)
TUICore.registerObjectFactory(TUICore_TUIRoomImAccessFactory, objectFactory: TUIRoomImAccessFactory.shared)
TUICore.registerExtension(TUICore_TUIContactExtension_MeSettingMenu_ClassicExtensionID, object: RoomMessageExtensionObserver.shared)
ServiceInitializer.shared.registerObserver()
}
}

View File

@@ -0,0 +1,42 @@
//
// ChatExtensionRoomSettingsViewController.swift
// TUIRoomKit
//
// Created by janejntang on 2023/6/26.
//
import Foundation
class ChatExtensionRoomSettingsViewController: UIViewController {
let roomView: ChatExtensionRoomSettingsView
init(isOpenMicrophone: Bool, isOpenCamera: Bool) {
let viewModel = ChatExtensionRoomSettingsViewModel(isOpenMicrophone: isOpenMicrophone, isOpenCamera: isOpenCamera)
roomView = ChatExtensionRoomSettingsView(viewModel: viewModel)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = roomView
}
override func viewDidLoad() {
setNavBar()
}
private func setNavBar() {
navigationItem.title = .roomDeviceSetText
}
deinit {
debugPrint("deinit \(self)")
}
}
private extension String {
static var roomDeviceSetText: String {
localized("Meeting Settings")
}
}

View File

@@ -0,0 +1,35 @@
//
// InvitedToJoinRoomViewController.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/25.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
class InvitedToJoinRoomViewController: UIViewController {
let roomView: InvitedToJoinRoomView
let viewModel: InvitedToJoinRoomViewModel
init(inviteUserName: String,inviteUserAvatarUrl: String, roomId: String) {
viewModel = InvitedToJoinRoomViewModel(inviteUserName: inviteUserName, inviteUserAvatarUrl:inviteUserAvatarUrl, roomId: roomId)
roomView = InvitedToJoinRoomView(viewModel: viewModel)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = roomView
}
override func viewDidDisappear(_ animated: Bool) {
viewModel.stopPlay()
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,86 @@
//
// ChatExtensionRoomSettingsItemView.swift
// TUIRoomKit
//
// Created by janejntang on 2023/6/27.
//
import Foundation
class ChatExtensionRoomSettingsItemView: UIView {
let itemData: RoomSetListItemData
let titleLabel: UILabel = {
let view = UILabel()
view.backgroundColor = .clear
view.textColor = .black
view.font = UIFont(name: "PingFangSC-Medium", size: 14)
view.minimumScaleFactor = 0.5
return view
}()
let rightSwitch: UISwitch = {
let view = UISwitch()
view.isOn = true
view.onTintColor = UIColor(0x0062E3)
return view
}()
let line: UIView = {
let view = UIView()
view.backgroundColor = .gray
view.alpha = 0.3
return view
}()
init(itemData: RoomSetListItemData) {
self.itemData = itemData
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var isViewReady: Bool = false
override func didMoveToWindow() {
super.didMoveToWindow()
guard !isViewReady else { return }
constructViewHierarchy()
activateConstraints()
bindInteraction()
isViewReady = true
}
private func constructViewHierarchy() {
addSubview(titleLabel)
addSubview(rightSwitch)
addSubview(line)
}
private func activateConstraints() {
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(10)
make.centerY.equalToSuperview()
make.width.equalTo(100.scale375())
}
rightSwitch.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-10)
make.centerY.equalToSuperview()
}
line.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(10)
make.trailing.equalToSuperview().offset(-10)
make.bottom.equalToSuperview().offset(-2)
make.height.equalTo(0.5)
}
}
private func bindInteraction() {
backgroundColor = .white
titleLabel.text = itemData.titleText
rightSwitch.isOn = itemData.isSwitchOn
rightSwitch.addTarget(self, action: #selector(switchAction(sender:)), for: .touchUpInside)
}
@objc func switchAction(sender: UISwitch) {
itemData.action?(sender)
}
}

View File

@@ -0,0 +1,65 @@
//
// ChatExtensionRoomSettingsView.swift
// TUIRoomKit
//
// Created by janejntang on 2023/6/26.
//
import Foundation
class ChatExtensionRoomSettingsView: UIView {
let viewModel : ChatExtensionRoomSettingsViewModel
let stackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.alignment = .center
view.distribution = .equalSpacing
view.spacing = 0
return view
}()
init(viewModel: ChatExtensionRoomSettingsViewModel) {
self.viewModel = viewModel
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - view layout
private var isViewReady: Bool = false
override func didMoveToWindow() {
super.didMoveToWindow()
guard !isViewReady else { return }
backgroundColor = .white
constructViewHierarchy()
activateConstraints()
isViewReady = true
}
private func constructViewHierarchy() {
addSubview(stackView)
for item in viewModel.roomSettingsViewItems {
let view = ChatExtensionRoomSettingsItemView(itemData: item)
stackView.addArrangedSubview(view)
view.snp.makeConstraints { make in
make.height.equalTo(60.scale375())
make.width.equalToSuperview()
}
}
}
private func activateConstraints() {
stackView.snp.makeConstraints { make in
make.top.equalTo(safeAreaLayoutGuide.snp.top).offset(10)
make.leading.equalToSuperview().offset(10)
make.trailing.equalToSuperview().offset(-10)
}
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,178 @@
//
// RoomaInviteView.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/24.
//
import Foundation
class InvitedToJoinRoomView: UIView {
let viewModel : InvitedToJoinRoomViewModel
let userNameLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "PingFangSC-Regular", size: 30)
label.textColor = .white
return label
}()
let inviteLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "PingFangSC-Regular", size: 15)
label.text = .inviteMeetingText
label.textColor = .white
return label
}()
let userImageView: UIImageView = {
let image = UIImageView()
return image
}()
let disagreeButton: UIButton = {
let button = UIButton(type: .custom)
return button
}()
let disagreeImageView: UIImageView = {
let image = UIImage(named: "room_hangup", in: tuiRoomKitBundle(), compatibleWith: nil)
let imageView = UIImageView(image: image)
return imageView
}()
let disagreeLabel: UILabel = {
let label = UILabel()
label.text = .declineText
label.textColor = .red
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 20.0)
return label
}()
let agreeButton: UIButton = {
let button = UIButton(type: .custom)
return button
}()
let agreeImageView: UIImageView = {
let image = UIImage(named: "room_handsfree_on", in: tuiRoomKitBundle(), compatibleWith: nil)
let imageView = UIImageView(image: image)
return imageView
}()
let agreeLabel: UILabel = {
let label = UILabel()
label.text = .agreeText
label.textColor = .white
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 20.0)
return label
}()
init(viewModel: InvitedToJoinRoomViewModel) {
self.viewModel = viewModel
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - view layout
private var isViewReady: Bool = false
override func didMoveToWindow() {
super.didMoveToWindow()
guard !isViewReady else { return }
backgroundColor = UIColor(0x242424)
constructViewHierarchy()
activateConstraints()
bindInteraction()
isViewReady = true
}
private func constructViewHierarchy() {
addSubview(userNameLabel)
addSubview(inviteLabel)
addSubview(userImageView)
addSubview(disagreeButton)
disagreeButton.addSubview(disagreeImageView)
disagreeButton.addSubview(disagreeLabel)
addSubview(agreeButton)
agreeButton.addSubview(agreeImageView)
agreeButton.addSubview(agreeLabel)
}
private func activateConstraints() {
userNameLabel.snp.makeConstraints { make in
make.top.equalTo(safeAreaLayoutGuide.snp.top).offset(30)
make.trailing.equalToSuperview().offset(-130)
}
inviteLabel.snp.makeConstraints { make in
make.trailing.equalTo(userNameLabel)
make.top.equalTo(userNameLabel.snp.bottom).offset(15)
}
userImageView.snp.makeConstraints { make in
make.top.equalTo(userNameLabel)
make.trailing.equalToSuperview().offset(-20)
make.width.height.equalTo(80)
}
disagreeButton.snp.makeConstraints { make in
make.bottom.equalToSuperview().offset(-100)
make.width.equalTo(80)
make.height.equalTo(120)
make.leading.equalToSuperview().offset(50)
}
disagreeImageView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.width.height.equalTo(80)
}
disagreeLabel.snp.makeConstraints { make in
make.top.equalTo(disagreeImageView.snp.bottom).offset(5)
make.centerX.equalToSuperview()
make.width.equalTo(80)
make.bottom.equalToSuperview()
}
agreeButton.snp.makeConstraints { make in
make.bottom.equalTo(disagreeButton)
make.width.equalTo(80)
make.height.equalTo(120)
make.trailing.equalToSuperview().offset(-50)
}
agreeImageView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.width.height.equalTo(80)
}
agreeLabel.snp.makeConstraints { make in
make.top.equalTo(agreeImageView.snp.bottom).offset(5)
make.centerX.equalToSuperview()
make.width.equalTo(80)
make.bottom.equalToSuperview()
}
}
private func bindInteraction() {
disagreeButton.addTarget(self, action: #selector(disagreeAction(sender:)), for: .touchUpInside)
agreeButton.addTarget(self, action: #selector(agreeAction(sender:)), for: .touchUpInside)
viewModel.startPlay()
setupViewState()
}
func setupViewState() {
userNameLabel.text = viewModel.inviteUserName
let placeholderImage = UIImage(named: "room_default_avatar", in: tuiRoomKitBundle(), compatibleWith: nil)
userImageView.sd_setImage(with: URL(string: viewModel.avatarUrl), placeholderImage: placeholderImage)
}
@objc private func disagreeAction(sender: UIButton) {
viewModel.disagreeAction()
}
@objc private func agreeAction(sender: UIButton) {
viewModel.agreeAction()
}
deinit {
debugPrint("deinit \(self)")
}
}
private extension String {
static var inviteMeetingText: String {
localized("Invite you to a meeting")
}
static var declineText: String {
localized("Decline")
}
static var agreeText: String {
localized("Agree")
}
}

View File

@@ -0,0 +1,49 @@
//
// RoomMsgCell.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/8.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import TIMCommon
@objc(RoomMessageBubbleCell)
class RoomMessageBubbleCell: TUIBubbleMessageCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
guard self.container != nil else { return }
let message = RoomMessageModel()
let view = RoomMessageView(viewModel: RoomMessageViewModel(message: message))
self.container.addSubview(view)
view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func fill(with data: TUIBubbleMessageCellData) {
super.fill(with: data)
let customData = data as? RoomMessageBubbleCellData
guard let messageModel = customData?.messageModel as? RoomMessageModel else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
let subviewArray = self.container.subviews
for view in subviewArray where view is RoomMessageView {
guard let messageView = view as? RoomMessageView else { continue }
messageView.viewModel.changeMessage(message: messageModel)
}
}
}
override class func getContentSize(_ data: TUIMessageCellData?) -> CGSize {
return CGSize(width: 238, height: 157)
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,328 @@
//
// RoomMessageView.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/9.
// Copyright © 2023 Tencent. All rights reserved.
//
class RoomMessageView: UIView {
let viewModel: RoomMessageViewModel
private var viewArray: [UIView] = []
let roomStatusView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(0xD1E4FD)
return view
}()
let roomStatusImageView: UIImageView = {
let image = UIImageView()
return image
}()
let roomStatusLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "PingFangSC-Regular", size: 15)
return label
}()
let roomNameLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "PingFangSC-Regular", size: 20)
label.textColor = .black
label.adjustsFontSizeToFitWidth = true
return label
}()
let stackView: UIStackView = {
let view = UIStackView()
view.axis = .horizontal
view.alignment = .leading
view.distribution = .equalSpacing
view.spacing = 2
return view
}()
lazy var inviteUserButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "room_chat_invite", in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
return button
}()
let userNumberLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "PingFangSC-Regular", size: 15)
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor(0x888888)
label.textAlignment = isRTL ? .right : .left
return label
}()
let enterRoomButton: UIButton = {
let button = UIButton()
button.setTitle(.enterMeetingText, for: .normal)
button.setTitleColor(.white, for: .normal)
button.setBackgroundImage(UIColor.tui_color(withHex: "147AFF").trans2Image(), for: .normal)
button.setTitle(.alreadyEnteredMeeting, for: .selected)
button.setTitleColor(UIColor(0x999999), for: .selected)
button.setBackgroundImage(UIColor.tui_color(withHex: "F3F4F4").trans2Image(), for: .selected)
button.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 15)
return button
}()
let enterRoomStatusLabel: UILabel = {
let label = UILabel()
label.adjustsFontSizeToFitWidth = true
label.font = UIFont(name: "PingFangSC-Regular", size: 15)
label.textColor = UIColor(0x888888)
label.textAlignment = .center
return label
}()
init(viewModel: RoomMessageViewModel) {
self.viewModel = viewModel
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - view layout
private var isViewReady: Bool = false
override func didMoveToWindow() {
super.didMoveToWindow()
guard !isViewReady else { return }
backgroundColor = .white
constructViewHierarchy()
activateConstraints()
bindInteraction()
isViewReady = true
}
func constructViewHierarchy() {
addSubview(roomStatusView)
roomStatusView.addSubview(roomStatusImageView)
roomStatusView.addSubview(roomStatusLabel)
roomStatusView.addSubview(roomNameLabel)
roomStatusView.addSubview(stackView)
setupUserStackView()
roomStatusView.addSubview(inviteUserButton)
addSubview(enterRoomStatusLabel)
addSubview(userNumberLabel)
addSubview(enterRoomButton)
}
func activateConstraints() {
roomStatusView.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
make.height.equalTo(110)
}
roomStatusImageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(15)
make.leading.equalToSuperview().offset(12)
}
roomStatusLabel.snp.makeConstraints { make in
make.centerY.equalTo(roomStatusImageView)
make.leading.equalTo(roomStatusImageView.snp.trailing).offset(4)
}
roomNameLabel.snp.makeConstraints { make in
make.top.equalTo(roomStatusImageView.snp.bottom).offset(9)
make.leading.equalTo(roomStatusImageView)
make.trailing.equalToSuperview().offset(-12)
}
stackView.snp.makeConstraints { make in
make.leading.equalTo(roomNameLabel)
make.bottom.equalToSuperview().offset(-4)
}
inviteUserButton.snp.makeConstraints { make in
make.leading.equalTo(stackView.snp.trailing).offset(2)
make.width.height.equalTo(24)
make.bottom.equalToSuperview().offset(-4)
}
userNumberLabel.snp.makeConstraints { make in
make.bottom.equalToSuperview().offset(-10)
make.leading.equalToSuperview().offset(12)
make.width.equalTo(100)
make.height.equalTo(20)
}
enterRoomButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-12)
make.bottom.equalToSuperview().offset(-10)
make.width.equalTo(70)
make.height.equalTo(27)
}
enterRoomStatusLabel.snp.makeConstraints { make in
make.bottom.equalToSuperview().offset(-10)
make.centerX.equalToSuperview()
make.width.equalTo(150)
make.height.equalTo(20)
}
}
func bindInteraction() {
viewModel.viewResponder = self
enterRoomButton.addTarget(self, action: #selector(enterRoomAction(sender:)), for: .touchUpInside)
inviteUserButton.addTarget(self, action: #selector(inviteUserAction(sender:)), for: .touchUpInside)
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(sender:)))
addGestureRecognizer(longPressGesture)
layer.masksToBounds = true
layer.borderWidth = 0.5
layer.borderColor = UIColor(0xDDDDDD).cgColor
setupViewState()
}
@objc func enterRoomAction(sender: UIButton) {
viewModel.enterRoomAction()
}
@objc func inviteUserAction(sender: UIButton) {
viewModel.inviteUserAction()
}
@objc func longPressAction(sender: UIView) {
return
}
func setupViewState() {
roomNameLabel.text = (viewModel.message.ownerName ) + .quickMeetingText
if viewModel.message.owner != viewModel.userId {
inviteUserButton.isHidden = true
} else {
inviteUserButton.isHidden = false
}
switch viewModel.message.roomState {
case .creating:
roomStatusImageView.image = UIImage(named: "room_is_creating", in: tuiRoomKitBundle(), compatibleWith: nil)
roomStatusLabel.text = .meetingText
enterRoomStatusLabel.isHidden = false
enterRoomStatusLabel.text = .startingMeetingText + "..."
userNumberLabel.isHidden = true
enterRoomButton.isHidden = true
roomStatusView.backgroundColor = UIColor(0xDCEAFD)
case .created:
roomStatusImageView.image = UIImage(named: "room_created_success", in: tuiRoomKitBundle(), compatibleWith: nil)
roomStatusLabel.text = .meetingText + "." + .inProgressText
roomStatusLabel.textColor = UIColor(0x15B72D)
enterRoomButton.isHidden = false
userNumberLabel.isHidden = false
enterRoomStatusLabel.isHidden = true
if viewModel.message.memberCount > 1 {
userNumberLabel.text = String(viewModel.message.memberCount) + .peopleEnteredMeetingText
} else {
userNumberLabel.text = .waitingMembersEnterMeetingText
}
roomStatusView.backgroundColor = UIColor(0xDCEAFD)
case .destroyed:
roomStatusImageView.image = UIImage(named: "room_has_destroyed", in: tuiRoomKitBundle(), compatibleWith: nil)
roomStatusLabel.text = .meetingText
roomStatusLabel.textColor = UIColor(0x888888)
enterRoomStatusLabel.isHidden = false
enterRoomStatusLabel.text = .meetingEnded
enterRoomButton.isHidden = true
userNumberLabel.isHidden = true
roomStatusView.backgroundColor = UIColor(0xDDDDDD)
inviteUserButton.isHidden = true
default: break
}
}
func setupStackView() {
if !isViewReady {
return
}
setupUserStackView()
layoutIfNeeded()
if viewModel.message.memberCount > 1 {
userNumberLabel.text = String(viewModel.message.memberCount) + .peopleEnteredMeetingText
} else {
userNumberLabel.text = .waitingMembersEnterMeetingText
}
}
private func setupUserStackView() {
var userNumber = 0
viewArray.forEach { view in
view.removeFromSuperview()
}
viewModel.message.userList.forEach { userDic in
if userNumber >= 5 {
let label = UILabel()
label.text = "..."
viewArray.append(label)
stackView.addArrangedSubview(label)
label.snp.makeConstraints { make in
make.height.equalTo(24)
make.width.equalTo(24)
}
return
}
let placeholderImage = UIImage(named: "room_default_avatar", in: tuiRoomKitBundle(), compatibleWith: nil)
let imageView = UIImageView()
if let userAvatar = userDic["faceUrl"] as? String {
imageView.sd_setImage(with: URL(string: userAvatar), placeholderImage: placeholderImage)
} else {
imageView.image = placeholderImage
}
viewArray.append(imageView)
stackView.addArrangedSubview(imageView)
imageView.snp.makeConstraints { make in
make.height.equalTo(24)
make.width.equalTo(24)
}
userNumber = userNumber + 1
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let viewFrame = self.frame
let inviteViewFrame = self.inviteUserButton.frame
if viewFrame.contains(point) && !inviteViewFrame.contains(point) {
return enterRoomButton
}
return super.hitTest(point, with: event)
}
deinit {
debugPrint("deinit \(self)")
}
}
extension RoomMessageView: RoomMessageViewResponder {
func updateStackView() {
setupStackView()
}
func updateRoomStatus() {
setupViewState()
}
func changeEnterRoomButton(isEnabled: Bool) {
enterRoomButton.isEnabled = isEnabled
}
func updateEnteredRoom() {
enterRoomButton.isSelected = true
}
func updateExitRoom() {
enterRoomButton.isSelected = false
}
}
private extension String {
static var inviteMeetingText: String {
localized("Invite you to a meeting")
}
static var enterMeetingText: String {
localized("Enter the meeting")
}
static var alreadyEnteredMeeting: String {
localized("Already entered the meeting")
}
static var meetingText: String {
localized("Meeting")
}
static var inProgressText: String {
localized("Ongoing")
}
static var peopleEnteredMeetingText: String {
localized("People have entered the meeting")
}
static var startingMeetingText: String {
localized("Starting meeting")
}
static var waitingMembersEnterMeetingText: String {
localized("Waiting for members to enter the meeting")
}
static var meetingEnded: String {
localized("Meeting ended")
}
static var quickMeetingText: String {
localized("Quick meeting")
}
}

View File

@@ -0,0 +1,47 @@
//
// ChatExtensionRoomSettingsViewModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/6/26.
//
import Foundation
class ChatExtensionRoomSettingsViewModel {
var isOpenMicrophone: Bool
var isOpenCamera: Bool
private let engineManager = EngineManager.shared
private(set) var roomSettingsViewItems: [RoomSetListItemData] = []
init(isOpenMicrophone: Bool, isOpenCamera: Bool) {
self.isOpenMicrophone = isOpenMicrophone
self.isOpenCamera = isOpenCamera
createRoomSettingsModel()
}
func createRoomSettingsModel() {
let openMicItem = RoomSetListItemData()
openMicItem.titleText = .micSeatText
openMicItem.isSwitchOn = isOpenMicrophone
openMicItem.action = {[weak self] sender in
guard let self = self, let view = sender as? UISwitch else { return }
self.engineManager.store.isOpenMicrophone = view.isOn
}
roomSettingsViewItems.append(openMicItem)
let openCameraItem = RoomSetListItemData()
openCameraItem.titleText = .cameraSetText
openCameraItem.isSwitchOn = isOpenCamera
openCameraItem.action = {[weak self] sender in
guard let self = self, let view = sender as? UISwitch else { return }
self.engineManager.store.isOpenCamera = view.isOn
}
roomSettingsViewItems.append(openCameraItem)
}
}
private extension String {
static var cameraSetText: String {
localized("Join the meeting and start the camera")
}
static var micSeatText: String {
localized("Join the conference and turn on the mic")
}
}

View File

@@ -0,0 +1,95 @@
//
// RoomaInviteViewModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/24.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import TUICore
import AVFAudio
class InvitedToJoinRoomViewModel: NSObject, AVAudioPlayerDelegate {
let inviteUserName: String
let roomId: String
var avatarUrl: String
var displayLink: CADisplayLink?
var audioPlayer: AVAudioPlayer = AVAudioPlayer()
var startTime: TimeInterval?
var endTime: TimeInterval?
var messageManager: RoomMessageManager {
return RoomMessageManager.shared
}
private var roomManager: RoomManager {
RoomManager.shared
}
init(inviteUserName: String, inviteUserAvatarUrl: String, roomId: String) {
self.inviteUserName = inviteUserName
self.roomId = roomId
avatarUrl = inviteUserAvatarUrl
super.init()
playAudio(forResource: "phone_ringing", ofType: "mp3")
}
func startPlay() {
audioPlayer.play()
}
func stopPlay() {
audioPlayer.stop()
}
func disagreeAction() {
stopPlay()
closeInvitedToJoinRoomView()
}
func agreeAction() {
stopPlay()
if EngineManager.shared.store.isEnteredRoom {
roomManager.exitOrDestroyPreviousRoom { [weak self] in
guard let self = self else { return }
self.enterRoom()
} onError: { code, message in
debugPrint("exitRoom, code:\(code), message:\(message)")
}
} else {
enterRoom()
}
}
private func enterRoom() {
roomManager.enterRoom(roomId: roomId)
closeInvitedToJoinRoomView()
}
private func playAudio(forResource: String, ofType: String){
if let bundlePath = Bundle.main.path(forResource: forResource, ofType: ofType) {
let url = URL(fileURLWithPath: bundlePath)
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
} catch let error {
debugPrint("AVAudioSession set outputAudioPort error:\(error.localizedDescription)")
}
do {
try audioPlayer = AVAudioPlayer(contentsOf: url)
audioPlayer.numberOfLoops = -1
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
} catch let error {
debugPrint("audioPlayer error: \(error.localizedDescription)")
}
}
}
private func closeInvitedToJoinRoomView() {
TUIRoomImAccessService.shared.inviteWindow?.isHidden = true
TUIRoomImAccessService.shared.inviteWindow = nil
TUIRoomImAccessService.shared.isShownInvitedToJoinRoomView = false
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,59 @@
//
// RoomMsgViewModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/8.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import TIMCommon
import TUICore
import RTCRoomEngine
@objc(RoomMessageBubbleCellData)
class RoomMessageBubbleCellData: TUIBubbleMessageCellData {
var messageModel: RoomMessageModel?
override init(direction: TMsgDirection) {
super.init(direction: direction)
}
override class func getCellData(_ message: V2TIMMessage) -> TUIMessageCellData {
let messageModel = RoomMessageModel()
messageModel.updateMessage(message: message)
if messageModel.roomId == RoomManager.shared.roomId, messageModel.roomState != .destroyed {
RoomManager.shared.roomObserver.messageModel.updateMessage(message: message)
}
let messageCellData = RoomMessageBubbleCellData(direction: message.isSelf ? .MsgDirectionOutgoing : .MsgDirectionIncoming)
messageCellData.messageModel = messageModel
return messageCellData
}
override class func getDisplayString(_ message: V2TIMMessage) -> String {
let businessID = parseBusinessID(message: message)
if businessID == BussinessID_GroupRoomMessage {
let dict = TUITool.jsonData2Dictionary(message.customElem.data) as? [String: Any]
let userName = dict?["ownerName"] as? String ?? ""
return userName + .quickMeetingText
} else {
return super.getDisplayString(message)
}
}
private class func parseBusinessID(message: V2TIMMessage?) -> String {
guard let message = message else { return "" }
let customData = message.customElem.data
let dict = TUITool.jsonData2Dictionary(customData)
guard let businessID = dict?["businessID"] as? String else { return ""}
return businessID
}
deinit {
debugPrint("deinit \(self)")
}
}
private extension String {
static var quickMeetingText: String {
localized("'s quick meeting")
}
}

View File

@@ -0,0 +1,96 @@
//
// RoomMessageViewModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/5/10.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import RTCRoomEngine
import TUICore
import TIMCommon
protocol RoomMessageViewResponder: NSObject {
func updateStackView()
func updateRoomStatus()
func updateEnteredRoom()
func updateExitRoom()
}
class RoomMessageViewModel: NSObject {
var message: RoomMessageModel
private var engineManager: EngineManager {
EngineManager.shared
}
lazy var userId: String = {
return TUILogin.getUserID() ?? EngineManager.shared.store.currentUser.userId
}()
var messageManager: RoomMessageManager {
RoomMessageManager.shared
}
let roomManager = RoomManager.shared
weak var viewResponder: RoomMessageViewResponder?
init(message: RoomMessageModel) {
self.message = message
super.init()
roomManager.roomObserver.addListener(listener: self)
}
deinit {
roomManager.roomObserver.removeListener(listener: self)
debugPrint("deinit \(self)")
}
func changeMessage(message: RoomMessageModel) {
self.message = message
viewResponder?.updateStackView()
viewResponder?.updateRoomStatus()
}
func enterRoomAction() {
guard BusinessSceneUtil.canJoinRoom() else { return }
if roomManager.isEnteredOtherRoom(roomId: message.roomId) {
roomManager.exitOrDestroyPreviousRoom { [weak self] in
guard let self = self else { return }
self.enterRoom()
} onError: { [weak self] code, message in
debugPrint("exitRoom,code:\(code),message:\(message)")
guard let self = self else { return }
self.enterRoom()
}
} else {
enterRoom()
}
}
private func enterRoom() {
if !engineManager.store.isEnteredRoom {
roomManager.enterRoom(roomId: message.roomId)
} else {
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_ShowRoomMainView, param: [:])
}
}
func inviteUserAction() {
guard message.groupId.count > 0 else { return }
let inviter = TUILoginUserInfo()
inviter.userId = userId
inviter.userName = TUILogin.getNickName() ?? ""
inviter.avatarUrl = TUILogin.getFaceUrl() ?? ""
InviteToJoinRoomManager.startInviteToJoinRoom(message: message, inviter: inviter)
}
}
extension RoomMessageViewModel: RoomObserverListener {
func onRoomEnter(messageId: String, code: Int, message: String) {
if code == 0, messageId == self.message.messageId {
viewResponder?.updateEnteredRoom()
}
}
func onRoomExit(messageId: String) {
if messageId == self.message.messageId {
viewResponder?.updateExitRoom()
}
}
}