diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index b6d45009e..b6a67f828 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -9,6 +9,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { let mode: Mode let webRTCSession: WebRTCSession var isMuted = false + var isVideoEnabled = false var conversationVC: ConversationVC? = nil lazy var cameraManager: CameraManager = { @@ -24,6 +25,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { // MARK: UI Components private lazy var localVideoView: RTCMTLVideoView = { let result = RTCMTLVideoView() + result.isHidden = !isVideoEnabled result.contentMode = .scaleAspectFill result.set(.width, to: 80) result.set(.height, to: 173) @@ -52,6 +54,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { private lazy var minimizeButton: UIButton = { let result = UIButton(type: .custom) + result.isHidden = true let image = UIImage(named: "Minimize")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -60,6 +63,18 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var answerButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "AnswerCall")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = Colors.accent + result.layer.cornerRadius = 30 + result.addTarget(self, action: #selector(answerCall), for: UIControl.Event.touchUpInside) + return result + }() + private lazy var hangUpButton: UIButton = { let result = UIButton(type: .custom) let image = UIImage(named: "EndCall")!.withTint(.white) @@ -68,12 +83,20 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { result.set(.height, to: 60) result.backgroundColor = Colors.destructive result.layer.cornerRadius = 30 - result.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + result.addTarget(self, action: #selector(endCall), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var responsePanel: UIStackView = { + let result = UIStackView(arrangedSubviews: [hangUpButton, answerButton]) + result.axis = .horizontal + result.spacing = Values.veryLargeSpacing * 2 + 40 return result }() private lazy var switchCameraButton: UIButton = { let result = UIButton(type: .custom) + result.isEnabled = isVideoEnabled let image = UIImage(named: "SwitchCamera")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -96,6 +119,26 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var videoButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "VideoCall")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.layer.cornerRadius = 30 + result.alpha = 0.5 + result.addTarget(self, action: #selector(operateCamera), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var operationPanel: UIStackView = { + let result = UIStackView(arrangedSubviews: [videoButton, switchAudioButton, switchCameraButton]) + result.axis = .horizontal + result.spacing = Values.veryLargeSpacing + return result + }() + private lazy var titleLabel: UILabel = { let result = UILabel() result.textColor = .white @@ -146,9 +189,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { Storage.write { transaction in self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() } - } else if case let .answer(sdp) = mode { - callInfoLabel.text = "Connecting..." - webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + answerButton.isHidden = true } } @@ -186,21 +227,14 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: minimizeButton) titleLabel.center(.horizontal, in: view) - // End call button - view.addSubview(hangUpButton) - hangUpButton.translatesAutoresizingMaskIntoConstraints = false - hangUpButton.center(.horizontal, in: view) - hangUpButton.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) - // Switch camera button - view.addSubview(switchCameraButton) - switchCameraButton.translatesAutoresizingMaskIntoConstraints = false - switchCameraButton.center(.vertical, in: hangUpButton) - switchCameraButton.pin(.right, to: .left, of: hangUpButton, withInset: -Values.veryLargeSpacing) - // Switch audio button - view.addSubview(switchAudioButton) - switchAudioButton.translatesAutoresizingMaskIntoConstraints = false - switchAudioButton.center(.vertical, in: hangUpButton) - switchAudioButton.pin(.left, to: .right, of: hangUpButton, withInset: Values.veryLargeSpacing) + // Response Panel + view.addSubview(responsePanel) + responsePanel.center(.horizontal, in: view) + responsePanel.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) + // Operation Panel + view.addSubview(operationPanel) + operationPanel.center(.horizontal, in: view) + operationPanel.pin(.bottom, to: .top, of: responsePanel, withInset: -Values.veryLargeSpacing) } private func getBackgroudView() -> UIView { @@ -229,12 +263,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - cameraManager.start() + if (isVideoEnabled) { cameraManager.start() } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - cameraManager.stop() + if (isVideoEnabled) { cameraManager.stop() } } // MARK: Interaction @@ -256,7 +290,18 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } } - @objc private func close() { + @objc private func answerCall() { + if case let .answer(sdp) = mode { + callInfoLabel.text = "Connecting..." + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + self.answerButton.alpha = 0 + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + self.answerButton.isHidden = true + }, completion: nil) + } + } + + @objc private func endCall() { Storage.write { transaction in WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) } @@ -265,16 +310,25 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } @objc private func minimize() { - if (localVideoView.isHidden) { + + } + + @objc private func operateCamera() { + if (isVideoEnabled) { + webRTCSession.turnOffVideo() + localVideoView.isHidden = true + cameraManager.stop() + videoButton.alpha = 0.5 + switchCameraButton.isEnabled = false + } else { webRTCSession.turnOnVideo() localVideoView.isHidden = false cameraManager.prepare() cameraManager.start() - } else { - webRTCSession.turnOffVideo() - localVideoView.isHidden = true - cameraManager.stop() + videoButton.alpha = 1.0 + switchCameraButton.isEnabled = true } + isVideoEnabled = !isVideoEnabled } @objc private func switchCamera() { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 269f1a5ca..6aeaa4e64 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -28,38 +28,13 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // MARK: Call @objc func startCall(_ sender: Any) { - let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet) - let voiceCallAction = UIAlertAction.init(title: NSLocalizedString("voice_call", comment: ""), style: .default) { _ in - self.startVoiceCall() - } - voiceCallAction.setValue(UIImage(named: "Phone"), forKey: "image") - alertVC.addAction(voiceCallAction) - - let videoCallAction = UIAlertAction.init(title: NSLocalizedString("video_call", comment: ""), style: .default) { _ in - self.startVideoCall() - } - videoCallAction.setValue(UIImage(named: "VideoCall"), forKey: "image") - alertVC.addAction(videoCallAction) - - let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in - self.showInputAccessoryView() - } - alertVC.addAction(cancelAction) - self.inputAccessoryView?.isHidden = true - self.inputAccessoryView?.alpha = 0 - self.presentAlert(alertVC, animated: true) - } - - private func startVoiceCall() { - - } - - private func startVideoCall() { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } let callVC = CallVC(for: contactSessionID, mode: .offer) callVC.conversationVC = self callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve + self.inputAccessoryView?.isHidden = true + self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 48fc38f4a..ae0fbfc3e 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -10,19 +10,10 @@ extension AppDelegate { DispatchQueue.main.async { let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - let alert = UIAlertController(title: "Incoming Call", message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in - let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) - presentingVC.dismiss(animated: true) { - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve - presentingVC.present(callVC, animated: true, completion: nil) - } - })) - alert.addAction(UIAlertAction(title: "Decline", style: .default, handler: { _ in - // Do nothing - })) - presentingVC.present(alert, animated: true, completion: nil) + let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve + presentingVC.present(callVC, animated: true, completion: nil) } } // Answer messages diff --git a/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf b/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf new file mode 100644 index 000000000..4350772de Binary files /dev/null and b/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf differ diff --git a/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/Contents.json new file mode 100644 index 000000000..20ef2663e --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "AnswerCall.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}