AzureCommunicationUI/sdk/AzureCommunicationUICalling/Sources/Presentation/Factories/CompositeViewModelFactory.swift (470 lines of code) (raw):

// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // import FluentUI import Foundation import SwiftUI // swiftlint:disable file_length class CompositeViewModelFactory: CompositeViewModelFactoryProtocol { private let logger: Logger private let store: Store<AppState, Action> private let networkManager: NetworkManager private let audioSessionManager: AudioSessionManagerProtocol private let accessibilityProvider: AccessibilityProviderProtocol private let localizationProvider: LocalizationProviderProtocol private let debugInfoManager: DebugInfoManagerProtocol private let captionsRttDataManager: CaptionsRttDataManager private let events: CallComposite.Events private let localOptions: LocalOptions? private let enableMultitasking: Bool private let enableSystemPipWhenMultitasking: Bool private let capabilitiesManager: CapabilitiesManager private let avatarManager: AvatarViewManagerProtocol private let retrieveLogFiles: () -> [URL] private weak var setupViewModel: SetupViewModel? private weak var callingViewModel: CallingViewModel? private let setupScreenOptions: SetupScreenOptions? private let callScreenOptions: CallScreenOptions? private let callType: CompositeCallType private let themeOptions: ThemeOptions private let updatableOptionsManager: UpdatableOptionsManagerProtocol init(logger: Logger, store: Store<AppState, Action>, networkManager: NetworkManager, audioSessionManager: AudioSessionManagerProtocol, localizationProvider: LocalizationProviderProtocol, accessibilityProvider: AccessibilityProviderProtocol, debugInfoManager: DebugInfoManagerProtocol, captionsRttDataManager: CaptionsRttDataManager, localOptions: LocalOptions? = nil, enableMultitasking: Bool, enableSystemPipWhenMultitasking: Bool, eventsHandler: CallComposite.Events, leaveCallConfirmationMode: LeaveCallConfirmationMode, callType: CompositeCallType, setupScreenOptions: SetupScreenOptions?, callScreenOptions: CallScreenOptions?, capabilitiesManager: CapabilitiesManager, avatarManager: AvatarViewManagerProtocol, themeOptions: ThemeOptions, updatableOptionsManager: UpdatableOptionsManagerProtocol, retrieveLogFiles: @escaping () -> [URL] ) { self.logger = logger self.store = store self.networkManager = networkManager self.audioSessionManager = audioSessionManager self.accessibilityProvider = accessibilityProvider self.localizationProvider = localizationProvider self.debugInfoManager = debugInfoManager self.captionsRttDataManager = captionsRttDataManager self.events = eventsHandler self.localOptions = localOptions self.enableMultitasking = enableMultitasking self.enableSystemPipWhenMultitasking = enableSystemPipWhenMultitasking self.retrieveLogFiles = retrieveLogFiles self.setupScreenOptions = setupScreenOptions self.callScreenOptions = callScreenOptions self.capabilitiesManager = capabilitiesManager self.callType = callType self.themeOptions = themeOptions self.avatarManager = avatarManager self.updatableOptionsManager = updatableOptionsManager } func makeLeaveCallConfirmationViewModel( endCall: @escaping (() -> Void), dismissConfirmation: @escaping (() -> Void)) -> LeaveCallConfirmationViewModel { return LeaveCallConfirmationViewModel( state: store.state, localizationProvider: localizationProvider, endCall: endCall, dismissConfirmation: dismissConfirmation) } func makeSupportFormViewModel() -> SupportFormViewModel { return SupportFormViewModel( isDisplayed: store.state.navigationState.supportFormVisible && store.state.visibilityState.currentStatus == .visible, dispatchAction: store.dispatch, events: events, localizationProvider: localizationProvider, getDebugInfo: { [self] in self.debugInfoManager.getDebugInfo() }) } // MARK: CompositeViewModels func getSetupViewModel() -> SetupViewModel { guard let viewModel = self.setupViewModel else { let viewModel = SetupViewModel(compositeViewModelFactory: self, logger: logger, store: store, networkManager: networkManager, audioSessionManager: audioSessionManager, localizationProvider: localizationProvider, setupScreenViewData: localOptions?.setupScreenViewData, callType: callType) self.setupViewModel = viewModel self.callingViewModel = nil return viewModel } return viewModel } func getCallingViewModel(rendererViewManager: RendererViewManager) -> CallingViewModel { guard let viewModel = self.callingViewModel else { let viewModel = CallingViewModel(compositeViewModelFactory: self, store: store, localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, isIpadInterface: UIDevice.current.userInterfaceIdiom == .pad, allowLocalCameraPreview: localOptions?.audioVideoMode != CallCompositeAudioVideoMode.audioOnly, callType: callType, captionsOptions: localOptions?.captionsOptions ?? CaptionsOptions(), capabilitiesManager: self.capabilitiesManager, callScreenOptions: callScreenOptions ?? CallScreenOptions(), rendererViewManager: rendererViewManager) self.setupViewModel = nil self.callingViewModel = viewModel return viewModel } return viewModel } // MARK: ComponentViewModels func makeIconButtonViewModel(iconName: CompositeIcon, buttonType: IconButtonViewModel.ButtonType = .controlButton, isDisabled: Bool, action: @escaping (() -> Void)) -> IconButtonViewModel { IconButtonViewModel(iconName: iconName, buttonType: buttonType, isDisabled: isDisabled, action: action) } func makeIconButtonViewModel(iconName: CompositeIcon, buttonType: IconButtonViewModel.ButtonType = .controlButton, isDisabled: Bool, isVisible: Bool, action: @escaping (() -> Void)) -> IconButtonViewModel { IconButtonViewModel(iconName: iconName, buttonType: buttonType, isDisabled: isDisabled, isVisible: isVisible, action: action) } func makeIconButtonViewModel(icon: UIImage, buttonType: IconButtonViewModel.ButtonType = .controlButton, isDisabled: Bool, isVisible: Bool, action: @escaping (() -> Void)) -> IconButtonViewModel { IconButtonViewModel(icon: icon, buttonType: buttonType, isDisabled: isDisabled, isVisible: isVisible, action: action) } func makeIconWithLabelButtonViewModel<T: ButtonState>( selectedButtonState: T, localizationProvider: LocalizationProviderProtocol, buttonTypeColor: IconWithLabelButtonViewModel<T>.ButtonTypeColor, isDisabled: Bool, action: @escaping (() -> Void)) -> IconWithLabelButtonViewModel<T> { IconWithLabelButtonViewModel( selectedButtonState: selectedButtonState, localizationProvider: localizationProvider, buttonTypeColor: buttonTypeColor, isDisabled: isDisabled, action: action) } func makeLocalVideoViewModel(dispatchAction: @escaping ActionDispatch) -> LocalVideoViewModel { LocalVideoViewModel(compositeViewModelFactory: self, logger: logger, localizationProvider: localizationProvider, dispatchAction: dispatchAction) } func makePrimaryButtonViewModel(buttonStyle: FluentUI.ButtonStyle, buttonLabel: String, iconName: CompositeIcon?, isDisabled: Bool = false, paddings: CompositeButton.Paddings? = nil, action: @escaping (() -> Void)) -> PrimaryButtonViewModel { PrimaryButtonViewModel(buttonStyle: buttonStyle, buttonLabel: buttonLabel, iconName: iconName, isDisabled: isDisabled, paddings: paddings, themeOptions: themeOptions, action: action) } func makeAudioDevicesListViewModel(dispatchAction: @escaping ActionDispatch, localUserState: LocalUserState) -> AudioDevicesListViewModel { AudioDevicesListViewModel(compositeViewModelFactory: self, dispatchAction: dispatchAction, localUserState: localUserState, localizationProvider: localizationProvider) } func makeCaptionsLanguageListViewModel(dispatchAction: @escaping ActionDispatch, state: AppState ) -> CaptionsLanguageListViewModel { CaptionsLanguageListViewModel(compositeViewModelFactory: self, dispatchAction: dispatchAction, state: state, localizationProvider: localizationProvider) } func makeCaptionsRttListViewModel(state: AppState, captionsOptions: CaptionsOptions, dispatchAction: @escaping ActionDispatch, buttonActions: ButtonActions, isDisplayed: Bool) -> CaptionsRttListViewModel { return CaptionsRttListViewModel(compositeViewModelFactory: self, localizationProvider: localizationProvider, captionsOptions: captionsOptions, state: state, dispatchAction: dispatchAction, buttonActions: buttonActions, isDisplayed: store.state.navigationState.captionsRttViewVisible && store.state.visibilityState.currentStatus == .visible) } func makeCaptionsRttInfoViewModel(state: AppState, captionsOptions: CaptionsOptions) -> CaptionsRttInfoViewModel { return CaptionsRttInfoViewModel(state: state, captionsManager: captionsRttDataManager, captionsOptions: captionsOptions, dispatch: store.dispatch, localizationProvider: localizationProvider) } func makeCaptionsErrorViewModel(dispatchAction: @escaping ActionDispatch) -> CaptionsErrorViewModel { return CaptionsErrorViewModel(compositeViewModelFactory: self, logger: logger, localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, dispatchAction: dispatchAction) } func makeCaptionsLangaugeCellViewModel(title: String, isSelected: Bool, accessibilityLabel: String, onSelectedAction: @escaping (() -> Void)) -> DrawerSelectableItemViewModel { return DrawerSelectableItemViewModel(icon: nil, title: title, accessibilityIdentifier: "", accessibilityLabel: accessibilityLabel, isSelected: isSelected, action: onSelectedAction) } func makeErrorInfoViewModel(title: String, subtitle: String) -> ErrorInfoViewModel { ErrorInfoViewModel(localizationProvider: localizationProvider, title: title, subtitle: subtitle) } } extension CompositeViewModelFactory { func makeCallDiagnosticsViewModel(dispatchAction: @escaping ActionDispatch) -> CallDiagnosticsViewModel { CallDiagnosticsViewModel(localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, dispatchAction: dispatchAction) } // MARK: CallingViewModels func makeLobbyOverlayViewModel() -> LobbyOverlayViewModel { LobbyOverlayViewModel(localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider) } func makeLoadingOverlayViewModel() -> LoadingOverlayViewModel { LoadingOverlayViewModel(localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, networkManager: networkManager, audioSessionManager: audioSessionManager, themeOptions: themeOptions, store: store, callType: callType) } func makeOnHoldOverlayViewModel(resumeAction: @escaping (() -> Void)) -> OnHoldOverlayViewModel { OnHoldOverlayViewModel(localizationProvider: localizationProvider, compositeViewModelFactory: self, logger: logger, accessibilityProvider: accessibilityProvider, audioSessionManager: audioSessionManager, resumeAction: resumeAction) } func makeControlBarViewModel(dispatchAction: @escaping ActionDispatch, onEndCallTapped: @escaping (() -> Void), localUserState: LocalUserState, capabilitiesManager: CapabilitiesManager, buttonViewDataState: ButtonViewDataState) -> ControlBarViewModel { ControlBarViewModel(compositeViewModelFactory: self, logger: logger, localizationProvider: localizationProvider, dispatchAction: dispatchAction, onEndCallTapped: onEndCallTapped, localUserState: localUserState, accessibilityProvider: accessibilityProvider, audioVideoMode: localOptions?.audioVideoMode ?? .audioAndVideo, capabilitiesManager: capabilitiesManager, controlBarOptions: callScreenOptions?.controlBarOptions, buttonViewDataState: buttonViewDataState) } func makeInfoHeaderViewModel(dispatchAction: @escaping ActionDispatch, localUserState: LocalUserState, callScreenInfoHeaderState: CallScreenInfoHeaderState, buttonViewDataState: ButtonViewDataState, controlHeaderViewData: CallScreenHeaderViewData? ) -> InfoHeaderViewModel { InfoHeaderViewModel(compositeViewModelFactory: self, logger: logger, localUserState: localUserState, localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, dispatchAction: dispatchAction, enableMultitasking: enableMultitasking, enableSystemPipWhenMultitasking: enableSystemPipWhenMultitasking, callScreenInfoHeaderState: callScreenInfoHeaderState, buttonViewDataState: buttonViewDataState, controlHeaderViewData: controlHeaderViewData ) } func makeLobbyWaitingHeaderViewModel(localUserState: LocalUserState, dispatchAction: @escaping ActionDispatch) -> LobbyWaitingHeaderViewModel { LobbyWaitingHeaderViewModel(compositeViewModelFactory: self, logger: logger, localUserState: localUserState, localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, dispatchAction: dispatchAction) } func makeLobbyActionErrorViewModel(localUserState: LocalUserState, dispatchAction: @escaping ActionDispatch) -> LobbyErrorHeaderViewModel { LobbyErrorHeaderViewModel(compositeViewModelFactory: self, logger: logger, localUserState: localUserState, localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, dispatchAction: dispatchAction) } func makeParticipantCellViewModel(participantModel: ParticipantInfoModel) -> ParticipantGridCellViewModel { ParticipantGridCellViewModel(localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, participantModel: participantModel, isCameraEnabled: localOptions?.audioVideoMode != .audioOnly, callType: callType) } func makeParticipantGridsViewModel(isIpadInterface: Bool, rendererViewManager: RendererViewManager) -> ParticipantGridViewModel { ParticipantGridViewModel(compositeViewModelFactory: self, localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, isIpadInterface: isIpadInterface, callType: callType, rendererViewManager: rendererViewManager) } func makeParticipantsListViewModel(localUserState: LocalUserState, isDisplayed: Bool, dispatchAction: @escaping ActionDispatch) -> ParticipantsListViewModel { ParticipantsListViewModel(compositeViewModelFactory: self, localUserState: localUserState, dispatchAction: dispatchAction, localizationProvider: localizationProvider, onUserClicked: { participant in dispatchAction(Action.showParticipantActions(participant)) }, avatarManager: avatarManager) } func makeParticipantMenuViewModel(localUserState: LocalUserState, isDisplayed: Bool, dispatchAction: @escaping ActionDispatch) -> ParticipantMenuViewModel { ParticipantMenuViewModel(compositeViewModelFactory: self, localUserState: localUserState, localizationProvider: localizationProvider, capabilitiesManager: capabilitiesManager, onRemoveUser: { user in dispatchAction(.remoteParticipantsAction(.remove(participantId: user.userIdentifier))) dispatchAction(.hideDrawer) }, isDisplayed: isDisplayed) } func makeBannerViewModel(dispatchAction: @escaping ActionDispatch) -> BannerViewModel { BannerViewModel(compositeViewModelFactory: self, dispatchAction: dispatchAction) } func makeBannerTextViewModel() -> BannerTextViewModel { BannerTextViewModel(accessibilityProvider: accessibilityProvider, localizationProvider: localizationProvider) } func makeMoreCallOptionsListViewModel( isCaptionsAvailable: Bool, buttonActions: ButtonActions, controlBarOptions: CallScreenControlBarOptions?, buttonViewDataState: ButtonViewDataState, dispatchAction: @escaping ActionDispatch) -> MoreCallOptionsListViewModel { // events.onUserReportedIssue return MoreCallOptionsListViewModel(compositeViewModelFactory: self, localizationProvider: localizationProvider, buttonActions: buttonActions, controlBarOptions: controlBarOptions, isCaptionsAvailable: isCaptionsAvailable, isSupportFormAvailable: events.onUserReportedIssue != nil, buttonViewDataState: buttonViewDataState, dispatchAction: dispatchAction) } func makeLanguageListItemViewModel(title: String, subtitle: String?, accessibilityIdentifier: String, startIcon: CompositeIcon, endIcon: CompositeIcon?, isEnabled: Bool, action: @escaping (() -> Void)) -> DrawerGenericItemViewModel { DrawerGenericItemViewModel(title: title, subtitle: subtitle, accessibilityIdentifier: accessibilityIdentifier, accessibilityTraits: .isButton, action: action, startCompositeIcon: startIcon, endIcon: endIcon, isEnabled: isEnabled) } func makeToggleListItemViewModel(title: String, isToggleOn: Binding<Bool>, showToggle: Bool, accessibilityIdentifier: String, startIcon: CompositeIcon, isEnabled: Bool, action: @escaping (() -> Void)) -> DrawerGenericItemViewModel { DrawerGenericItemViewModel(title: title, accessibilityIdentifier: accessibilityIdentifier, action: action, startCompositeIcon: startIcon, showToggle: showToggle, isToggleOn: isToggleOn, isEnabled: isEnabled) } func makeDrawerListItemViewModel(icon: CompositeIcon, title: String, accessibilityIdentifier: String) -> DrawerGenericItemViewModel { DrawerGenericItemViewModel(title: title, accessibilityIdentifier: accessibilityIdentifier, action: nil, startCompositeIcon: icon) } func makeDebugInfoSharingActivityViewModel() -> DebugInfoSharingActivityViewModel { DebugInfoSharingActivityViewModel(accessibilityProvider: accessibilityProvider, debugInfoManager: debugInfoManager) { self.store.dispatch(action: .hideDrawer) } } func makeBottomToastViewModel(toastNotificationState: ToastNotificationState, dispatchAction: @escaping ActionDispatch) -> BottomToastViewModel { BottomToastViewModel(dispatchAction: dispatchAction, localizationProvider: localizationProvider, accessibilityProvider: accessibilityProvider, toastNotificationState: toastNotificationState) } // MARK: SetupViewModels func makePreviewAreaViewModel(dispatchAction: @escaping ActionDispatch) -> PreviewAreaViewModel { PreviewAreaViewModel(compositeViewModelFactory: self, dispatchAction: dispatchAction, localizationProvider: localizationProvider) } func makeSetupControlBarViewModel(dispatchAction: @escaping ActionDispatch, localUserState: LocalUserState, buttonViewDataState: ButtonViewDataState) -> SetupControlBarViewModel { let audioVideoMode = localOptions?.audioVideoMode ?? CallCompositeAudioVideoMode.audioAndVideo return SetupControlBarViewModel(compositeViewModelFactory: self, logger: logger, dispatchAction: dispatchAction, updatableOptionsManager: updatableOptionsManager, localUserState: localUserState, localizationProvider: localizationProvider, audioVideoMode: audioVideoMode, setupScreenOptions: setupScreenOptions, buttonViewDataState: buttonViewDataState) } func makeJoiningCallActivityViewModel() -> JoiningCallActivityViewModel { JoiningCallActivityViewModel(title: self.localizationProvider.getLocalizedString(LocalizationKey.joiningCall)) } }