Files
featherVoice/TUIKit/TUIRoomKit/Source/View/Page/RoomRouter.swift
2025-08-08 10:49:36 +08:00

443 lines
19 KiB
Swift

//
// RoomRouter.swift
// TUIRoomKit
//
// Created by janejntang on 2022/9/30.
// Copyright © 2022 Tencent. All rights reserved.
//
import Foundation
import TUICore
import RTCRoomEngine
// View routing context
class RouteContext {
var rootNavigation: UINavigationController?
typealias Weak<T> = () -> T?
var alterControllers: [Weak<UIViewController>] = []
var popUpViewController: Weak<PopUpViewController>?
var appearance: AnyObject?
let navigationDelegate = RoomRouter.RoomNavigationDelegate()
var currentLandscape: Bool = isLandscape
weak var rootViewController: UIViewController?
var chatWindow : UIWindow?
let chatWindowWidth = min(kScreenWidth, kScreenHeight) + 20
let chatWindowHeight = min(kScreenWidth, kScreenHeight)
init() {
if #available(iOS 13, *) {
appearance = UINavigationBarAppearance()
}
}
}
class RoomRouter: NSObject {
static let shared = RoomRouter()
private let context: RouteContext = RouteContext()
private override init() {
super.init()
subscribeUIEvent()
}
class RoomNavigationDelegate: NSObject {
}
var navController: UINavigationController? {
return context.rootNavigation
}
private func subscribeUIEvent() {
EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_ShowRoomMainView, responder: self)
EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_ShowRoomVideoFloatView, responder: self)
EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_HiddenChatWindow, responder: self)
EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, responder: self)
NotificationCenter.default.addObserver(self, selector: #selector(handleDeviceOrientationChange), name: UIDevice.orientationDidChangeNotification, object: nil)
}
private func unsubscribeEvent() {
EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_ShowRoomMainView, responder: self)
EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_ShowRoomVideoFloatView, responder: self)
EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_HiddenChatWindow, responder: self)
EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_DismissConferenceViewController, responder: self)
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}
func hasChatWindow() -> Bool {
return context.chatWindow != nil
}
func pushToChatController(user: UserEntity, roomInfo: TUIRoomInfo) {
guard let chatVC = makeChatController(user: user, roomInfo: roomInfo) else { return }
if !isLandscape {
push(viewController: chatVC, animated: false)
} else {
let nav = UINavigationController(rootViewController: chatVC)
nav.navigationBar.backgroundColor = .white
if #available(iOS 13, *) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
context.chatWindow = UIWindow(windowScene: windowScene)
} else {
context.chatWindow = UIWindow(frame: UIScreen.main.bounds)
}
context.chatWindow?.frame = CGRect(x: kScreenWidth - context.chatWindowWidth - kDeviceSafeBottomHeight, y: 0, width: context.chatWindowWidth, height: context.chatWindowHeight)
context.chatWindow?.rootViewController = nav
context.chatWindow?.windowLevel = UIWindow.Level.statusBar + 1
context.chatWindow?.isHidden = false
context.chatWindow?.makeKeyAndVisible()
}
}
func makeChatController(user: UserEntity, roomInfo: TUIRoomInfo) -> UIViewController? {
let config: [String : Any] = [
TUICore_TUIChatService_SetChatExtensionMethod_EnableVideoCallKey: false,
TUICore_TUIChatService_SetChatExtensionMethod_EnableAudioCallKey: false,
TUICore_TUIChatService_SetChatExtensionMethod_EnableLinkKey: false,
]
TUICore.callService(TUICore_TUIChatService, method: TUICore_TUIChatService_SetChatExtensionMethod, param: config)
let maxSizeKey = "TUICore_TUIChatService_SetMaxTextSize"
let chatWidth = min(kScreenWidth, kScreenHeight)
let sizeParam : [String : Any] = ["maxsize": CGSize(width: chatWidth - 150, height: Double(MAXFLOAT))]
TUICore.callService(TUICore_TUIChatService, method: maxSizeKey, param: sizeParam)
let param : [String : Any] = [
TUICore_TUIChatObjectFactory_ChatViewController_Title : String.chatText,
TUICore_TUIChatObjectFactory_ChatViewController_GroupID: roomInfo.roomId,
TUICore_TUIChatObjectFactory_ChatViewController_AvatarUrl : user.avatarUrl,
TUICore_TUIChatObjectFactory_ChatViewController_Enable_Video_Call : String(0),
TUICore_TUIChatObjectFactory_ChatViewController_Enable_Audio_Call : String(0),
TUICore_TUIChatObjectFactory_ChatViewController_Enable_Room : String(0),
TUICore_TUIChatObjectFactory_ChatViewController_Limit_Portrait_Orientation: String(1),
TUICore_TUIChatObjectFactory_ChatViewController_Enable_Poll : String(0),
TUICore_TUIChatObjectFactory_ChatViewController_Enable_GroupNote : String(0),
TUICore_TUIChatObjectFactory_ChatViewController_Enable_WelcomeCustomMessage :String(0),
TUICore_TUIChatObjectFactory_ChatViewController_Enable_TakePhoto :String(0),
TUICore_TUIChatObjectFactory_ChatViewController_Enable_RecordVideo :String(0),
]
return TUICore.createObject(TUICore_TUIChatObjectFactory, key: TUICore_TUIChatObjectFactory_ChatViewController_Classic,
param: param) as? UIViewController
}
func pushMainViewController() {
let vc = makeMainViewController()
push(viewController: vc)
}
func presentPopUpViewController(viewType: PopUpViewType, height: CGFloat, backgroundColor: UIColor = UIColor(0x1B1E26)) {
if let observer = context.popUpViewController, let vc = observer() {
vc.dismiss(animated: false)
}
let vc = makePopUpViewController(viewType: viewType, height: height, backgroundColor: backgroundColor)
let weakObserver = { [weak vc] in return vc }
context.popUpViewController = weakObserver
present(viewController: vc)
}
func dismissPopupViewController(completion: (() -> Void)? = nil) {
guard let observer = context.popUpViewController, let vc = observer() else {
completion?()
return
}
vc.viewModel.changeSearchControllerActive()
vc.dismiss(animated: true, completion: completion)
context.popUpViewController = nil
}
func pop(animated: Bool = true) {
guard let viewControllerArray = navController?.viewControllers else { return }
if viewControllerArray.count <= 1 {
viewControllerArray.first?.dismiss(animated: true)
navController?.dismiss(animated: true)
context.rootNavigation = nil
} else {
if let vc = viewControllerArray.last, vc is ConferenceMainViewController {
navController?.popViewController(animated: animated)
context.rootNavigation = nil
} else {
navController?.popViewController(animated: animated)
}
}
}
func popToRoomEntranceViewController() {
if let navController = navController {
var controllerArray = navController.viewControllers
controllerArray.reverse()
for vc in controllerArray {
if vc is PopUpViewController {
vc.dismiss(animated: true)
} else {
pop()
}
if vc is ConferenceMainViewController {
break
}
}
} else if let vc = context.rootViewController {
vc.dismiss(animated: true)
}
}
class func presentAlert(title: String?, message: String?, sureTitle:String?, declineTitle: String?, sureBlock: (() -> ())?, declineBlock: (() -> ())?, autoConfirmSeconds: Int? = nil) {
var timer: Timer?
var remainingSeconds = autoConfirmSeconds ?? 0
let alertVC = UIAlertController(title: title,
message: message,
preferredStyle: .alert)
if let declineTitle = declineTitle {
let declineAction = UIAlertAction(title: declineTitle, style: .destructive) { _ in
declineBlock?()
}
declineAction.setValue(UIColor(0x4F586B), forKey: "titleTextColor")
alertVC.addAction(declineAction)
}
let sureActionTitle = (autoConfirmSeconds != nil) ? "\(sureTitle ?? "") (\(remainingSeconds))" : sureTitle
let sureAction = UIAlertAction(title: sureActionTitle, style: .default) { _ in
sureBlock?()
timer?.invalidate()
}
alertVC.addAction(sureAction)
if autoConfirmSeconds != nil {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
remainingSeconds -= 1
if remainingSeconds <= 0 {
sureBlock?()
timer?.invalidate()
} else {
sureAction.setValue("\(sureTitle ?? "") (\(remainingSeconds))", forKey: "title")
}
}
}
shared.getCurrentWindowViewController()?.present(alertVC, animated: true)
let weakObserver = { [weak alertVC] in return alertVC }
shared.context.alterControllers.append(weakObserver)
}
func dismissAllAlertController(complete: @escaping (()->())) {
guard context.alterControllers.count > 0 else {
complete()
return
}
dismissAlertController(index: context.alterControllers.count - 1) { [weak self] in
guard let self = self else { return }
self.context.alterControllers = []
complete()
}
}
private func dismissAlertController(index: Int, complete: @escaping (()->())) {
if index < 0 {
complete()
return
}
if let observer = context.alterControllers[safe: index], let vc = observer() {
vc.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
self.dismissAlertController(index: index - 1, complete: complete)
}
} else {
dismissAlertController(index: index-1, complete: complete)
}
}
class func makeToast(toast: String) {
shared.getCurrentWindowViewController()?.view.makeToast(toast)
}
class func makeToastInCenter(toast: String, duration:TimeInterval) {
guard let windowView = shared.getCurrentWindowViewController()?.view else {return}
windowView.makeToast(toast,duration: duration,position:TUICSToastPositionCenter)
}
class func makeToastInWindow(toast: String, duration:TimeInterval) {
guard let window = RoomRouter.getCurrentWindow() else {return}
window.makeToast(toast,duration: duration,position:TUICSToastPositionCenter)
}
class func getCurrentWindow() -> UIWindow? {
var windows: [UIWindow]
if #available(iOS 13.0, *), let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
windows = windowScene.windows
} else {
windows = UIApplication.shared.windows
}
if let keyWindow = windows.first(where: { $0.isKeyWindow }) {
return keyWindow
} else {
return windows.last(where: { $0.windowLevel == .normal && $0.isHidden == false &&
CGRectEqualToRect($0.bounds , UIScreen.main.bounds) })
}
}
func initializeNavigationController(rootViewController: UIViewController) {
guard context.rootNavigation == nil else { return }
if let nav = rootViewController.navigationController {
context.rootNavigation = nav
}
context.rootViewController = rootViewController
}
@objc func handleDeviceOrientationChange() {
guard context.currentLandscape != isLandscape else { return }
destroyChatWindow()
context.currentLandscape = isLandscape
}
deinit {
unsubscribeEvent()
debugPrint("deinit \(self)")
}
}
extension RoomRouter {
func push(viewController: UIViewController, animated: Bool = true) {
guard let navController = navController else {
createRootNavigationAndPresent(controller: viewController)
return
}
navController.pushViewController(viewController, animated: animated)
}
func present(viewController: UIViewController, animated: Bool = true) {
if #available(iOS 13.0, *) {
viewController.modalPresentationStyle = .automatic
} else {
viewController.modalPresentationStyle = .overFullScreen
}
if let navController = navController {
navController.present(viewController, animated: animated)
} else if let vc = context.rootViewController {
vc.present(viewController, animated: true)
}
}
private func createRootNavigationAndPresent(controller: UIViewController) {
let navigationController = RoomKitNavigationController(rootViewController: controller)
navigationController.modalPresentationStyle = .fullScreen
context.rootNavigation = navigationController
if #available(iOS 13.0, *) {
setupNavigationBarAppearance()
if let appearance = context.appearance as? UINavigationBarAppearance {
navigationController.navigationBar.standardAppearance = appearance
navigationController.navigationBar.scrollEdgeAppearance = appearance
}
} else {
navigationController.navigationBar.shadowImage = UIImage()
navigationController.navigationBar.barStyle = .default
}
let weakObserver = { [weak navigationController] in
return navigationController
}
guard let controller = getCurrentWindowViewController() else { return }
controller.present(navigationController, animated: true)
}
@available(iOS 13.0, *)
private func setupNavigationBarAppearance() {
guard let barAppearance = context.appearance as? UINavigationBarAppearance else {
return
}
barAppearance.configureWithDefaultBackground()
barAppearance.shadowColor = nil
barAppearance.backgroundEffect = nil
barAppearance.backgroundColor = .white
}
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
}
private func makeMainViewController() -> UIViewController {
let controller = ConferenceMainViewController()
return controller
}
private func makePopUpViewController(viewType: PopUpViewType, height: CGFloat, backgroundColor: UIColor) -> PopUpViewController {
let controller = PopUpViewController(popUpViewModelFactory: self, viewType: viewType, height: height, backgroundColor: backgroundColor)
return controller
}
private func destroyChatWindow() {
guard context.chatWindow != nil else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.context.chatWindow = nil
}
}
}
extension RoomRouter.RoomNavigationDelegate: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if viewController is ConferenceMainViewController {
if #available(iOS 13.0, *) {
if let appearance = RoomRouter.shared.context.appearance as? UINavigationBarAppearance {
navigationController.navigationBar.standardAppearance = appearance
navigationController.navigationBar.scrollEdgeAppearance = appearance
}
}
}
}
}
extension RoomRouter: PopUpViewModelFactory {
func makeRootViewModel(viewType: PopUpViewType, height: CGFloat, backgroundColor: UIColor) -> PopUpViewModel {
let viewModel = PopUpViewModel(viewType: viewType, height: height)
viewModel.backgroundColor = backgroundColor
return viewModel
}
}
extension RoomRouter: RoomKitUIEventResponder {
func onNotifyUIEvent(key: EngineEventCenter.RoomUIEvent, Object: Any?, info: [AnyHashable : Any]?) {
switch key {
case .TUIRoomKitService_ShowRoomVideoFloatView:
dismissPopupViewController()
popToRoomEntranceViewController()
RoomVideoFloatView.show()
case .TUIRoomKitService_ShowRoomMainView:
RoomVideoFloatView.dismiss()
self.pushMainViewController()
case .TUIRoomKitService_HiddenChatWindow:
destroyChatWindow()
case .TUIRoomKitService_DismissConferenceViewController:
dismissAllAlertController() { [weak self] in
guard let self = self else { return }
self.dismissPopupViewController() { [weak self] in
guard let self = self else { return }
self.popToRoomEntranceViewController()
}
}
default: break
}
}
}
private extension String {
static var chatText: String {
localized("Chat")
}
}