Extract delegate parts from GroupCallVC

This commit is contained in:
Marissa Le Coz 2023-11-30 16:21:07 -05:00 committed by GitHub
parent ff87f958f5
commit c2a76513f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 152 additions and 109 deletions

View File

@ -27,7 +27,7 @@ class CallAudioService: NSObject, CallObserver {
}
// Track whether the speaker should be enabled or not.
private var isSpeakerEnabled = false
private(set) var isSpeakerEnabled = false
// MARK: Vibration config
private let vibrateRepeatDuration = 1.6

View File

@ -9,13 +9,9 @@ import SignalUI
@objc
protocol CallControlsDelegate: AnyObject {
func didPressHangup(sender: UIButton)
func didPressAudioSource(sender: UIButton)
func didPressMute(sender: UIButton)
func didPressVideo(sender: UIButton)
func didPressRing(sender: UIButton)
func didPressFlipCamera(sender: UIButton)
func didPressJoin(sender: UIButton)
func didPressRing()
func didPressJoin()
func didPressHangup()
}
class CallControls: UIView {
@ -24,7 +20,7 @@ class CallControls: UIView {
let button = createButton(
iconName: "phone-down-fill-28",
accessibilityLabel: viewModel.hangUpButtonAccessibilityLabel,
action: #selector(CallControlsDelegate.didPressHangup)
action: #selector(CallControlsViewModel.didPressHangup)
)
button.unselectedBackgroundColor = .ows_accentRed
return button
@ -32,28 +28,28 @@ class CallControls: UIView {
private(set) lazy var audioSourceButton = createButton(
iconName: "speaker-fill-28",
accessibilityLabel: viewModel.audioSourceAccessibilityLabel,
action: #selector(CallControlsDelegate.didPressAudioSource)
action: #selector(CallControlsViewModel.didPressAudioSource)
)
private lazy var muteButton = createButton(
iconName: "mic-slash-fill-28",
accessibilityLabel: viewModel.muteButtonAccessibilityLabel,
action: #selector(CallControlsDelegate.didPressMute)
action: #selector(CallControlsViewModel.didPressMute)
)
private lazy var videoButton = createButton(
iconName: "video-fill-28",
accessibilityLabel: viewModel.videoButtonAccessibilityLabel,
action: #selector(CallControlsDelegate.didPressVideo)
action: #selector(CallControlsViewModel.didPressVideo)
)
private lazy var ringButton = createButton(
iconName: "bell-ring-fill-28",
// TODO: Accessibility label
action: #selector(CallControlsDelegate.didPressRing)
action: #selector(CallControlsViewModel.didPressRing)
)
private lazy var flipCameraButton: CallButton = {
let button = createButton(
iconName: "switch-camera-28",
accessibilityLabel: viewModel.flipCameraButtonAccessibilityLabel,
action: #selector(CallControlsDelegate.didPressFlipCamera)
action: #selector(CallControlsViewModel.didPressFlipCamera)
)
button.selectedIconColor = button.iconColor
button.selectedBackgroundColor = button.unselectedBackgroundColor
@ -72,7 +68,7 @@ class CallControls: UIView {
button.clipsToBounds = true
button.layer.cornerRadius = height / 2
button.block = { [weak self, unowned button] in
self?.delegate.didPressJoin(sender: button)
self?.viewModel.didPressJoin()
}
button.contentEdgeInsets = UIEdgeInsets(top: 17, leading: 17, bottom: 17, trailing: 17)
button.addSubview(joinButtonActivityIndicator)
@ -105,7 +101,8 @@ class CallControls: UIView {
callService: CallService,
delegate: CallControlsDelegate
) {
self.viewModel = CallControlsViewModel(call: call, callService: callService)
let viewModel = CallControlsViewModel(call: call, callService: callService, delegate: delegate)
self.viewModel = viewModel
self.delegate = delegate
super.init(frame: .zero)
@ -183,6 +180,9 @@ class CallControls: UIView {
videoButton.isSelected = viewModel.videoButtonIsSelected
muteButton.isSelected = viewModel.muteButtonIsSelected
audioSourceButton.isSelected = viewModel.audioSourceButtonIsSelected
ringButton.isSelected = viewModel.ringButtonIsSelected
flipCameraButton.isSelected = viewModel.flipCameraButtonIsSelected
if !viewModel.audioSourceButtonIsHidden {
let config = viewModel.audioSourceButtonConfiguration
@ -226,7 +226,7 @@ class CallControls: UIView {
private func createButton(iconName: String, accessibilityLabel: String? = nil, action: Selector) -> CallButton {
let button = CallButton(iconName: iconName)
button.accessibilityLabel = accessibilityLabel
button.addTarget(delegate, action: action, for: .touchUpInside)
button.addTarget(viewModel, action: action, for: .touchUpInside)
button.setContentHuggingHorizontalHigh()
button.setCompressionResistanceHorizontalLow()
button.alpha = 0.9
@ -237,10 +237,12 @@ class CallControls: UIView {
private class CallControlsViewModel {
private let call: SignalCall
private let callService: CallService
private weak var delegate: CallControlsDelegate?
fileprivate var refreshView: (() -> Void)?
init(call: SignalCall, callService: CallService) {
init(call: SignalCall, callService: CallService, delegate: CallControlsDelegate) {
self.call = call
self.callService = callService
self.delegate = delegate
call.addObserverAndSyncState(observer: self)
callService.audioService.delegate = self
}
@ -440,6 +442,27 @@ private class CallControlsViewModel {
return call.isOutgoingAudioMuted
}
var ringButtonIsSelected: Bool {
// Ring button currently only used for group calls.
switch call.groupCallRingState {
case .doNotRing:
return false
case .shouldRing:
return true
default:
// Ring button shouldn't be displayed in any other cases anyway
return false
}
}
var audioSourceButtonIsSelected: Bool {
return callService.audioService.isSpeakerEnabled
}
var flipCameraButtonIsSelected: Bool {
return false
}
func controlSpacing(controlCount: Int) -> CGFloat {
return (UIDevice.current.isNarrowerThanIPhone6 && controlCount > 4) ? 12 : 16
}
@ -502,6 +525,63 @@ extension CallControlsViewModel: CallAudioServiceDelegate {
}
}
extension CallControlsViewModel {
@objc
func didPressHangup() {
callService.callUIAdapter.localHangupCall(call)
delegate?.didPressHangup()
}
@objc
func didPressAudioSource() {
if callService.audioService.hasExternalInputs {
callService.audioService.presentRoutePicker()
} else {
callService.audioService.requestSpeakerphone(call: self.call, isEnabled: !audioSourceButtonIsSelected)
}
refreshView?()
}
@objc
func didPressMute() {
callService.updateIsLocalAudioMuted(isLocalAudioMuted: !muteButtonIsSelected)
refreshView?()
}
@objc
func didPressVideo() {
callService.updateIsLocalVideoMuted(isLocalVideoMuted: videoButtonIsSelected)
// When turning off video, default speakerphone to on.
if !videoButtonIsSelected && !callService.audioService.hasExternalInputs {
callService.audioService.requestSpeakerphone(call: self.call, isEnabled: true)
}
refreshView?()
}
@objc
func didPressRing() {
if call.ringRestrictions.isEmpty {
call.groupCallRingState = !ringButtonIsSelected ? .shouldRing : .doNotRing
refreshView?()
}
delegate?.didPressRing()
}
@objc
func didPressFlipCamera() {
if let isUsingFrontCamera = call.videoCaptureController.isUsingFrontCamera {
callService.updateCameraSource(call: call, isUsingFrontCamera: !isUsingFrontCamera)
refreshView?()
}
}
@objc
func didPressJoin() {
delegate?.didPressJoin()
}
}
// MARK: - Accessibility
extension CallControlsViewModel {

View File

@ -539,11 +539,14 @@ class GroupCallViewController: UIViewController {
updateSwipeToastView()
}
func dismissCall(shouldHangUp: Bool = true) {
private func dismissCall(shouldHangUp: Bool = true) {
if shouldHangUp {
callService.callUIAdapter.localHangupCall(call)
}
didHangupCall()
}
func didHangupCall() {
guard !hasDismissed else {
return
}
@ -862,96 +865,6 @@ extension GroupCallViewController: CallObserver {
}
}
extension GroupCallViewController: CallControlsDelegate {
func didPressHangup(sender: UIButton) {
dismissCall()
}
func didPressAudioSource(sender: UIButton) {
if callService.audioService.hasExternalInputs {
callService.audioService.presentRoutePicker()
} else {
sender.isSelected = !sender.isSelected
callService.audioService.requestSpeakerphone(call: self.call, isEnabled: sender.isSelected)
}
}
func didPressMute(sender: UIButton) {
sender.isSelected = !sender.isSelected
callService.updateIsLocalAudioMuted(isLocalAudioMuted: sender.isSelected)
}
func didPressVideo(sender: UIButton) {
sender.isSelected = !sender.isSelected
callService.updateIsLocalVideoMuted(isLocalVideoMuted: !sender.isSelected)
// When turning off video, default speakerphone to on.
if !sender.isSelected && !callService.audioService.hasExternalInputs {
callControls.audioSourceButton.isSelected = true
callService.audioService.requestSpeakerphone(call: self.call, isEnabled: true)
}
}
func didPressRing(sender: UIButton) {
if call.ringRestrictions.isEmpty {
let oldShouldRing = sender.isSelected
let newShouldRing = !oldShouldRing
sender.isSelected = newShouldRing
call.groupCallRingState = newShouldRing ? .shouldRing : .doNotRing
// Refresh the call header.
callHeader.groupCallLocalDeviceStateChanged(call)
} else {
if call.ringRestrictions.intersects([.notApplicable, .callInProgress]) {
owsFailDebug("should not show the ring button at all")
} else if call.ringRestrictions.contains(.groupTooLarge) {
let toast = ToastController(text: OWSLocalizedString("GROUP_CALL_TOO_LARGE_TO_RING", comment: "Text displayed when trying to turn on ringing when calling a large group."))
toast.presentToastView(from: .top, of: view, inset: view.safeAreaInsets.top + 8)
} else {
owsAssertDebug(call.ringRestrictions.isEmpty, "unknown ring restriction")
}
}
}
func didPressFlipCamera(sender: UIButton) {
sender.isSelected = !sender.isSelected
callService.updateCameraSource(call: call, isUsingFrontCamera: !sender.isSelected)
}
func didPressJoin(sender: UIButton) {
guard !call.groupCall.isFull else {
let text: String
if let maxDevices = call.groupCall.maxDevices {
let formatString = OWSLocalizedString("GROUP_CALL_HAS_MAX_DEVICES_%d", tableName: "PluralAware",
comment: "An error displayed to the user when the group call ends because it has exceeded the max devices. Embeds {{max device count}}."
)
text = String.localizedStringWithFormat(formatString, maxDevices)
} else {
text = OWSLocalizedString(
"GROUP_CALL_HAS_MAX_DEVICES_UNKNOWN_COUNT",
comment: "An error displayed to the user when the group call ends because it has exceeded the max devices."
)
}
let toastController = ToastController(text: text)
// Leave the toast up longer than usual because this message is pretty long.
toastController.presentToastView(from: .top,
of: view,
inset: view.safeAreaInsets.top + 8,
dismissAfter: .seconds(8))
return
}
presentSafetyNumberChangeSheetIfNecessary { [weak self] success in
guard let self = self else { return }
if success {
self.callService.joinGroupCallIfNecessary(self.call)
}
}
}
}
extension GroupCallViewController: IncomingCallControlsDelegate {
func didDeclineIncomingCall() {
dismissCall()
@ -1014,6 +927,56 @@ extension GroupCallViewController: UIScrollViewDelegate {
}
}
extension GroupCallViewController: CallControlsDelegate {
func didPressRing() {
if call.ringRestrictions.isEmpty {
// Refresh the call header.
callHeader.groupCallLocalDeviceStateChanged(call)
} else if call.ringRestrictions.contains(.groupTooLarge) {
let toast = ToastController(text: OWSLocalizedString("GROUP_CALL_TOO_LARGE_TO_RING", comment: "Text displayed when trying to turn on ringing when calling a large group."))
toast.presentToastView(from: .top, of: view, inset: view.safeAreaInsets.top + 8)
}
}
func didPressJoin() {
guard call.canJoin else {
let text: String
if let maxDevices = call.groupCall.maxDevices {
let formatString = OWSLocalizedString("GROUP_CALL_HAS_MAX_DEVICES_%d", tableName: "PluralAware",
comment: "An error displayed to the user when the group call ends because it has exceeded the max devices. Embeds {{max device count}}."
)
text = String.localizedStringWithFormat(formatString, maxDevices)
} else {
text = OWSLocalizedString(
"GROUP_CALL_HAS_MAX_DEVICES_UNKNOWN_COUNT",
comment: "An error displayed to the user when the group call ends because it has exceeded the max devices."
)
}
let toastController = ToastController(text: text)
// Leave the toast up longer than usual because this message is pretty long.
toastController.presentToastView(
from: .top,
of: view,
inset: view.safeAreaInsets.top + 8,
dismissAfter: .seconds(8)
)
return
}
presentSafetyNumberChangeSheetIfNecessary { [weak self] success in
guard let self = self else { return }
if success {
self.callService.joinGroupCallIfNecessary(self.call)
}
}
}
func didPressHangup() {
didHangupCall()
}
}
extension GroupCallViewController: GroupCallMemberViewDelegate {
func memberView(_ view: GroupCallMemberView, userRequestedInfoAboutError error: GroupCallMemberView.ErrorState) {
let title: String