AzureCommunicationUI/sdk/AzureCommunicationUICalling/Sources/Presentation/SwiftUI/Setup/SetupViewComponent/SetupControlBarViewModel.swift (210 lines of code) (raw):

// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // import Combine import Foundation class SetupControlBarViewModel: ObservableObject { @Published var cameraPermission: AppPermission.Status = .unknown @Published var audioPermission: AppPermission.Status = .unknown @Published var isAudioDeviceSelectionDisplayed = false @Published var isCameraButtonVisible = true @Published var isMicButtonVisible = true @Published var isAudioDeviceButtonVisible = true private let logger: Logger private let dispatch: ActionDispatch private let localizationProvider: LocalizationProviderProtocol private let audioVideoMode: CallCompositeAudioVideoMode private var buttonViewDataState: ButtonViewDataState private var isJoinRequested = false private var isDefaultUserStateMapped = false private var callingStatus: CallingStatus = .none private var cameraStatus: LocalUserState.CameraOperationalStatus = .off private(set) var micStatus: LocalUserState.AudioOperationalStatus = .off private var localVideoStreamId: String? private(set) var cameraButtonViewModel: IconWithLabelButtonViewModel<CameraButtonState>! private(set) var micButtonViewModel: IconWithLabelButtonViewModel<MicButtonState>! private(set) var audioDeviceButtonViewModel: IconWithLabelButtonViewModel<AudioButtonState>! init(compositeViewModelFactory: CompositeViewModelFactoryProtocol, logger: Logger, dispatchAction: @escaping ActionDispatch, updatableOptionsManager: UpdatableOptionsManagerProtocol, localUserState: LocalUserState, localizationProvider: LocalizationProviderProtocol, audioVideoMode: CallCompositeAudioVideoMode, setupScreenOptions: SetupScreenOptions?, buttonViewDataState: ButtonViewDataState ) { self.logger = logger self.dispatch = dispatchAction self.localizationProvider = localizationProvider self.audioVideoMode = audioVideoMode self.buttonViewDataState = buttonViewDataState cameraButtonViewModel = compositeViewModelFactory.makeIconWithLabelButtonViewModel( selectedButtonState: CameraButtonState.videoOff, localizationProvider: self.localizationProvider, buttonTypeColor: .colorThemedWhite, isDisabled: isCameraDisabled()) { [weak self] in guard let self = self else { return } self.callCustomOnClickHandler(updatableOptionsManager.setupScreenOptions?.cameraButton) self.logger.debug("Toggle camera button tapped") self.videoButtonTapped() } cameraButtonViewModel.accessibilityLabel = self.localizationProvider.getLocalizedString( .videoOffAccessibilityLabel) micButtonViewModel = compositeViewModelFactory.makeIconWithLabelButtonViewModel( selectedButtonState: MicButtonState.micOff, localizationProvider: self.localizationProvider, buttonTypeColor: .colorThemedWhite, isDisabled: isMicButtonDisabled()) { [weak self] in guard let self = self else { return } self.callCustomOnClickHandler(updatableOptionsManager.setupScreenOptions?.microphoneButton) self.logger.debug("Toggle microphone button tapped") self.microphoneButtonTapped() } micButtonViewModel.accessibilityLabel = self.localizationProvider.getLocalizedString(.micOffAccessibilityLabel) audioDeviceButtonViewModel = compositeViewModelFactory.makeIconWithLabelButtonViewModel( selectedButtonState: AudioButtonState.speaker, localizationProvider: self.localizationProvider, buttonTypeColor: .colorThemedWhite, isDisabled: isAudioDeviceButtonDisabled()) { [weak self] in guard let self = self else { return } self.callCustomOnClickHandler(updatableOptionsManager.setupScreenOptions?.audioDeviceButton) self.logger.debug("Select audio device button tapped") self.selectAudioDeviceButtonTapped() } audioDeviceButtonViewModel.accessibilityLabel = self.localizationProvider.getLocalizedString( .deviceAccesibiiltyLabel) isCameraButtonVisible = shouldCameraButtonBeVisible(audioVideoMode, buttonViewDataState) isMicButtonVisible = shouldMicButtonBeVisible(buttonViewDataState) isAudioDeviceButtonVisible = shouldsAudioDeviceButtonBeVisible(buttonViewDataState) } private func callCustomOnClickHandler(_ button: ButtonViewData?) { guard let button = button else { return } button.onClick?(button) } func videoButtonTapped() { let isPreview = callingStatus == .none let isCameraOn = cameraStatus == .on switch (isCameraOn, isPreview) { case (false, true): dispatch(.localUserAction(.cameraPreviewOnTriggered)) case (false, false): dispatch(.localUserAction(.cameraOnTriggered)) case (true, _): dispatch(.localUserAction(.cameraOffTriggered)) } } func microphoneButtonTapped() { let isPreview = callingStatus == .none let isMicOn = micStatus == .on switch (isMicOn, isPreview) { case (false, true): dispatch(.localUserAction(.microphonePreviewOn)) case (false, false): dispatch(.localUserAction(.microphoneOnTriggered)) case (true, true): dispatch(.localUserAction(.microphonePreviewOff)) case (true, false): dispatch(.localUserAction(.microphoneOffTriggered)) } } func selectAudioDeviceButtonTapped() { dispatch(.showAudioSelection) isAudioDeviceSelectionDisplayed = true } func isCameraDisabled() -> Bool { return buttonViewDataState.setupScreenCameraButtonState?.enabled == false || isJoinRequested || cameraPermission == .denied } func isMicButtonDisabled() -> Bool { return buttonViewDataState.setupScreenMicButtonState?.enabled == false || isJoinRequested || audioPermission == .denied } func isAudioDeviceButtonDisabled() -> Bool { return buttonViewDataState.setupScreenAudioDeviceButtonState?.enabled == false || isJoinRequested } func isControlBarHidden() -> Bool { return audioPermission == .denied } func update(localUserState: LocalUserState, permissionState: PermissionState, callingState: CallingState, buttonViewDataState: ButtonViewDataState) { self.buttonViewDataState = buttonViewDataState if cameraPermission != permissionState.cameraPermission { cameraPermission = permissionState.cameraPermission } if audioPermission != permissionState.audioPermission { audioPermission = permissionState.audioPermission } callingStatus = callingState.status cameraStatus = localUserState.cameraState.operation micStatus = localUserState.audioState.operation updateButtonViewModel(localUserState: localUserState, buttonViewDataState: buttonViewDataState) if localVideoStreamId != localUserState.localVideoStreamIdentifier { localVideoStreamId = localUserState.localVideoStreamIdentifier updateButtonTypeColor(isLocalVideoOff: localVideoStreamId == nil) } isCameraButtonVisible = shouldCameraButtonBeVisible(audioVideoMode, buttonViewDataState) isMicButtonVisible = shouldMicButtonBeVisible(buttonViewDataState) isAudioDeviceButtonVisible = shouldsAudioDeviceButtonBeVisible(buttonViewDataState) } func update(isJoinRequested: Bool) { self.isJoinRequested = isJoinRequested cameraButtonViewModel.update(isDisabled: isCameraDisabled()) micButtonViewModel.update(isDisabled: isMicButtonDisabled()) } private func updateButtonViewModel(localUserState: LocalUserState, buttonViewDataState: ButtonViewDataState) { cameraButtonViewModel.update( selectedButtonState: cameraStatus == .on ? CameraButtonState.videoOn : CameraButtonState.videoOff) cameraButtonViewModel.update(accessibilityLabel: cameraStatus == .on ? localizationProvider.getLocalizedString(.videoOnAccessibilityLabel) : localizationProvider.getLocalizedString(.videoOffAccessibilityLabel)) cameraButtonViewModel.update(isDisabled: isCameraDisabled()) micButtonViewModel.update( selectedButtonState: micStatus == .on ? MicButtonState.micOn : MicButtonState.micOff) micButtonViewModel.update(accessibilityLabel: micStatus == .on ? localizationProvider.getLocalizedString(.micOnAccessibilityLabel) : localizationProvider.getLocalizedString(.micOffAccessibilityLabel)) micButtonViewModel.update(isDisabled: isMicButtonDisabled()) let audioDeviceStatus = localUserState.audioState.device audioDeviceButtonViewModel.update(isDisabled: isAudioDeviceButtonDisabled()) audioDeviceButtonViewModel.update( selectedButtonState: AudioButtonState.getButtonState(from: audioDeviceStatus)) audioDeviceButtonViewModel.update( accessibilityValue: audioDeviceStatus.getLabel(localizationProvider: localizationProvider)) } private func updateButtonTypeColor(isLocalVideoOff: Bool) { let cameraButtonTypeColor: IconWithLabelButtonViewModel<CameraButtonState>.ButtonTypeColor = isLocalVideoOff ? .colorThemedWhite : .white let micButtonTypeColor: IconWithLabelButtonViewModel<MicButtonState>.ButtonTypeColor = isLocalVideoOff ? .colorThemedWhite : .white let audioButtonTypeColor: IconWithLabelButtonViewModel<AudioButtonState>.ButtonTypeColor = isLocalVideoOff ? .colorThemedWhite : .white cameraButtonViewModel.update(buttonTypeColor: cameraButtonTypeColor) micButtonViewModel.update(buttonTypeColor: micButtonTypeColor) audioDeviceButtonViewModel.update(buttonTypeColor: audioButtonTypeColor) } private func shouldCameraButtonBeVisible( _ audioVideoMode: CallCompositeAudioVideoMode, _ buttonViewDataState: ButtonViewDataState) -> Bool { return buttonViewDataState.setupScreenCameraButtonState?.visible ?? true && audioVideoMode != .audioOnly } private func shouldMicButtonBeVisible( _ buttonViewDataState: ButtonViewDataState) -> Bool { return buttonViewDataState.setupScreenMicButtonState?.visible ?? true } private func shouldsAudioDeviceButtonBeVisible( _ buttonViewDataState: ButtonViewDataState) -> Bool { return buttonViewDataState.setupScreenAudioDeviceButtonState?.visible ?? true } }