Add individual call support to group call controls view
This commit is contained in:
parent
1ce1dcc9f1
commit
1e258907c9
@ -194,6 +194,34 @@ public class IndividualCall: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// `JoinState` is a group call concept, but we want to bridge
|
||||
/// between the two call types.
|
||||
/// TODO: Continue to tweak this as we unify the individual and
|
||||
/// group call UIs.
|
||||
var joinState: JoinState {
|
||||
switch self.state {
|
||||
case .idle,
|
||||
.remoteHangup,
|
||||
.remoteHangupNeedPermission,
|
||||
.localHangup,
|
||||
.remoteRinging,
|
||||
.localRinging_Anticipatory,
|
||||
.localRinging_ReadyToAnswer,
|
||||
.remoteBusy,
|
||||
.localFailure,
|
||||
.busyElsewhere,
|
||||
.answeredElsewhere,
|
||||
.declinedElsewhere:
|
||||
return .notJoined
|
||||
case .connected,
|
||||
.accepting,
|
||||
.answering,
|
||||
.reconnecting,
|
||||
.dialing:
|
||||
return .joined
|
||||
}
|
||||
}
|
||||
|
||||
public var offerMediaType: TSRecentCallOfferType = .audio
|
||||
|
||||
// We start out muted if the record permission isn't granted. This should generally
|
||||
|
||||
@ -119,6 +119,27 @@ public class SignalCall: NSObject, CallManagerCallReference {
|
||||
}
|
||||
}
|
||||
|
||||
public var isOutgoingVideoMuted: Bool {
|
||||
switch mode {
|
||||
case .individual(let call): return !call.hasLocalVideo
|
||||
case .group(let call): return call.isOutgoingVideoMuted
|
||||
}
|
||||
}
|
||||
|
||||
public var joinState: JoinState {
|
||||
switch mode {
|
||||
case .individual(let call): return call.joinState
|
||||
case .group(let call): return call.localDeviceState.joinState
|
||||
}
|
||||
}
|
||||
|
||||
public var canJoin: Bool {
|
||||
switch mode {
|
||||
case .individual(_): return true
|
||||
case .group(let call): return !call.isFull
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the remote party for an incoming 1:1 call, or the ringer for a group call ring.
|
||||
///
|
||||
/// Returns `nil` for an outgoing 1:1 call, a manually-entered group call,
|
||||
@ -165,8 +186,16 @@ public class SignalCall: NSObject, CallManagerCallReference {
|
||||
internal var ringRestrictions: RingRestrictions {
|
||||
didSet {
|
||||
AssertIsOnMainThread()
|
||||
if ringRestrictions != oldValue && groupCall.localDeviceState.joinState == .notJoined {
|
||||
if
|
||||
isGroupCall,
|
||||
ringRestrictions != oldValue,
|
||||
joinState == .notJoined
|
||||
{
|
||||
// Use a fake local state change to refresh the call controls.
|
||||
//
|
||||
// If we ever introduce ringing restrictions for 1:1 calls,
|
||||
// a similar affordance will be needed to refresh the call
|
||||
// controls.
|
||||
self.groupCall(onLocalDeviceStateChanged: groupCall)
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ protocol CallControlsDelegate: AnyObject {
|
||||
}
|
||||
|
||||
class CallControls: UIView {
|
||||
private lazy var topStackView = createTopStackView()
|
||||
private lazy var hangUpButton: CallButton = {
|
||||
let button = createButton(
|
||||
iconName: "phone-down-fill-28",
|
||||
@ -90,19 +91,21 @@ class CallControls: UIView {
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var topStackView = createTopStackView()
|
||||
|
||||
private weak var delegate: CallControlsDelegate!
|
||||
private let call: SignalCall
|
||||
private let viewModel: CallControlsViewModel
|
||||
|
||||
init(call: SignalCall, delegate: CallControlsDelegate) {
|
||||
self.call = call
|
||||
init(
|
||||
call: SignalCall,
|
||||
callService: CallService,
|
||||
delegate: CallControlsDelegate
|
||||
) {
|
||||
self.viewModel = CallControlsViewModel(call: call, callService: callService)
|
||||
self.delegate = delegate
|
||||
super.init(frame: .zero)
|
||||
|
||||
call.addObserverAndSyncState(observer: self)
|
||||
|
||||
callService.audioService.delegate = self
|
||||
viewModel.refreshView = { [weak self] in
|
||||
self?.updateControls()
|
||||
}
|
||||
|
||||
addSubview(gradientView)
|
||||
gradientView.autoPinEdgesToSuperviewEdges()
|
||||
@ -113,7 +116,10 @@ class CallControls: UIView {
|
||||
joinButton.autoPinWidthToSuperviewMargins(relation: .lessThanOrEqual)
|
||||
joinButton.autoPinHeightToSuperview()
|
||||
|
||||
let controlsStack = UIStackView(arrangedSubviews: [topStackView, joinButtonContainer])
|
||||
let controlsStack = UIStackView(arrangedSubviews: [
|
||||
topStackView,
|
||||
joinButtonContainer
|
||||
])
|
||||
controlsStack.axis = .vertical
|
||||
controlsStack.spacing = 40
|
||||
controlsStack.alignment = .center
|
||||
@ -129,11 +135,6 @@ class CallControls: UIView {
|
||||
updateControls()
|
||||
}
|
||||
|
||||
deinit {
|
||||
call.removeObserver(self)
|
||||
callService.audioService.delegate = nil
|
||||
}
|
||||
|
||||
func createTopStackView() -> UIStackView {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .horizontal
|
||||
@ -150,99 +151,59 @@ class CallControls: UIView {
|
||||
}
|
||||
|
||||
private func updateControls() {
|
||||
let hasExternalAudioInputs = callService.audioService.hasExternalInputs
|
||||
let isLocalVideoMuted = call.groupCall.isOutgoingVideoMuted
|
||||
let joinState = call.groupCall.localDeviceState.joinState
|
||||
// Top row
|
||||
audioSourceButton.isHidden = viewModel.audioSourceButtonIsHidden
|
||||
hangUpButton.isHidden = viewModel.hangUpButtonIsHidden
|
||||
muteButton.isHidden = viewModel.muteButtonIsHidden
|
||||
videoButton.isHidden = viewModel.videoButtonIsHidden
|
||||
flipCameraButton.isHidden = viewModel.flipCameraButtonIsHidden
|
||||
ringButton.isHidden = viewModel.ringButtonIsHidden
|
||||
|
||||
flipCameraButton.isHidden = isLocalVideoMuted
|
||||
videoButton.isSelected = !isLocalVideoMuted
|
||||
muteButton.isSelected = call.groupCall.isOutgoingAudioMuted
|
||||
// Bottom row
|
||||
joinButton.superview?.isHidden = viewModel.joinButtonIsHidden
|
||||
|
||||
ringButton.isHidden = joinState == .joined || call.ringRestrictions.intersects([.notApplicable, .callInProgress])
|
||||
// Leave the button visible but locked if joining, like the "join call" button.
|
||||
ringButton.isUserInteractionEnabled = joinState == .notJoined
|
||||
if call.ringRestrictions.isEmpty, case .shouldRing = call.groupCallRingState {
|
||||
ringButton.isSelected = true
|
||||
} else {
|
||||
ringButton.isSelected = false
|
||||
}
|
||||
// Leave the button enabled so we can present an explanatory toast, but show it disabled.
|
||||
ringButton.shouldDrawAsDisabled = !call.ringRestrictions.isEmpty
|
||||
|
||||
hangUpButton.isHidden = joinState != .joined
|
||||
|
||||
if !UIDevice.current.isIPad {
|
||||
// Use small controls if video is enabled and we have external
|
||||
// audio inputs, because we have five buttons now.
|
||||
[audioSourceButton, flipCameraButton, videoButton, muteButton, ringButton, hangUpButton].forEach {
|
||||
let isSmall = hasExternalAudioInputs && !isLocalVideoMuted
|
||||
$0.isSmall = isSmall
|
||||
if UIDevice.current.isNarrowerThanIPhone6 {
|
||||
topStackView.spacing = isSmall ? 12 : 16
|
||||
}
|
||||
// Sizing and spacing
|
||||
let controlCount = topStackView.arrangedSubviews.filter({!$0.isHidden}).count
|
||||
topStackView.spacing = viewModel.controlSpacing(controlCount: controlCount)
|
||||
let shouldControlButtonsBeSmall = viewModel.shouldControlButtonsBeSmall(controlCount: controlCount)
|
||||
for view in topStackView.arrangedSubviews {
|
||||
if let button = view as? CallButton {
|
||||
button.isSmall = shouldControlButtonsBeSmall
|
||||
}
|
||||
}
|
||||
|
||||
// Audio Source Handling
|
||||
if hasExternalAudioInputs, let audioSource = callService.audioService.currentAudioSource {
|
||||
audioSourceButton.showDropdownArrow = true
|
||||
audioSourceButton.isHidden = false
|
||||
|
||||
if audioSource.isBuiltInEarPiece {
|
||||
audioSourceButton.iconName = "phone-fill-28"
|
||||
} else if audioSource.isBuiltInSpeaker {
|
||||
audioSourceButton.iconName = "speaker-fill-28"
|
||||
} else {
|
||||
audioSourceButton.iconName = "speaker-bt-fill-28"
|
||||
}
|
||||
} else if UIDevice.current.isIPad {
|
||||
// iPad *only* supports speaker mode, if there are no external
|
||||
// devices connected, so we don't need to show the button unless
|
||||
// we have alternate audio sources.
|
||||
audioSourceButton.isHidden = true
|
||||
} else {
|
||||
// If there are no external audio sources, and video is enabled,
|
||||
// speaker mode is always enabled so we don't need to show the button.
|
||||
audioSourceButton.isHidden = !isLocalVideoMuted
|
||||
|
||||
// No bluetooth audio detected
|
||||
audioSourceButton.iconName = "speaker-fill-28"
|
||||
audioSourceButton.showDropdownArrow = false
|
||||
}
|
||||
|
||||
// Show/hide the superview to adjust the containing stack.
|
||||
joinButton.superview?.isHidden = joinState == .joined
|
||||
gradientView.isHidden = joinState != .joined
|
||||
gradientView.isHidden = viewModel.gradientViewIsHidden
|
||||
|
||||
if call.groupCall.isFull {
|
||||
// Make the button look disabled, but don't actually disable it.
|
||||
// We want to show a toast if the user taps anyway.
|
||||
joinButton.setTitleColor(.ows_whiteAlpha40, for: .normal)
|
||||
joinButton.adjustsImageWhenHighlighted = false
|
||||
videoButton.isSelected = viewModel.videoButtonIsSelected
|
||||
muteButton.isSelected = viewModel.muteButtonIsSelected
|
||||
|
||||
joinButton.setTitle(
|
||||
OWSLocalizedString(
|
||||
"GROUP_CALL_IS_FULL",
|
||||
comment: "Text explaining the group call is full"),
|
||||
for: .normal)
|
||||
if !viewModel.audioSourceButtonIsHidden {
|
||||
let config = viewModel.audioSourceButtonConfiguration
|
||||
audioSourceButton.showDropdownArrow = config.showDropdownArrow
|
||||
audioSourceButton.iconName = config.iconName
|
||||
}
|
||||
|
||||
} else if joinState == .joining || joinState == .pending {
|
||||
joinButton.isUserInteractionEnabled = false
|
||||
joinButtonActivityIndicator.startAnimating()
|
||||
if
|
||||
!viewModel.ringButtonIsHidden,
|
||||
let ringButtonConfig = viewModel.ringButtonConfiguration
|
||||
{
|
||||
ringButton.isUserInteractionEnabled = ringButtonConfig.isUserInteractionEnabled
|
||||
ringButton.isSelected = ringButtonConfig.isSelected
|
||||
ringButton.shouldDrawAsDisabled = ringButtonConfig.shouldDrawAsDisabled
|
||||
}
|
||||
|
||||
joinButton.setTitle("", for: .normal)
|
||||
|
||||
} else {
|
||||
joinButton.setTitleColor(.white, for: .normal)
|
||||
joinButton.adjustsImageWhenHighlighted = true
|
||||
joinButton.isUserInteractionEnabled = true
|
||||
joinButtonActivityIndicator.stopAnimating()
|
||||
|
||||
let startCallText = OWSLocalizedString("GROUP_CALL_START_BUTTON", comment: "Button to start a group call")
|
||||
let joinCallText = OWSLocalizedString("GROUP_CALL_JOIN_BUTTON", comment: "Button to join an ongoing group call")
|
||||
|
||||
joinButton.setTitle(call.ringRestrictions.contains(.callInProgress) ? joinCallText : startCallText,
|
||||
for: .normal)
|
||||
if !viewModel.joinButtonIsHidden {
|
||||
let joinButtonConfig = viewModel.joinButtonConfig
|
||||
joinButton.setTitle(joinButtonConfig.label, for: .normal)
|
||||
joinButton.setTitleColor(joinButtonConfig.color, for: .normal)
|
||||
joinButton.adjustsImageWhenHighlighted = joinButtonConfig.adjustsImageWhenHighlighted
|
||||
joinButton.isUserInteractionEnabled = joinButtonConfig.isUserInteractionEnabled
|
||||
if viewModel.shouldJoinButtonActivityIndicatorBeAnimating {
|
||||
joinButtonActivityIndicator.startAnimating()
|
||||
} else {
|
||||
joinButtonActivityIndicator.stopAnimating()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,31 +221,270 @@ class CallControls: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
extension CallControls: CallObserver {
|
||||
private class CallControlsViewModel {
|
||||
private let call: SignalCall
|
||||
private let callService: CallService
|
||||
fileprivate var refreshView: (() -> Void)?
|
||||
init(call: SignalCall, callService: CallService) {
|
||||
self.call = call
|
||||
self.callService = callService
|
||||
call.addObserverAndSyncState(observer: self)
|
||||
callService.audioService.delegate = self
|
||||
}
|
||||
|
||||
deinit {
|
||||
call.removeObserver(self)
|
||||
callService.audioService.delegate = nil
|
||||
}
|
||||
|
||||
private var hasExternalAudioInputsAndAudioSource: Bool {
|
||||
let audioService = callService.audioService
|
||||
return audioService.hasExternalInputs && audioService.currentAudioSource != nil
|
||||
}
|
||||
|
||||
var audioSourceButtonIsHidden: Bool {
|
||||
if hasExternalAudioInputsAndAudioSource {
|
||||
return false
|
||||
} else if UIDevice.current.isIPad {
|
||||
// iPad *only* supports speaker mode, if there are no external
|
||||
// devices connected, so we don't need to show the button unless
|
||||
// we have alternate audio sources.
|
||||
return true
|
||||
} else {
|
||||
return !call.isOutgoingVideoMuted
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioSourceButtonConfiguration {
|
||||
let showDropdownArrow: Bool
|
||||
let iconName: String
|
||||
}
|
||||
|
||||
var audioSourceButtonConfiguration: AudioSourceButtonConfiguration {
|
||||
let showDropdownArrow: Bool
|
||||
let iconName: String
|
||||
if
|
||||
callService.audioService.hasExternalInputs,
|
||||
let audioSource = callService.audioService.currentAudioSource
|
||||
{
|
||||
showDropdownArrow = true
|
||||
if audioSource.isBuiltInEarPiece {
|
||||
iconName = "phone-fill-28"
|
||||
} else if audioSource.isBuiltInSpeaker {
|
||||
iconName = "speaker-fill-28"
|
||||
} else {
|
||||
iconName = "speaker-bt-fill-28"
|
||||
}
|
||||
} else {
|
||||
// No bluetooth audio detected
|
||||
showDropdownArrow = false
|
||||
iconName = "speaker-fill-28"
|
||||
}
|
||||
return AudioSourceButtonConfiguration(showDropdownArrow: showDropdownArrow, iconName: iconName)
|
||||
}
|
||||
|
||||
var hangUpButtonIsHidden: Bool {
|
||||
switch call.mode {
|
||||
case .individual(_):
|
||||
return false
|
||||
case .group(_):
|
||||
return call.joinState != .joined
|
||||
}
|
||||
}
|
||||
|
||||
var muteButtonIsHidden: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var videoButtonIsHidden: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var flipCameraButtonIsHidden: Bool {
|
||||
return call.isOutgoingVideoMuted
|
||||
}
|
||||
|
||||
var joinButtonIsHidden: Bool {
|
||||
switch call.mode {
|
||||
case .individual(_):
|
||||
// TODO: Introduce lobby for starting 1:1 video calls.
|
||||
return true
|
||||
case .group(let call):
|
||||
return call.localDeviceState.joinState == .joined
|
||||
}
|
||||
}
|
||||
|
||||
struct JoinButtonConfiguration {
|
||||
let label: String
|
||||
let color: UIColor
|
||||
let adjustsImageWhenHighlighted: Bool
|
||||
let isUserInteractionEnabled: Bool
|
||||
}
|
||||
|
||||
var joinButtonConfig: JoinButtonConfiguration {
|
||||
if !call.canJoin {
|
||||
// Make the button look disabled, but don't actually disable it.
|
||||
// We want to show a toast if the user taps anyway.
|
||||
return JoinButtonConfiguration(
|
||||
label: OWSLocalizedString(
|
||||
"GROUP_CALL_IS_FULL",
|
||||
comment: "Text explaining the group call is full"
|
||||
),
|
||||
color: .ows_whiteAlpha40,
|
||||
adjustsImageWhenHighlighted: false,
|
||||
isUserInteractionEnabled: true
|
||||
)
|
||||
} else if call.joinState == .joining || call.joinState == .pending {
|
||||
return JoinButtonConfiguration(
|
||||
label: "",
|
||||
color: .ows_whiteAlpha40,
|
||||
adjustsImageWhenHighlighted: false,
|
||||
isUserInteractionEnabled: false
|
||||
)
|
||||
} else {
|
||||
let startCallText = OWSLocalizedString(
|
||||
"CALL_START_BUTTON",
|
||||
comment: "Button to start a call"
|
||||
)
|
||||
let label: String
|
||||
switch call.mode {
|
||||
case .individual(_):
|
||||
// We only show a lobby for 1:1 calls when the call is being initiated.
|
||||
// TODO: The work of adding the lobby for 1:1 calls in the unified call view
|
||||
// controller (currently GroupCallViewController) is not yet complete.
|
||||
label = startCallText
|
||||
case .group(_):
|
||||
let joinCallText = OWSLocalizedString(
|
||||
"GROUP_CALL_JOIN_BUTTON",
|
||||
comment: "Button to join an ongoing group call"
|
||||
)
|
||||
label = call.ringRestrictions.contains(.callInProgress) ? joinCallText : startCallText
|
||||
}
|
||||
return JoinButtonConfiguration(
|
||||
label: label,
|
||||
color: .white,
|
||||
adjustsImageWhenHighlighted: true,
|
||||
isUserInteractionEnabled: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var shouldJoinButtonActivityIndicatorBeAnimating: Bool {
|
||||
return (call.joinState == .joining || call.joinState == .pending) && !joinButtonIsHidden
|
||||
}
|
||||
|
||||
var ringButtonIsHidden: Bool {
|
||||
switch call.mode {
|
||||
case .individual(_):
|
||||
return true
|
||||
case .group(_):
|
||||
return call.joinState == .joined || call.ringRestrictions.intersects([.notApplicable, .callInProgress])
|
||||
}
|
||||
}
|
||||
|
||||
struct RingButtonConfiguration {
|
||||
let isUserInteractionEnabled: Bool
|
||||
let isSelected: Bool
|
||||
let shouldDrawAsDisabled: Bool
|
||||
}
|
||||
|
||||
var ringButtonConfiguration: RingButtonConfiguration? {
|
||||
switch call.mode {
|
||||
case .individual(_):
|
||||
// We never show the ring button for 1:1 calls.
|
||||
return nil
|
||||
case .group(_):
|
||||
// Leave the button visible but locked if joining, like the "join call" button.
|
||||
let isUserInteractionEnabled = call.joinState == .notJoined
|
||||
let isSelected: Bool
|
||||
if
|
||||
call.ringRestrictions.isEmpty,
|
||||
case .shouldRing = call.groupCallRingState
|
||||
{
|
||||
isSelected = true
|
||||
} else {
|
||||
isSelected = false
|
||||
}
|
||||
// Leave the button enabled so we can present an explanatory toast, but show it disabled.
|
||||
let shouldDrawAsDisabled = !call.ringRestrictions.isEmpty
|
||||
return RingButtonConfiguration(
|
||||
isUserInteractionEnabled: isUserInteractionEnabled,
|
||||
isSelected: isSelected,
|
||||
shouldDrawAsDisabled: shouldDrawAsDisabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var gradientViewIsHidden: Bool {
|
||||
return call.joinState != .joined
|
||||
}
|
||||
|
||||
var videoButtonIsSelected: Bool {
|
||||
return !call.isOutgoingVideoMuted
|
||||
}
|
||||
|
||||
var muteButtonIsSelected: Bool {
|
||||
return call.isOutgoingAudioMuted
|
||||
}
|
||||
|
||||
func controlSpacing(controlCount: Int) -> CGFloat {
|
||||
return (UIDevice.current.isNarrowerThanIPhone6 && controlCount > 4) ? 12 : 16
|
||||
}
|
||||
|
||||
func shouldControlButtonsBeSmall(controlCount: Int) -> Bool {
|
||||
return UIDevice.current.isIPad ? false : controlCount > 4
|
||||
}
|
||||
}
|
||||
|
||||
extension CallControlsViewModel: CallObserver {
|
||||
func groupCallLocalDeviceStateChanged(_ call: SignalCall) {
|
||||
owsAssertDebug(call.isGroupCall)
|
||||
updateControls()
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func groupCallPeekChanged(_ call: SignalCall) {
|
||||
updateControls()
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func groupCallRemoteDeviceStatesChanged(_ call: SignalCall) {
|
||||
updateControls()
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func groupCallEnded(_ call: SignalCall, reason: GroupCallEndReason) {
|
||||
updateControls()
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func individualCallStateDidChange(_ call: SignalCall, state: CallState) {
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func individualCallLocalVideoMuteDidChange(_ call: SignalCall, isVideoMuted: Bool) {
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func individualCallLocalAudioMuteDidChange(_ call: SignalCall, isAudioMuted: Bool) {
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func individualCallHoldDidChange(_ call: SignalCall, isOnHold: Bool) {
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func individualCallRemoteVideoMuteDidChange(_ call: SignalCall, isVideoMuted: Bool) {
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func individualCallRemoteSharingScreenDidChange(_ call: SignalCall, isRemoteSharingScreen: Bool) {
|
||||
refreshView?()
|
||||
}
|
||||
}
|
||||
|
||||
extension CallControls: CallAudioServiceDelegate {
|
||||
extension CallControlsViewModel: CallAudioServiceDelegate {
|
||||
func callAudioServiceDidChangeAudioSession(_ callAudioService: CallAudioService) {
|
||||
updateControls()
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
func callAudioServiceDidChangeAudioSource(_ callAudioService: CallAudioService, audioSource: AudioSource?) {
|
||||
updateControls()
|
||||
refreshView?()
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import SignalUI
|
||||
class GroupCallViewController: UIViewController {
|
||||
private let call: SignalCall
|
||||
private var groupCall: GroupCall { call.groupCall }
|
||||
private lazy var callControls = CallControls(call: call, delegate: self)
|
||||
private lazy var callControls = CallControls(call: call, callService: callService, delegate: self)
|
||||
private lazy var incomingCallControls = IncomingCallControls(video: true, delegate: self)
|
||||
private var incomingCallControlsConstraint: NSLayoutConstraint?
|
||||
private lazy var noVideoIndicatorView: UIStackView = createNoVideoIndicatorView()
|
||||
@ -740,7 +740,7 @@ extension GroupCallViewController: CallViewControllerWindowReference {
|
||||
existingSheet.dismiss(animated: false)
|
||||
}
|
||||
|
||||
let startCallString = OWSLocalizedString("GROUP_CALL_START_BUTTON", comment: "Button to start a group call")
|
||||
let startCallString = OWSLocalizedString("CALL_START_BUTTON", comment: "Button to start a call")
|
||||
let joinCallString = OWSLocalizedString("GROUP_CALL_JOIN_BUTTON", comment: "Button to join an ongoing group call")
|
||||
let continueCallString = OWSLocalizedString("GROUP_CALL_CONTINUE_BUTTON", comment: "Button to continue an ongoing group call")
|
||||
let leaveCallString = OWSLocalizedString("GROUP_CALL_LEAVE_BUTTON", comment: "Button to leave a group call")
|
||||
|
||||
@ -736,6 +736,9 @@
|
||||
/* Call setup status label after outgoing call times out */
|
||||
"CALL_SCREEN_STATUS_NO_ANSWER" = "No Answer";
|
||||
|
||||
/* Button to start a call */
|
||||
"CALL_START_BUTTON" = "Start Call";
|
||||
|
||||
/* embeds {{Call Status}} in call screen label. For ongoing calls, {{Call Status}} is a seconds timer like 01:23, otherwise {{Call Status}} is a short text like 'Ringing', 'Busy', or 'Failed Call' */
|
||||
"CALL_STATUS_FORMAT" = "Signal %@";
|
||||
|
||||
@ -2701,9 +2704,6 @@
|
||||
/* Toast view text informing user about swiping to speaker view */
|
||||
"GROUP_CALL_SPEAKER_VIEW_TOAST" = "Swipe up to change views";
|
||||
|
||||
/* Button to start a group call */
|
||||
"GROUP_CALL_START_BUTTON" = "Start Call";
|
||||
|
||||
/* Tooltip highlighting group calls. */
|
||||
"GROUP_CALL_START_TOOLTIP" = "Tap here to start a group call";
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user