This commit is contained in:
启星
2025-08-08 10:49:36 +08:00
parent 6400cf78bb
commit b5ce3d580a
8780 changed files with 978183 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
//
// ConferenceRouteDefine.swift
// TUIRoomKit
//
// Created by aby on 2024/6/21.
//
import Foundation
import RTCRoomEngine
import Factory
enum ConferenceRoute {
case none
case schedule(memberSelectFactory: MemberSelectionFactory?)
case main(conferenceParams: ConferenceParamType)
case selectMember(memberSelectParams: MemberSelectParams?)
case selectedMember(showDeleteButton: Bool, selectedMembers: [UserInfo])
case timeZone
case scheduleDetails(conferenceInfo: ConferenceInfo)
case modifySchedule(conferenceInfo: ConferenceInfo)
case popup(view: UIView)
case alert(state: AlertState)
case invitation(roomInfo: TUIRoomInfo, invitation: TUIInvitation)
func hideNavigationBar() -> Bool {
switch self {
case .main, .selectMember, .timeZone, .invitation:
return true
default:
return false
}
}
init(viewController: UIViewController) {
switch viewController {
case is ConferenceMainViewController:
let vc = viewController as? ConferenceMainViewController
if let startParams = vc?.startConferenceParams {
self = .main(conferenceParams: ConferenceParamType(startParams: startParams))
} else if let joinParams = vc?.joinConferenceParams {
self = .main(conferenceParams: ConferenceParamType(joinParams: joinParams))
} else {
self = .none
}
case is ScheduleConferenceViewController:
guard let vc = viewController as? ScheduleConferenceViewController else {
self = .none
break
}
self = .schedule(memberSelectFactory: vc.memberSelectionFactory)
case _ as ContactViewProtocol:
self = .selectMember(memberSelectParams: nil)
case is SelectedMembersViewController:
let vc = viewController as? SelectedMembersViewController
let showDeleteButton = vc?.showDeleteButton ?? true
let selectedMember = vc?.selectedMember ?? []
self = .selectedMember(showDeleteButton: showDeleteButton, selectedMembers: selectedMember)
case is TimeZoneViewController:
self = .timeZone
case is ScheduleDetailsViewController:
let vc = viewController as? ScheduleDetailsViewController
self = .scheduleDetails(conferenceInfo: vc?.conferenceInfo ?? ConferenceInfo())
case is ModifyScheduleViewController:
let vc = viewController as? ModifyScheduleViewController
self = .modifySchedule(conferenceInfo: vc?.conferenceInfo ?? ConferenceInfo())
case is ConferenceInvitationViewController:
let vc = viewController as? ConferenceInvitationViewController
self = .invitation(roomInfo: vc?.roomInfo ?? TUIRoomInfo(), invitation: vc?.invitation ?? TUIInvitation())
case is PopupViewController:
let vc = viewController as? PopupViewController
self = .popup(view: vc?.contentView ?? viewController.view)
case is UIAlertController:
let vc = viewController as? UIAlertController
let alertState = AlertState(title: vc?.title, message: vc?.message, sureAction: vc?.actions.first, declineAction: vc?.actions.last)
self = .alert(state: alertState)
default:
self = .none
}
}
}
extension ConferenceRoute {
var viewController: UIViewController {
switch self {
case .main(let params):
let vc = ConferenceMainViewController()
if case .startConferecneParams(let startConferenceParams) = params {
vc.setStartConferenceParams(params: startConferenceParams)
} else if case .joinConferenceParams(let joinConferenceParams) = params {
vc.setJoinConferenceParams(params: joinConferenceParams)
}
return vc
case .schedule(memberSelectFactory: let factory):
return ScheduleConferenceViewController(memberSelectFactory: factory)
case .selectMember(memberSelectParams: let memberSelectParams):
guard let params = memberSelectParams else {
return UIViewController()
}
let participants = params.participants
guard let vc = getSelectMemberViewController(participants: participants) else {
return UIViewController()
}
vc.delegate = params.delegate
return vc
case .selectedMember(showDeleteButton: let isShow, let selectedMembers):
return SelectedMembersViewController(showDeleteButton: isShow, selectedMembers: selectedMembers)
case .timeZone:
return TimeZoneViewController()
case .scheduleDetails(conferenceInfo: let info):
return ScheduleDetailsViewController(conferenceInfo: info)
case .modifySchedule(conferenceInfo: let info):
return ModifyScheduleViewController(conferenceInfo: info)
case .invitation(roomInfo: let info, invitation: let invitation):
return ConferenceInvitationViewController(roomInfo: info, invitation: invitation)
case .popup(view: let view):
return PopupViewController(contentView: view)
case .alert(state: let alertState):
let alertVC = UIAlertController(title: alertState.title,
message: alertState.message,
preferredStyle: .alert)
if let sureAction = alertState.sureAction {
alertVC.addAction(sureAction)
}
if let declineAction = alertState.declineAction {
alertVC.addAction(declineAction)
}
return alertVC
default:
return UIViewController()
}
}
func getSelectMemberViewController(participants: ConferenceParticipants) -> (ContactViewProtocol & UIViewController)? {
guard let vc = Container.shared.contactViewController(participants) as? (ContactViewProtocol & UIViewController) else {
return nil
}
return vc
}
}
extension ConferenceRoute: Equatable {
static func ==(lhs: ConferenceRoute, rhs: ConferenceRoute) -> Bool {
switch (lhs, rhs) {
case (.none, .none),
(.schedule, .schedule),
(.main, .main),
(.selectMember, .selectMember),
(.selectedMember, .selectedMember),
(.timeZone, .timeZone),
(.scheduleDetails, .scheduleDetails),
(.modifySchedule, .modifySchedule),
(.invitation, .invitation):
return true
case let (.popup(l), .popup(r)):
return l == r
case let (.alert(l), .alert(r)):
return l == r
case (.none, _),
(.schedule, _),
(.main, _),
(.selectMember, _),
(.selectedMember, _),
(.timeZone, _),
(.scheduleDetails, _),
(.modifySchedule, _),
(.alert, _),
(.popup, _),
(.invitation, _):
return false
}
}
}
struct AlertState: Equatable {
var title: String?
var message: String?
var sureAction: UIAlertAction?
var declineAction: UIAlertAction?
}
enum ConferenceParamType {
case startConferecneParams(StartConferenceParams)
case joinConferenceParams(JoinConferenceParams)
init(startParams: StartConferenceParams) {
self = .startConferecneParams(startParams)
}
init(joinParams: JoinConferenceParams) {
self = .joinConferenceParams(joinParams)
}
}
#if DEBUG
class PrintRouteInterceptor: Interceptor {
typealias State = ConferenceRouteState
func actionDispatched(action: any Action, oldState: State, newState: State) {
let name = String(describing: type(of: self))
let actionName = String(describing: type(of: action))
print("route action is: \(newState.currentRouteAction)")
switch newState.currentRouteAction {
case .presented(route: let route):
print("route is: \(oldState.currentRoute) to \(route)")
default:
break
}
}
}
#endif

View File

@@ -0,0 +1,43 @@
//
// ViewRouteState.swift
// TUIRoomKit
//
// Created by aby on 2024/6/19.
//
import Foundation
enum NavigationAction<ViewRoute>: Equatable where ViewRoute: Equatable {
case present(route: ViewRoute)
case push(route: ViewRoute)
case presented(route: ViewRoute)
}
typealias ConferenceNavigation = NavigationAction<ConferenceRoute>
struct ConferenceRouteState {
var currentRouteAction: ConferenceNavigation = .presented(route: .none)
var currentRoute: ConferenceRoute = .none
var memberSelectionFactory: MemberSelectionFactory?
}
enum ConferenceNavigationAction {
static let key = "conference.navigation.action"
static let navigate = ActionTemplate(id: key.appending("navigate"), payloadType: ConferenceNavigation.self)
static let setMemberSelectionFactory = ActionTemplate(id: key.appending(".setMemberSelectionFactory"), payloadType: MemberSelectionFactory.self)
}
let routeReducer = Reducer<ConferenceRouteState>(
ReduceOn(ConferenceNavigationAction.navigate, reduce: { state, action in
state.currentRouteAction = action.payload
switch action.payload {
case let .presented(route: route):
state.currentRoute = route
default:
break
}
}),
ReduceOn(ConferenceNavigationAction.setMemberSelectionFactory, reduce: { state, action in
state.memberSelectionFactory = action.payload
})
)

View File

@@ -0,0 +1,236 @@
//
// ConferenceRoute.swift
// TUIRoomKit
//
// Created by aby on 2024/6/19.
//
import Combine
protocol Route: ActionDispatcher {
func initializeRoute(viewController: UIViewController, rootRoute: ConferenceRoute)
func uninitialize()
func pushTo(route: ConferenceRoute)
func present(route: ConferenceRoute)
func dismiss(animated: Bool)
func pop()
func popTo(route: ConferenceRoute)
func pop(route: ConferenceRoute)
func showContactView(delegate: ContactViewSelectDelegate, participants: ConferenceParticipants)
}
private let currentRouteActionSelector = Selector(keyPath: \ConferenceRouteState.currentRouteAction)
private let memberSelectFactorySelector = Selector(keyPath: \ConferenceRouteState.memberSelectionFactory)
class ConferenceRouter: NSObject {
override init() {
super.init()
}
// MARK: - private property.
private var cancellableSet = Set<AnyCancellable>()
private weak var navigationController: UINavigationController?
private weak var rootViewController: UIViewController?
private weak var navigationControllerDelegate: UINavigationControllerDelegate?
private let store: Store<ConferenceRouteState, Void> = {
let store = Store.init(initialState: ConferenceRouteState(), reducers: [routeReducer])
#if DEBUG
store.register(interceptor: PrintRouteInterceptor())
#endif
return store
}()
}
extension ConferenceRouter: Route {
func initializeRoute(viewController: UIViewController, rootRoute: ConferenceRoute) {
guard self.rootViewController == nil else { return }
self.rootViewController = viewController
if let nav = viewController.navigationController {
self.navigationController = nav
if nav.delegate == nil {
nav.delegate = self
} else {
self.navigationControllerDelegate = nav.delegate
nav.delegate = self
}
} else {
let navigationController = UINavigationController.init(rootViewController: viewController)
self.navigationController = navigationController
}
store.dispatch(action: ConferenceNavigationAction.navigate(payload: ConferenceNavigation.presented(route: rootRoute)))
let navigationPublisher = store.select(currentRouteActionSelector)
subscribe(to: navigationPublisher)
}
func pushTo(route: ConferenceRoute) {
store.dispatch(action: ConferenceNavigationAction.navigate(payload: .push(route: route)))
}
func present(route: ConferenceRoute) {
store.dispatch(action: ConferenceNavigationAction.navigate(payload: .present(route: route)))
}
func dismiss(animated: Bool) {
guard let viewController = self.navigationController?.topViewController ?? self.navigationController else { return }
if let presentedViewController = viewController.presentedViewController {
presentedViewController.dismiss(animated: animated) {
[weak self] in
guard let self = self else { return }
if let rootVC = self.rootViewController {
let viewRoute = ConferenceRoute.init(viewController: rootVC)
self.store.dispatch(action: ConferenceNavigationAction.navigate(payload: ConferenceNavigation.presented(route: viewRoute)))
}
}
}
}
func pop() {
self.navigationController?.popViewController(animated: true)
}
func popTo(route: ConferenceRoute) {
guard let viewControllers = self.navigationController?.viewControllers else { return }
if let targetVC = viewControllers.first(where: { viewController in
let iterateRoute = ConferenceRoute(viewController: viewController)
return iterateRoute == route
}) {
self.navigationController?.popToViewController(targetVC, animated: false)
}
}
func pop(route: ConferenceRoute) {
guard var viewControllers = self.navigationController?.viewControllers else { return }
if let index = viewControllers.firstIndex(where: { viewController in
let iterateRoute = ConferenceRoute(viewController: viewController)
return iterateRoute == route
}) {
viewControllers.remove(at: index)
self.navigationController?.viewControllers = viewControllers
}
}
func uninitialize() {
if let delegate = self.navigationControllerDelegate {
self.navigationController?.delegate = delegate
}
}
func showContactView(delegate: ContactViewSelectDelegate, participants: ConferenceParticipants) {
guard let factory = store.selectCurrent(memberSelectFactorySelector) else { return }
let selectParams = MemberSelectParams(participants: participants, delegate: delegate, factory: factory)
store.dispatch(action: ConferenceNavigationAction.navigate(payload: .push(route: .selectMember(memberSelectParams: selectParams))))
}
func dispatch(action: Action) {
store.dispatch(action: action)
}
}
extension ConferenceRouter {
var viewController: UIViewController? {
return navigationController ?? rootViewController
}
}
extension ConferenceRouter {
private func subscribe(to navigationPublisher: AnyPublisher<ConferenceNavigation, Never>) {
navigationPublisher
.receive(on: RunLoop.main)
.removeDuplicates()
.sink { [weak self] action in
guard let self = self else { return }
self.respond(to: action)
}
.store(in: &cancellableSet)
}
private func respond(to action: ConferenceNavigation) {
switch action {
case let .present(route: route):
present(viewRoute: route)
case let .push(route: route):
push(viewRoute: route)
case .presented:
break
}
}
private func present(viewRoute: ConferenceRoute) {
guard let viewController = self.navigationController?.topViewController ?? self.navigationController else { return }
if viewController.presentedViewController != nil {
self.dismiss(animated: false)
}
var animated: Bool = true
if case .invitation = viewRoute {
animated = false
}
viewController.present(viewRoute.viewController, animated: animated) { [weak self] in
guard let self = self else { return }
self.store.dispatch(action: ConferenceNavigationAction.navigate(payload: ConferenceNavigation.presented(route: viewRoute)))
}
}
private func push(viewRoute: ConferenceRoute) {
guard let navigationController = self.navigationController else { return }
navigationController.pushViewController(viewRoute.viewController, animated: true)
}
}
extension ConferenceRouter {
func toggleNavigationBar(for view: ConferenceRoute, animated: Bool, navigationController: UINavigationController) {
if view.hideNavigationBar() {
hideNavigationBar(navigationController: navigationController, animated: animated)
}
else {
showNavigationBar(navigationController: navigationController, animated: animated)
}
}
func hideNavigationBar(navigationController: UINavigationController, animated: Bool) {
if animated {
navigationController.transitionCoordinator?.animate(alongsideTransition: {
context in
navigationController.setNavigationBarHidden(true, animated: true)
})
}
else {
navigationController.setNavigationBarHidden(true, animated: false)
}
}
func showNavigationBar(navigationController: UINavigationController, animated: Bool) {
if navigationController.isNavigationBarHidden {
if animated {
navigationController.transitionCoordinator?.animate(alongsideTransition: {
context in
navigationController.setNavigationBarHidden(false, animated: true)
})
}
else {
navigationController.setNavigationBarHidden(false, animated: false)
}
}
}
}
extension ConferenceRouter: UINavigationControllerDelegate {
/// Animate the navigation bar display with view controller transition.
func navigationController(
_ navigationController: UINavigationController,
willShow viewController: UIViewController,
animated: Bool) {
let destRoute = ConferenceRoute.init(viewController: viewController)
guard destRoute != .none else { return }
toggleNavigationBar(for: destRoute, animated: animated, navigationController: navigationController)
}
/// Trigger a `ConferenceNavigationAction` event according to the destination view type.
func navigationController(
_ navigationController: UINavigationController,
didShow viewController: UIViewController,
animated: Bool) {
let destRoute = ConferenceRoute.init(viewController: viewController)
guard destRoute != .none else { return }
self.store.dispatch(action: ConferenceNavigationAction.navigate(payload: ConferenceNavigation.presented(route: destRoute)))
}
}

View File

@@ -0,0 +1,16 @@
//
// Navigation+Injection.swift
// TUIRoomKit
//
// Created by aby on 2024/6/20.
//
import Factory
extension Container {
var navigation: Factory<Route> {
Factory(self) {
ConferenceRouter()
}
.shared
}
}

View File

@@ -0,0 +1,103 @@
//
// PopupViewController.swift
// TUIRoomKit
//
// Created by aby on 2024/6/26.
//
import UIKit
import Factory
class PopupViewController: UIViewController {
let contentView: UIView
private let visualEffectView: UIView = {
let blurEffect = UIBlurEffect(style: .dark)
let view = UIVisualEffectView(effect: blurEffect)
view.frame = UIScreen.main.bounds
view.alpha = 0
return view
}()
public init(contentView: UIView) {
self.contentView = contentView
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .custom
transitioningDelegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
constructViewHierarchy()
activateConstraints()
}
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
func constructViewHierarchy() {
view.addSubview(contentView)
}
func activateConstraints() {
contentView.snp.remakeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else { return }
let point = touch.location(in: contentView)
guard !contentView.layer.contains(point) else { return }
route.dismiss(animated: true)
}
// MARK: - private property.
@Injected(\.navigation) private var route: Route
}
extension PopupViewController: UIViewControllerTransitioningDelegate {
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) ->
UIViewControllerAnimatedTransitioning? {
let transitionAnimator = AlertTransitionAnimator()
transitionAnimator.alertTransitionStyle = .present
if interfaceOrientation.isPortrait {
transitionAnimator.alertTransitionPosition = .bottom
} else {
transitionAnimator.alertTransitionPosition = .right
}
source.view.addSubview(visualEffectView)
UIView.animate(withDuration: transitionAnimator.duration) { [weak self] in
guard let self = self else { return }
self.visualEffectView.alpha = 1
}
return transitionAnimator
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitionAnimator = AlertTransitionAnimator()
transitionAnimator.alertTransitionStyle = .dismiss
if interfaceOrientation.isPortrait {
transitionAnimator.alertTransitionPosition = .bottom
} else {
transitionAnimator.alertTransitionPosition = .right
}
UIView.animate(withDuration: transitionAnimator.duration) { [weak self] in
guard let self = self else { return }
self.visualEffectView.alpha = 0
} completion: { [weak self] finished in
guard let self = self else { return }
if finished {
self.visualEffectView.removeFromSuperview()
}
}
return transitionAnimator
}
}