diff --git a/Session/Components/ConversationCell.swift b/Session/Components/ConversationCell.swift index c86a5d098..e4230a88d 100644 --- a/Session/Components/ConversationCell.swift +++ b/Session/Components/ConversationCell.swift @@ -166,7 +166,7 @@ final class ConversationCell : UITableViewCell { let image: UIImage let status = MessageRecipientStatusUtils.recipientStatus(outgoingMessage: lastMessage) switch status { - case .calculatingPoW, .uploading, .sending: image = #imageLiteral(resourceName: "CircleDotDotDot").asTintedImage(color: Colors.text)! + case .uploading, .sending: image = #imageLiteral(resourceName: "CircleDotDotDot").asTintedImage(color: Colors.text)! case .sent, .skipped, .delivered: image = #imageLiteral(resourceName: "CircleCheck").asTintedImage(color: Colors.text)! case .read: statusIndicatorView.backgroundColor = isLightMode ? .black : .white diff --git a/Session/Components/MentionCandidateSelectionView.swift b/Session/Components/MentionCandidateSelectionView.swift index a2eef63e9..f62d50d8b 100644 --- a/Session/Components/MentionCandidateSelectionView.swift +++ b/Session/Components/MentionCandidateSelectionView.swift @@ -1,6 +1,4 @@ -// MARK: - User Selection View - @objc(LKMentionCandidateSelectionView) final class MentionCandidateSelectionView : UIView, UITableViewDataSource, UITableViewDelegate { @objc var mentionCandidates: [Mention] = [] { didSet { tableView.reloadData() } } @@ -173,7 +171,8 @@ private extension MentionCandidateSelectionView { } } -// MARK: Delegate +// MARK: - Delegate + @objc(LKMentionCandidateSelectionViewDelegate) protocol MentionCandidateSelectionViewDelegate { diff --git a/Session/MessageHandler.swift b/Session/MessageReceiverDelegate.swift similarity index 100% rename from Session/MessageHandler.swift rename to Session/MessageReceiverDelegate.swift diff --git a/Session/Signal/AddToGroupViewController.h b/Session/Signal/AddToGroupViewController.h deleted file mode 100644 index dae373b41..000000000 --- a/Session/Signal/AddToGroupViewController.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SelectRecipientViewController.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol AddToGroupViewControllerDelegate - -- (void)recipientIdWasAdded:(NSString *)recipientId; - -- (BOOL)isRecipientGroupMember:(NSString *)recipientId; - -@end - -#pragma mark - - -@interface AddToGroupViewController : SelectRecipientViewController - -@property (nonatomic, weak) id addToGroupDelegate; - -@property (nonatomic) BOOL hideContacts; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Signal/AddToGroupViewController.m b/Session/Signal/AddToGroupViewController.m deleted file mode 100644 index c021ce8c3..000000000 --- a/Session/Signal/AddToGroupViewController.m +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "AddToGroupViewController.h" -#import "BlockListUIUtils.h" - -#import "Session-Swift.h" - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface AddToGroupViewController () - -@end - -#pragma mark - - -@implementation AddToGroupViewController - -- (void)loadView -{ - self.delegate = self; - - [super loadView]; - - self.title = NSLocalizedString(@"ADD_GROUP_MEMBER_VIEW_TITLE", @"Title for the 'add group member' view."); -} - -- (NSString *)phoneNumberSectionTitle -{ - return NSLocalizedString(@"ADD_GROUP_MEMBER_VIEW_PHONE_NUMBER_TITLE", - @"Title for the 'add by phone number' section of the 'add group member' view."); -} - -- (NSString *)phoneNumberButtonText -{ - return NSLocalizedString(@"ADD_GROUP_MEMBER_VIEW_BUTTON", - @"A label for the 'add by phone number' button in the 'add group member' view"); -} - -- (NSString *)contactsSectionTitle -{ - return NSLocalizedString( - @"ADD_GROUP_MEMBER_VIEW_CONTACT_TITLE", @"Title for the 'add contact' section of the 'add group member' view."); -} - -- (void)phoneNumberWasSelected:(NSString *)phoneNumber -{ - OWSAssertDebug(phoneNumber.length > 0); - - __weak AddToGroupViewController *weakSelf = self; - - if ([SSKEnvironment.shared.blockingManager isRecipientIdBlocked:phoneNumber]) { - [BlockListUIUtils showUnblockPhoneNumberActionSheet:phoneNumber - fromViewController:self - blockingManager:SSKEnvironment.shared.blockingManager - completionBlock:^(BOOL isBlocked) { - if (!isBlocked) { - [weakSelf addToGroup:phoneNumber]; - } - }]; - return; - } - - [self addToGroup:phoneNumber]; -} - -- (BOOL)canSignalAccountBeSelected:(SignalAccount *)signalAccount -{ - OWSAssertDebug(signalAccount); - - return ![self.addToGroupDelegate isRecipientGroupMember:signalAccount.recipientId]; -} - -- (void)signalAccountWasSelected:(SignalAccount *)signalAccount -{ - OWSAssertDebug(signalAccount); - - __weak AddToGroupViewController *weakSelf = self; - if ([self.addToGroupDelegate isRecipientGroupMember:signalAccount.recipientId]) { - OWSFailDebug(@"Cannot add user to group member if already a member."); - return; - } - - if ([SSKEnvironment.shared.blockingManager isRecipientIdBlocked:signalAccount.recipientId]) { - [BlockListUIUtils showUnblockSignalAccountActionSheet:signalAccount - fromViewController:self - blockingManager:SSKEnvironment.shared.blockingManager - completionBlock:^(BOOL isBlocked) { - if (!isBlocked) { - [weakSelf addToGroup:signalAccount.recipientId]; - } - }]; - return; - } - - [self addToGroup:signalAccount.recipientId]; -} - -- (void)addToGroup:(NSString *)recipientId -{ - OWSAssertDebug(recipientId.length > 0); - - [self.addToGroupDelegate recipientIdWasAdded:recipientId]; - [self.navigationController popViewControllerAnimated:YES]; -} - -- (BOOL)shouldHideLocalNumber -{ - return YES; -} - -- (BOOL)shouldHideContacts -{ - return self.hideContacts; -} - -- (BOOL)shouldValidatePhoneNumbers -{ - return YES; -} - -- (nullable NSString *)accessoryMessageForSignalAccount:(SignalAccount *)signalAccount -{ - OWSAssertDebug(signalAccount); - - if ([self.addToGroupDelegate isRecipientGroupMember:signalAccount.recipientId]) { - return NSLocalizedString(@"NEW_GROUP_MEMBER_LABEL", @"An indicator that a user is a member of the new group."); - } - - return nil; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Signal/AppEnvironment.swift b/Session/Signal/AppEnvironment.swift index 2df0f0dc8..cfacd98f3 100644 --- a/Session/Signal/AppEnvironment.swift +++ b/Session/Signal/AppEnvironment.swift @@ -86,10 +86,7 @@ import SignalUtilitiesKit @objc public func setup() { -// callService.createCallUIAdapter() - // Hang certain singletons on SSKEnvironment too. SSKEnvironment.shared.notificationsManager = notificationPresenter -// SSKEnvironment.shared.callMessageHandler = callMessageHandler } } diff --git a/Session/Signal/AvatarViewHelper.m b/Session/Signal/AvatarViewHelper.m index b5d4b7972..bf4664007 100644 --- a/Session/Signal/AvatarViewHelper.m +++ b/Session/Signal/AvatarViewHelper.m @@ -36,14 +36,6 @@ NS_ASSUME_NONNULL_BEGIN preferredStyle:UIAlertControllerStyleActionSheet]; [actionSheet addAction:[OWSAlerts cancelAction]]; -// UIAlertAction *takePictureAction = [UIAlertAction -// actionWithTitle:NSLocalizedString(@"MEDIA_FROM_CAMERA_BUTTON", @"media picker option to take photo or video") -// style:UIAlertActionStyleDefault -// handler:^(UIAlertAction *_Nonnull action) { -// [self takePicture]; -// }]; -// [actionSheet addAction:takePictureAction]; - UIAlertAction *choosePictureAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"MEDIA_FROM_LIBRARY_BUTTON", @"media picker option to choose from library") style:UIAlertActionStyleDefault diff --git a/Session/Signal/CallVideoHintView.swift b/Session/Signal/CallVideoHintView.swift deleted file mode 100644 index ba49030d0..000000000 --- a/Session/Signal/CallVideoHintView.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -protocol CallVideoHintViewDelegate: AnyObject { - func didTapCallVideoHintView(_ videoHintView: CallVideoHintView) -} - -class CallVideoHintView: UIView { - let label = UILabel() - var tapGesture: UITapGestureRecognizer! - weak var delegate: CallVideoHintViewDelegate? - - let kTailHMargin: CGFloat = 12 - let kTailWidth: CGFloat = 16 - let kTailHeight: CGFloat = 8 - - init() { - super.init(frame: .zero) - - tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(tapGesture:))) - addGestureRecognizer(tapGesture) - - let layerView = OWSLayerView(frame: .zero) { _ in } - let shapeLayer = CAShapeLayer() - shapeLayer.fillColor = UIColor.ows_signalBlue.cgColor - layerView.layer.addSublayer(shapeLayer) - addSubview(layerView) - layerView.autoPinEdgesToSuperviewEdges() - - let container = UIView() - addSubview(container) - container.autoSetDimension(.width, toSize: ScaleFromIPhone5(250), relation: .lessThanOrEqual) - container.layoutMargins = UIEdgeInsets(top: 7, leading: 12, bottom: 7, trailing: 12) - container.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: 0, leading: 0, bottom: kTailHeight, trailing: 0)) - - container.addSubview(label) - label.autoPinEdgesToSuperviewMargins() - label.setCompressionResistanceHigh() - label.setContentHuggingHigh() - label.font = UIFont.ows_dynamicTypeBody - label.textColor = .ows_white - label.numberOfLines = 0 - label.text = NSLocalizedString("CALL_VIEW_ENABLE_VIDEO_HINT", comment: "tooltip label when remote party has enabled their video") - - layerView.layoutCallback = { view in - let bezierPath = UIBezierPath() - - // Bubble - let bubbleBounds = container.bounds - bezierPath.append(UIBezierPath(roundedRect: bubbleBounds, cornerRadius: 8)) - - // Tail - var tailBottom = CGPoint(x: self.kTailHMargin + self.kTailWidth * 0.5, y: view.height()) - var tailLeft = CGPoint(x: self.kTailHMargin, y: view.height() - self.kTailHeight) - var tailRight = CGPoint(x: self.kTailHMargin + self.kTailWidth, y: view.height() - self.kTailHeight) - if (!CurrentAppContext().isRTL) { - tailBottom.x = view.width() - tailBottom.x - tailLeft.x = view.width() - tailLeft.x - tailRight.x = view.width() - tailRight.x - } - bezierPath.move(to: tailBottom) - bezierPath.addLine(to: tailLeft) - bezierPath.addLine(to: tailRight) - bezierPath.addLine(to: tailBottom) - shapeLayer.path = bezierPath.cgPath - shapeLayer.frame = view.bounds - } - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - - - @objc - func didTap(tapGesture: UITapGestureRecognizer) { - self.delegate?.didTapCallVideoHintView(self) - } -} diff --git a/Session/Signal/CallViewController.swift b/Session/Signal/CallViewController.swift deleted file mode 100644 index 02e27363d..000000000 --- a/Session/Signal/CallViewController.swift +++ /dev/null @@ -1,1227 +0,0 @@ -//// -//// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -//// -// -//import Foundation -//import WebRTC -//import PromiseKit -//import SignalUtilitiesKit -//import SignalUtilitiesKit -// -//// TODO: Add category so that button handlers can be defined where button is created. -//// TODO: Ensure buttons enabled & disabled as necessary. -//class CallViewController: OWSViewController, CallObserver, CallServiceObserver, CallAudioServiceDelegate { -// -// // Dependencies -// -// var callUIAdapter: CallUIAdapter { -// return AppEnvironment.shared.callService.callUIAdapter -// } -// -// var proximityMonitoringManager: OWSProximityMonitoringManager { -// return Environment.shared.proximityMonitoringManager -// } -// -// // Feature Flag -// @objc public static let kShowCallViewOnSeparateWindow = true -// -// let contactsManager: OWSContactsManager -// -// // MARK: - Properties -// -// let thread: TSContactThread -// let call: SignalCall -// var hasDismissed = false -// -// // MARK: - Views -// -// var hasConstraints = false -// var blurView: UIVisualEffectView! -// var dateFormatter: DateFormatter? -// -// // MARK: - Contact Views -// -// var contactNameLabel: MarqueeLabel! -// var contactAvatarView: AvatarImageView! -// var contactAvatarContainerView: UIView! -// var callStatusLabel: UILabel! -// var callDurationTimer: Timer? -// var leaveCallViewButton: UIButton! -// -// // MARK: - Ongoing Call Controls -// -// var ongoingCallControls: UIStackView! -// -// var ongoingAudioCallControls: UIStackView! -// var ongoingVideoCallControls: UIStackView! -// -// var hangUpButton: UIButton! -// var audioSourceButton: UIButton! -// var audioModeMuteButton: UIButton! -// var audioModeVideoButton: UIButton! -// var videoModeMuteButton: UIButton! -// var videoModeVideoButton: UIButton! -// var videoModeFlipCameraButton: UIButton! -// -// // MARK: - Incoming Call Controls -// -// var incomingCallControls: UIStackView! -// -// var acceptIncomingButton: UIButton! -// var declineIncomingButton: UIButton! -// -// // MARK: - Video Views -// -// var remoteVideoView: RemoteVideoView! -// var localVideoView: RTCCameraPreviewView! -// var hasShownLocalVideo = false -// weak var localCaptureSession: AVCaptureSession? -// weak var remoteVideoTrack: RTCVideoTrack? -// -// override public var canBecomeFirstResponder: Bool { -// return true -// } -// -// var shouldRemoteVideoControlsBeHidden = false { -// didSet { -// updateCallUI(callState: call.state) -// } -// } -// -// // MARK: - Settings Nag Views -// -// var isShowingSettingsNag = false { -// didSet { -// if oldValue != isShowingSettingsNag { -// updateCallUI(callState: call.state) -// } -// } -// } -// var settingsNagView: UIView! -// var settingsNagDescriptionLabel: UILabel! -// -// // MARK: - Audio Source -// -// var hasAlternateAudioSources: Bool { -// Logger.info("available audio sources: \(allAudioSources)") -// // internal mic and speakerphone will be the first two, any more than one indicates e.g. an attached bluetooth device. -// -// // TODO is this sufficient? Are their devices w/ bluetooth but no external speaker? e.g. ipod? -// return allAudioSources.count > 2 -// } -// -// var allAudioSources: Set = Set() -// -// var appropriateAudioSources: Set { -// if call.hasLocalVideo { -// let appropriateForVideo = allAudioSources.filter { audioSource in -// if audioSource.isBuiltInSpeaker { -// return true -// } else { -// guard let portDescription = audioSource.portDescription else { -// owsFailDebug("Only built in speaker should be lacking a port description.") -// return false -// } -// -// // Don't use receiver when video is enabled. Only bluetooth or speaker -// return portDescription.portType != AVAudioSession.Port.builtInMic -// } -// } -// return Set(appropriateForVideo) -// } else { -// return allAudioSources -// } -// } -// -// // MARK: - Initializers -// -// @available(*, unavailable, message: "use init(call:) constructor instead.") -// required init?(coder aDecoder: NSCoder) { -// notImplemented() -// } -// -// required init(call: SignalCall) { -// contactsManager = Environment.shared.contactsManager -// self.call = call -// self.thread = TSContactThread.getOrCreateThread(contactId: call.remotePhoneNumber) -// super.init(nibName: nil, bundle: nil) -// -// allAudioSources = Set(callUIAdapter.audioService.availableInputs) -// -// self.shouldUseTheme = false -// } -// -// deinit { -// Logger.info("") -// NotificationCenter.default.removeObserver(self) -// self.proximityMonitoringManager.remove(lifetime: self) -// } -// -// @objc func didBecomeActive() { -// if (self.isViewLoaded) { -// shouldRemoteVideoControlsBeHidden = false -// } -// } -// -// // MARK: - View Lifecycle -// -// override func viewDidDisappear(_ animated: Bool) { -// super.viewDidDisappear(animated) -// -// callDurationTimer?.invalidate() -// callDurationTimer = nil -// } -// -// override func viewWillAppear(_ animated: Bool) { -// super.viewWillAppear(animated) -// -// ensureProximityMonitoring() -// -// updateCallUI(callState: call.state) -// -// self.becomeFirstResponder() -// } -// -// override func viewDidAppear(_ animated: Bool) { -// super.viewDidAppear(animated) -// -// self.becomeFirstResponder() -// } -// -// override func loadView() { -// self.view = UIView() -// self.view.backgroundColor = UIColor.black -// self.view.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) -// -// createViews() -// createViewConstraints() -// } -// -// override func viewDidLoad() { -// super.viewDidLoad() -// -// contactNameLabel.text = contactsManager.stringForConversationTitle(withPhoneIdentifier: thread.contactIdentifier()) -// updateAvatarImage() -// NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in -// guard let strongSelf = self else { return } -// Logger.info("updating avatar image") -// strongSelf.updateAvatarImage() -// } -// -// // Subscribe for future call updates -// call.addObserverAndSyncState(observer: self) -// -// AppEnvironment.shared.callService.addObserverAndSyncState(observer: self) -// -// assert(callUIAdapter.audioService.delegate == nil) -// callUIAdapter.audioService.delegate = self -// -// NotificationCenter.default.addObserver(self, -// selector: #selector(didBecomeActive), -// name: NSNotification.Name.OWSApplicationDidBecomeActive, -// object: nil) -// } -// -// override var supportedInterfaceOrientations: UIInterfaceOrientationMask { -// return .portrait -// } -// -// override var preferredStatusBarStyle: UIStatusBarStyle { -// return .lightContent -// } -// -// // MARK: - Create Views -// -// func createViews() { -// self.view.isUserInteractionEnabled = true -// self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, -// action: #selector(didTouchRootView))) -// -// videoHintView.delegate = self -// -// // Dark blurred background. -// let blurEffect = UIBlurEffect(style: .dark) -// blurView = UIVisualEffectView(effect: blurEffect) -// blurView.isUserInteractionEnabled = false -// self.view.addSubview(blurView) -// -// // Create the video views first, as they are under the other views. -// createVideoViews() -// createContactViews() -// createOngoingCallControls() -// createIncomingCallControls() -// createSettingsNagViews() -// } -// -// @objc func didTouchRootView(sender: UIGestureRecognizer) { -// if !remoteVideoView.isHidden { -// shouldRemoteVideoControlsBeHidden = !shouldRemoteVideoControlsBeHidden -// } -// } -// -// func createVideoViews() { -// remoteVideoView = RemoteVideoView() -// remoteVideoView.isUserInteractionEnabled = false -// localVideoView = RTCCameraPreviewView() -// -// remoteVideoView.isHidden = true -// localVideoView.isHidden = true -// self.view.addSubview(remoteVideoView) -// self.view.addSubview(localVideoView) -// } -// -// func createContactViews() { -// -// leaveCallViewButton = UIButton() -// let backButtonImage = CurrentAppContext().isRTL ? #imageLiteral(resourceName: "NavBarBackRTL") : #imageLiteral(resourceName: "NavBarBack") -// leaveCallViewButton.setImage(backButtonImage, for: .normal) -// leaveCallViewButton.autoSetDimensions(to: CGSize(width: 40, height: 40)) -// leaveCallViewButton.addTarget(self, action: #selector(didTapLeaveCall(sender:)), for: .touchUpInside) -// self.view.addSubview(leaveCallViewButton) -// -// contactNameLabel = MarqueeLabel() -// -// // marquee config -// contactNameLabel.type = .continuous -// // This feels pretty slow when you're initially waiting for it, but when you're overlaying video calls, anything faster is distracting. -// contactNameLabel.speed = .duration(30.0) -// contactNameLabel.animationCurve = .linear -// contactNameLabel.fadeLength = 10.0 -// contactNameLabel.animationDelay = 5 -// // Add trailing space after the name scrolls before it wraps around and scrolls back in. -// contactNameLabel.trailingBuffer = ScaleFromIPhone5(80.0) -// -// // label config -// contactNameLabel.font = UIFont.ows_dynamicTypeTitle1 -// contactNameLabel.textAlignment = .center -// contactNameLabel.textColor = UIColor.white -// contactNameLabel.layer.shadowOffset = CGSize.zero -// contactNameLabel.layer.shadowOpacity = 0.35 -// contactNameLabel.layer.shadowRadius = 4 -// -// self.view.addSubview(contactNameLabel) -// -// callStatusLabel = UILabel() -// callStatusLabel.font = UIFont.ows_dynamicTypeBody -// callStatusLabel.textAlignment = .center -// callStatusLabel.textColor = UIColor.white -// callStatusLabel.layer.shadowOffset = CGSize.zero -// callStatusLabel.layer.shadowOpacity = 0.35 -// callStatusLabel.layer.shadowRadius = 4 -// -// self.view.addSubview(callStatusLabel) -// -// contactAvatarContainerView = UIView.container() -// self.view.addSubview(contactAvatarContainerView) -// contactAvatarView = AvatarImageView() -// contactAvatarContainerView.addSubview(contactAvatarView) -// } -// -// func createSettingsNagViews() { -// settingsNagView = UIView() -// settingsNagView.isHidden = true -// self.view.addSubview(settingsNagView) -// -// let viewStack = UIView() -// settingsNagView.addSubview(viewStack) -// viewStack.autoPinWidthToSuperview() -// viewStack.autoVCenterInSuperview() -// -// settingsNagDescriptionLabel = UILabel() -// settingsNagDescriptionLabel.text = NSLocalizedString("CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL", -// comment: "Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy.") -// settingsNagDescriptionLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(16, 18)) -// settingsNagDescriptionLabel.textColor = UIColor.white -// settingsNagDescriptionLabel.numberOfLines = 0 -// settingsNagDescriptionLabel.lineBreakMode = .byWordWrapping -// viewStack.addSubview(settingsNagDescriptionLabel) -// settingsNagDescriptionLabel.autoPinWidthToSuperview() -// settingsNagDescriptionLabel.autoPinEdge(toSuperviewEdge: .top) -// -// let buttonHeight = ScaleFromIPhone5To7Plus(35, 45) -// let descriptionVSpacingHeight = ScaleFromIPhone5To7Plus(30, 60) -// -// let callSettingsButton = OWSFlatButton.button(title: NSLocalizedString("CALL_VIEW_SETTINGS_NAG_SHOW_CALL_SETTINGS", -// comment: "Label for button that shows the privacy settings."), -// font: OWSFlatButton.fontForHeight(buttonHeight), -// titleColor: UIColor.white, -// backgroundColor: UIColor.ows_signalBrandBlue, -// target: self, -// selector: #selector(didPressShowCallSettings)) -// viewStack.addSubview(callSettingsButton) -// callSettingsButton.autoSetDimension(.height, toSize: buttonHeight) -// callSettingsButton.autoPinWidthToSuperview() -// callSettingsButton.autoPinEdge(.top, to: .bottom, of: settingsNagDescriptionLabel, withOffset: descriptionVSpacingHeight) -// -// let notNowButton = OWSFlatButton.button(title: NSLocalizedString("CALL_VIEW_SETTINGS_NAG_NOT_NOW_BUTTON", -// comment: "Label for button that dismiss the call view's settings nag."), -// font: OWSFlatButton.fontForHeight(buttonHeight), -// titleColor: UIColor.white, -// backgroundColor: UIColor.ows_signalBrandBlue, -// target: self, -// selector: #selector(didPressDismissNag)) -// viewStack.addSubview(notNowButton) -// notNowButton.autoSetDimension(.height, toSize: buttonHeight) -// notNowButton.autoPinWidthToSuperview() -// notNowButton.autoPinEdge(toSuperviewEdge: .bottom) -// notNowButton.autoPinEdge(.top, to: .bottom, of: callSettingsButton, withOffset: 12) -// } -// -// func buttonSize() -> CGFloat { -// return ScaleFromIPhone5To7Plus(84, 108) -// } -// -// func buttonInset() -> CGFloat { -// return ScaleFromIPhone5To7Plus(7, 9) -// } -// -// func createOngoingCallControls() { -// -// audioSourceButton = createButton(image: #imageLiteral(resourceName: "audio-call-speaker-inactive"), -// action: #selector(didPressAudioSource)) -// audioSourceButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_AUDIO_SOURCE_LABEL", -// comment: "Accessibility label for selection the audio source") -// -// hangUpButton = createButton(image: #imageLiteral(resourceName: "hangup-active-wide"), -// action: #selector(didPressHangup)) -// hangUpButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_HANGUP_LABEL", -// comment: "Accessibility label for hang up call") -// -// audioModeMuteButton = createButton(image: #imageLiteral(resourceName: "audio-call-mute-inactive"), -// action: #selector(didPressMute)) -// audioModeMuteButton.setImage(#imageLiteral(resourceName: "audio-call-mute-active"), for: .selected) -// -// audioModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL", -// comment: "Accessibility label for muting the microphone") -// -// audioModeVideoButton = createButton(image: #imageLiteral(resourceName: "audio-call-video-inactive"), -// action: #selector(didPressVideo)) -// audioModeVideoButton.setImage(#imageLiteral(resourceName: "audio-call-video-active"), for: .selected) -// audioModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_VIDEO_LABEL", comment: "Accessibility label to switch to video call") -// -// videoModeMuteButton = createButton(image: #imageLiteral(resourceName: "video-mute-unselected"), -// action: #selector(didPressMute)) -// videoModeMuteButton.setImage(#imageLiteral(resourceName: "video-mute-selected"), for: .selected) -// videoModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL", comment: "Accessibility label for muting the microphone") -// videoModeMuteButton.alpha = 0.9 -// -// videoModeFlipCameraButton = createButton(image: #imageLiteral(resourceName: "video-switch-camera-unselected"), -// action: #selector(didPressFlipCamera)) -// -// videoModeFlipCameraButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_CAMERA_DIRECTION", comment: "Accessibility label to toggle front- vs. rear-facing camera") -// videoModeFlipCameraButton.alpha = 0.9 -// -// videoModeVideoButton = createButton(image: #imageLiteral(resourceName: "video-video-unselected"), -// action: #selector(didPressVideo)) -// videoModeVideoButton.setImage(#imageLiteral(resourceName: "video-video-selected"), for: .selected) -// videoModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_AUDIO_LABEL", comment: "Accessibility label to switch to audio only") -// videoModeVideoButton.alpha = 0.9 -// -// ongoingCallControls = UIStackView(arrangedSubviews: [hangUpButton]) -// ongoingCallControls.axis = .vertical -// ongoingCallControls.alignment = .center -// view.addSubview(ongoingCallControls) -// -// ongoingAudioCallControls = UIStackView(arrangedSubviews: [audioModeMuteButton, audioSourceButton, audioModeVideoButton]) -// ongoingAudioCallControls.distribution = .equalSpacing -// ongoingAudioCallControls.axis = .horizontal -// -// ongoingVideoCallControls = UIStackView(arrangedSubviews: [videoModeMuteButton, videoModeFlipCameraButton, videoModeVideoButton]) -// ongoingAudioCallControls.distribution = .equalSpacing -// ongoingVideoCallControls.axis = .horizontal -// } -// -// func presentAudioSourcePicker() { -// AssertIsOnMainThread() -// -// let actionSheetController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) -// -// let dismissAction = UIAlertAction(title: CommonStrings.dismissButton, style: .cancel, handler: nil) -// actionSheetController.addAction(dismissAction) -// -// let currentAudioSource = callUIAdapter.audioService.currentAudioSource(call: self.call) -// for audioSource in self.appropriateAudioSources { -// let routeAudioAction = UIAlertAction(title: audioSource.localizedName, style: .default) { _ in -// self.callUIAdapter.setAudioSource(call: self.call, audioSource: audioSource) -// } -// -// // HACK: private API to create checkmark for active audio source. -// routeAudioAction.setValue(currentAudioSource == audioSource, forKey: "checked") -// -// // TODO: pick some icons. Leaving out for MVP -// // HACK: private API to add image to actionsheet -// // routeAudioAction.setValue(audioSource.image, forKey: "image") -// -// actionSheetController.addAction(routeAudioAction) -// } -// -// // Note: It's critical that we present from this view and -// // not the "frontmost view controller" since this view may -// // reside on a separate window. -// presentAlert(actionSheetController) -// } -// -// func updateAvatarImage() { -// contactAvatarView.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: 400) -// } -// -// func createIncomingCallControls() { -// -// acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"), -// action: #selector(didPressAnswerCall)) -// acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL", -// comment: "Accessibility label for accepting incoming calls") -// declineIncomingButton = createButton(image: #imageLiteral(resourceName: "hangup-active-wide"), -// action: #selector(didPressDeclineCall)) -// declineIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_DECLINE_INCOMING_CALL_LABEL", -// comment: "Accessibility label for declining incoming calls") -// -// incomingCallControls = UIStackView(arrangedSubviews: [acceptIncomingButton, declineIncomingButton]) -// incomingCallControls.axis = .horizontal -// incomingCallControls.alignment = .center -// incomingCallControls.distribution = .equalSpacing -// -// view.addSubview(incomingCallControls) -// } -// -// func createButton(image: UIImage, action: Selector) -> UIButton { -// let button = UIButton() -// button.setImage(image, for: .normal) -// button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(), -// left: buttonInset(), -// bottom: buttonInset(), -// right: buttonInset()) -// button.addTarget(self, action: action, for: .touchUpInside) -// button.autoSetDimension(.width, toSize: buttonSize()) -// button.autoSetDimension(.height, toSize: buttonSize()) -// return button -// } -// -// // MARK: - Layout -// -// var localVideoViewTopConstraintDefault: NSLayoutConstraint! -// var localVideoViewTopConstraintHidden: NSLayoutConstraint! -// -// func createViewConstraints() { -// -// let contactVSpacing = CGFloat(3) -// let settingsNagHMargin = CGFloat(30) -// let ongoingBottomMargin = ScaleFromIPhone5To7Plus(23, 41) -// let incomingHMargin = ScaleFromIPhone5To7Plus(30, 56) -// let incomingBottomMargin = CGFloat(41) -// let settingsNagBottomMargin = CGFloat(41) -// let avatarTopSpacing = ScaleFromIPhone5To7Plus(25, 50) -// // The buttons have built-in 10% margins, so to appear centered -// // the avatar's bottom spacing should be a bit less. -// let avatarBottomSpacing = ScaleFromIPhone5To7Plus(18, 41) -// // Layout of the local video view is a bit unusual because -// // although the view is square, it will be used -// let videoPreviewHMargin = CGFloat(0) -// -// // Dark blurred background. -// blurView.autoPinEdgesToSuperviewEdges() -// -// leaveCallViewButton.autoPinEdge(toSuperviewEdge: .leading) -// -// if #available(iOS 11, *) { -// leaveCallViewButton.autoPinEdge(toSuperviewMargin: .top) -// contactNameLabel.autoPinEdge(toSuperviewMargin: .top) -// } else { -// leaveCallViewButton.autoPinEdge(.top, to: .top, of: view) -// contactNameLabel.autoPinEdge(.top, to: .top, of: view) -// } -// -// contactNameLabel.autoPinEdge(.leading, to: .trailing, of: leaveCallViewButton, withOffset: 8, relation: .greaterThanOrEqual) -// contactNameLabel.autoHCenterInSuperview() -// contactNameLabel.setContentHuggingVerticalHigh() -// contactNameLabel.setCompressionResistanceHigh() -// -// callStatusLabel.autoPinEdge(.top, to: .bottom, of: contactNameLabel, withOffset: contactVSpacing) -// callStatusLabel.autoHCenterInSuperview() -// callStatusLabel.setContentHuggingVerticalHigh() -// callStatusLabel.setCompressionResistanceHigh() -// -// localVideoView.autoPinTrailingToSuperviewMargin(withInset: videoPreviewHMargin) -// -// self.localVideoViewTopConstraintDefault = localVideoView.autoPinEdge(.top, to: .bottom, of: callStatusLabel, withOffset: 4) -// self.localVideoViewTopConstraintHidden = localVideoView.autoPinEdge(toSuperviewMargin: .top) -// let localVideoSize = ScaleFromIPhone5To7Plus(80, 100) -// localVideoView.autoSetDimension(.width, toSize: localVideoSize) -// localVideoView.autoSetDimension(.height, toSize: localVideoSize) -// -// remoteVideoView.autoPinEdgesToSuperviewEdges() -// -// contactAvatarContainerView.autoPinEdge(.top, to: .bottom, of: callStatusLabel, withOffset: +avatarTopSpacing) -// contactAvatarContainerView.autoPinEdge(.bottom, to: .top, of: ongoingCallControls, withOffset: -avatarBottomSpacing) -// contactAvatarContainerView.autoPinWidthToSuperview(withMargin: avatarTopSpacing) -// -// contactAvatarView.autoCenterInSuperview() -// -// // Ensure ContacAvatarView gets as close as possible to it's superview edges while maintaining -// // aspect ratio. -// contactAvatarView.autoPinToSquareAspectRatio() -// contactAvatarView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual) -// contactAvatarView.autoPinEdge(toSuperviewEdge: .right, withInset: 0, relation: .greaterThanOrEqual) -// contactAvatarView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual) -// contactAvatarView.autoPinEdge(toSuperviewEdge: .left, withInset: 0, relation: .greaterThanOrEqual) -// NSLayoutConstraint.autoSetPriority(UILayoutPriority.defaultLow) { -// contactAvatarView.autoPinEdgesToSuperviewMargins() -// } -// -// // Ongoing call controls -// ongoingCallControls.autoPinEdge(toSuperviewEdge: .bottom, withInset: ongoingBottomMargin) -// ongoingCallControls.autoPinLeadingToSuperviewMargin() -// ongoingCallControls.autoPinTrailingToSuperviewMargin() -// ongoingCallControls.setContentHuggingVerticalHigh() -// -// // Incoming call controls -// incomingCallControls.autoPinEdge(toSuperviewEdge: .bottom, withInset: incomingBottomMargin) -// incomingCallControls.autoPinLeadingToSuperviewMargin(withInset: incomingHMargin) -// incomingCallControls.autoPinTrailingToSuperviewMargin(withInset: incomingHMargin) -// incomingCallControls.setContentHuggingVerticalHigh() -// -// // Settings nag views -// settingsNagView.autoPinEdge(toSuperviewEdge: .bottom, withInset: settingsNagBottomMargin) -// settingsNagView.autoPinWidthToSuperview(withMargin: settingsNagHMargin) -// settingsNagView.autoPinEdge(.top, to: .bottom, of: callStatusLabel) -// } -// -// override func updateViewConstraints() { -// updateRemoteVideoLayout() -// updateLocalVideoLayout() -// -// super.updateViewConstraints() -// } -// -// internal func updateRemoteVideoLayout() { -// remoteVideoView.isHidden = !self.hasRemoteVideoTrack -// updateCallUI(callState: call.state) -// } -// -// let videoHintView = CallVideoHintView() -// -// internal func updateLocalVideoLayout() { -// if !localVideoView.isHidden { -// localVideoView.superview?.bringSubviewToFront(localVideoView) -// } -// -// updateCallUI(callState: call.state) -// } -// -// // MARK: - Methods -// -// func showCallFailed(error: Error) { -// // TODO Show something in UI. -// Logger.error("call failed with error: \(error)") -// } -// -// // MARK: - View State -// -// func localizedTextForCallState(_ callState: CallState) -> String { -// assert(Thread.isMainThread) -// -// switch callState { -// case .idle, .remoteHangup, .localHangup: -// return NSLocalizedString("IN_CALL_TERMINATED", comment: "Call setup status label") -// case .dialing: -// return NSLocalizedString("IN_CALL_CONNECTING", comment: "Call setup status label") -// case .remoteRinging, .localRinging: -// return NSLocalizedString("IN_CALL_RINGING", comment: "Call setup status label") -// case .answering: -// return NSLocalizedString("IN_CALL_SECURING", comment: "Call setup status label") -// case .connected: -// let callDuration = call.connectionDuration() -// let callDurationDate = Date(timeIntervalSinceReferenceDate: callDuration) -// if dateFormatter == nil { -// dateFormatter = DateFormatter() -// dateFormatter!.dateFormat = "HH:mm:ss" -// dateFormatter!.timeZone = TimeZone(identifier: "UTC")! -// } -// var formattedDate = dateFormatter!.string(from: callDurationDate) -// if formattedDate.hasPrefix("00:") { -// // Don't show the "hours" portion of the date format unless the -// // call duration is at least 1 hour. -// formattedDate = String(formattedDate[formattedDate.index(formattedDate.startIndex, offsetBy: 3)...]) -// } else { -// // If showing the "hours" portion of the date format, strip any leading -// // zeroes. -// if formattedDate.hasPrefix("0") { -// formattedDate = String(formattedDate[formattedDate.index(formattedDate.startIndex, offsetBy: 1)...]) -// } -// } -// return formattedDate -// case .reconnecting: -// return NSLocalizedString("IN_CALL_RECONNECTING", comment: "Call setup status label") -// case .remoteBusy: -// return NSLocalizedString("END_CALL_RESPONDER_IS_BUSY", comment: "Call setup status label") -// case .localFailure: -// if let error = call.error { -// switch error { -// case .timeout(description: _): -// if self.call.direction == .outgoing { -// return NSLocalizedString("CALL_SCREEN_STATUS_NO_ANSWER", comment: "Call setup status label after outgoing call times out") -// } -// default: -// break -// } -// } -// -// return NSLocalizedString("END_CALL_UNCATEGORIZED_FAILURE", comment: "Call setup status label") -// } -// } -// -// var isBlinkingReconnectLabel = false -// func updateCallStatusLabel(callState: CallState) { -// assert(Thread.isMainThread) -// -// let text = String(format: CallStrings.callStatusFormat, -// localizedTextForCallState(callState)) -// self.callStatusLabel.text = text -// -// // Handle reconnecting blinking -// if case .reconnecting = callState { -// if !isBlinkingReconnectLabel { -// isBlinkingReconnectLabel = true -// UIView.animate(withDuration: 0.7, delay: 0, options: [.autoreverse, .repeat], -// animations: { -// self.callStatusLabel.alpha = 0.2 -// }, completion: nil) -// } else { -// // already blinking -// } -// } else { -// // We're no longer in a reconnecting state, either the call failed or we reconnected. -// // Stop the blinking animation -// if isBlinkingReconnectLabel { -// self.callStatusLabel.layer.removeAllAnimations() -// self.callStatusLabel.alpha = 1 -// isBlinkingReconnectLabel = false -// } -// } -// } -// -// func updateCallUI(callState: CallState) { -// assert(Thread.isMainThread) -// updateCallStatusLabel(callState: callState) -// if isShowingSettingsNag { -// settingsNagView.isHidden = false -// contactAvatarView.isHidden = true -// ongoingCallControls.isHidden = true -// return -// } -// -// // Marquee scrolling is distracting during a video call, disable it. -// contactNameLabel.labelize = call.hasLocalVideo -// -// audioModeMuteButton.isSelected = call.isMuted -// videoModeMuteButton.isSelected = call.isMuted -// audioModeVideoButton.isSelected = call.hasLocalVideo -// videoModeVideoButton.isSelected = call.hasLocalVideo -// -// // Show Incoming vs. Ongoing call controls -// let isRinging = callState == .localRinging -// incomingCallControls.isHidden = !isRinging -// incomingCallControls.isUserInteractionEnabled = isRinging -// ongoingCallControls.isHidden = isRinging -// ongoingCallControls.isUserInteractionEnabled = !isRinging -// -// // Rework control state if remote video is available. -// let hasRemoteVideo = !remoteVideoView.isHidden -// contactAvatarView.isHidden = hasRemoteVideo -// -// // Rework control state if local video is available. -// let hasLocalVideo = !localVideoView.isHidden -// -// if hasLocalVideo { -// ongoingAudioCallControls.removeFromSuperview() -// ongoingCallControls.insertArrangedSubview(ongoingVideoCallControls, at: 0) -// } else { -// ongoingVideoCallControls.removeFromSuperview() -// ongoingCallControls.insertArrangedSubview(ongoingAudioCallControls, at: 0) -// } -// // Layout immediately to avoid spurious animation. -// ongoingCallControls.layoutIfNeeded() -// -// // Also hide other controls if user has tapped to hide them. -// if shouldRemoteVideoControlsBeHidden && !remoteVideoView.isHidden { -// leaveCallViewButton.isHidden = true -// contactNameLabel.isHidden = true -// callStatusLabel.isHidden = true -// ongoingCallControls.isHidden = true -// videoHintView.isHidden = true -// } else { -// leaveCallViewButton.isHidden = false -// contactNameLabel.isHidden = false -// callStatusLabel.isHidden = false -// -// if hasRemoteVideo && !hasLocalVideo && !hasShownLocalVideo && !hasUserDismissedVideoHint { -// view.addSubview(videoHintView) -// videoHintView.isHidden = false -// videoHintView.autoPinEdge(.bottom, to: .top, of: audioModeVideoButton) -// videoHintView.autoPinEdge(.trailing, to: .leading, of: audioModeVideoButton, withOffset: buttonSize() / 2 + videoHintView.kTailHMargin + videoHintView.kTailWidth / 2) -// } else { -// videoHintView.removeFromSuperview() -// } -// } -// -// let doLocalVideoLayout = { -// self.localVideoViewTopConstraintDefault.isActive = !self.contactNameLabel.isHidden -// self.localVideoViewTopConstraintHidden.isActive = self.contactNameLabel.isHidden -// self.localVideoView.superview?.layoutIfNeeded() -// } -// if hasShownLocalVideo { -// // Animate. -// UIView.animate(withDuration: 0.25, animations: doLocalVideoLayout) -// } else { -// // Don't animate. -// doLocalVideoLayout() -// } -// -// // Audio Source Handling (bluetooth) -// if self.hasAlternateAudioSources { -// // With bluetooth, button does not stay selected. Pressing it pops an actionsheet -// // and the button should immediately "unselect". -// audioSourceButton.isSelected = false -// -// if hasLocalVideo { -// audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_video_mode"), for: .normal) -// audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_video_mode"), for: .selected) -// } else { -// audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_audio_mode"), for: .normal) -// audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_audio_mode"), for: .selected) -// } -// audioSourceButton.isHidden = false -// } else { -// // No bluetooth audio detected -// audioSourceButton.setImage(#imageLiteral(resourceName: "audio-call-speaker-inactive"), for: .normal) -// audioSourceButton.setImage(#imageLiteral(resourceName: "audio-call-speaker-active"), for: .selected) -// -// // If there's no bluetooth, we always use speakerphone, so no need for -// // a button, giving more screen back for the video. -// audioSourceButton.isHidden = hasLocalVideo -// } -// -// // Dismiss Handling -// switch callState { -// case .remoteHangup, .remoteBusy, .localFailure: -// Logger.debug("dismissing after delay because new state is \(callState)") -// dismissIfPossible(shouldDelay: true) -// case .localHangup: -// Logger.debug("dismissing immediately from local hangup") -// dismissIfPossible(shouldDelay: false) -// default: break -// } -// -// if callState == .connected { -// if callDurationTimer == nil { -// let kDurationUpdateFrequencySeconds = 1 / 20.0 -// callDurationTimer = WeakTimer.scheduledTimer(timeInterval: TimeInterval(kDurationUpdateFrequencySeconds), -// target: self, -// userInfo: nil, -// repeats: true) {[weak self] _ in -// self?.updateCallDuration() -// } -// } -// } else { -// callDurationTimer?.invalidate() -// callDurationTimer = nil -// } -// } -// -// func updateCallDuration() { -// updateCallStatusLabel(callState: call.state) -// } -// -// // We update the audioSourceButton outside of the main `updateCallUI` -// // because `updateCallUI` is intended to be idempotent, which isn't possible -// // with external speaker state because: -// // - the system API which enables the external speaker is a (somewhat slow) asyncronous -// // operation -// // - we want to give immediate UI feedback by marking the pressed button as selected -// // before the operation completes. -// func updateAudioSourceButtonIsSelected() { -// guard callUIAdapter.audioService.isSpeakerphoneEnabled else { -// self.audioSourceButton.isSelected = false -// return -// } -// -// // VideoChat mode enables the output speaker, but we don't -// // want to highlight the speaker button in that case. -// guard !call.hasLocalVideo else { -// self.audioSourceButton.isSelected = false -// return -// } -// -// self.audioSourceButton.isSelected = true -// } -// -// // MARK: - Actions -// -// /** -// * Ends a connected call. Do not confuse with `didPressDeclineCall`. -// */ -// @objc func didPressHangup(sender: UIButton) { -// Logger.info("") -// -// callUIAdapter.localHangupCall(call) -// -// dismissIfPossible(shouldDelay: false) -// } -// -// @objc func didPressMute(sender muteButton: UIButton) { -// Logger.info("") -// muteButton.isSelected = !muteButton.isSelected -// -// callUIAdapter.setIsMuted(call: call, isMuted: muteButton.isSelected) -// } -// -// @objc func didPressAudioSource(sender button: UIButton) { -// Logger.info("") -// -// if self.hasAlternateAudioSources { -// presentAudioSourcePicker() -// } else { -// didPressSpeakerphone(sender: button) -// } -// } -// -// func didPressSpeakerphone(sender button: UIButton) { -// Logger.info("") -// -// button.isSelected = !button.isSelected -// callUIAdapter.audioService.requestSpeakerphone(isEnabled: button.isSelected) -// } -// -// func didPressTextMessage(sender button: UIButton) { -// Logger.info("") -// -// dismissIfPossible(shouldDelay: false) -// } -// -// @objc func didPressAnswerCall(sender: UIButton) { -// Logger.info("") -// -// callUIAdapter.answerCall(call) -// } -// -// @objc func didPressVideo(sender: UIButton) { -// Logger.info("") -// let hasLocalVideo = !sender.isSelected -// -// callUIAdapter.setHasLocalVideo(call: call, hasLocalVideo: hasLocalVideo) -// } -// -// @objc func didPressFlipCamera(sender: UIButton) { -// sender.isSelected = !sender.isSelected -// -// let isUsingFrontCamera = !sender.isSelected -// Logger.info("with isUsingFrontCamera: \(isUsingFrontCamera)") -// -// callUIAdapter.setCameraSource(call: call, isUsingFrontCamera: isUsingFrontCamera) -// } -// -// /** -// * Denies an incoming not-yet-connected call, Do not confuse with `didPressHangup`. -// */ -// @objc func didPressDeclineCall(sender: UIButton) { -// Logger.info("") -// -// callUIAdapter.declineCall(call) -// -// dismissIfPossible(shouldDelay: false) -// } -// -// @objc func didPressShowCallSettings(sender: UIButton) { -// Logger.info("") -// -// markSettingsNagAsComplete() -// -// dismissIfPossible(shouldDelay: false, ignoreNag: true, completion: { -// // Find the frontmost presented UIViewController from which to present the -// // settings views. -// let fromViewController = UIApplication.shared.findFrontmostViewController(ignoringAlerts: true) -// assert(fromViewController != nil) -// -// // Construct the "settings" view & push the "privacy settings" view. -// let navigationController = AppSettingsViewController.inModalNavigationController() -// navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false) -// -// fromViewController?.present(navigationController, animated: true, completion: nil) -// }) -// } -// -// @objc func didPressDismissNag(sender: UIButton) { -// Logger.info("") -// -// markSettingsNagAsComplete() -// -// dismissIfPossible(shouldDelay: false, ignoreNag: true) -// } -// -// // We only show the "blocking" settings nag until the user has chosen -// // to view the privacy settings _or_ dismissed the nag at least once. -// // -// // In either case, we set the "CallKit enabled" and "CallKit privacy enabled" -// // settings to their default values to indicate that the user has reviewed -// // them. -// private func markSettingsNagAsComplete() { -// Logger.info("") -// -// let preferences = Environment.shared.preferences! -// -// preferences.setIsCallKitEnabled(preferences.isCallKitEnabled()) -// preferences.setIsCallKitPrivacyEnabled(preferences.isCallKitPrivacyEnabled()) -// } -// -// @objc func didTapLeaveCall(sender: UIButton) { -// OWSWindowManager.shared().leaveCallView() -// } -// -// // MARK: - CallObserver -// -// internal func stateDidChange(call: SignalCall, state: CallState) { -// AssertIsOnMainThread() -// Logger.info("new call status: \(state)") -// -// self.updateCallUI(callState: state) -// } -// -// internal func hasLocalVideoDidChange(call: SignalCall, hasLocalVideo: Bool) { -// AssertIsOnMainThread() -// self.updateCallUI(callState: call.state) -// } -// -// internal func muteDidChange(call: SignalCall, isMuted: Bool) { -// AssertIsOnMainThread() -// self.updateCallUI(callState: call.state) -// } -// -// func holdDidChange(call: SignalCall, isOnHold: Bool) { -// AssertIsOnMainThread() -// self.updateCallUI(callState: call.state) -// } -// -// internal func audioSourceDidChange(call: SignalCall, audioSource: AudioSource?) { -// AssertIsOnMainThread() -// self.updateCallUI(callState: call.state) -// } -// -// // MARK: - CallAudioServiceDelegate -// -// func callAudioService(_ callAudioService: CallAudioService, didUpdateIsSpeakerphoneEnabled isSpeakerphoneEnabled: Bool) { -// AssertIsOnMainThread() -// -// updateAudioSourceButtonIsSelected() -// } -// -// func callAudioServiceDidChangeAudioSession(_ callAudioService: CallAudioService) { -// AssertIsOnMainThread() -// -// // Which sources are available depends on the state of your Session. -// // When the audio session is not yet in PlayAndRecord none are available -// // Then if we're in speakerphone, bluetooth isn't available. -// // So we accrue all possible audio sources in a set, and that list lives as longs as the CallViewController -// // The downside of this is that if you e.g. unpair your bluetooth mid call, it will still appear as an option -// // until your next call. -// // FIXME: There's got to be a better way, but this is where I landed after a bit of work, and seems to work -// // pretty well in practice. -// let availableInputs = callAudioService.availableInputs -// self.allAudioSources.formUnion(availableInputs) -// } -// -// // MARK: - Video -// -// internal func updateLocalVideo(captureSession: AVCaptureSession?) { -// -// AssertIsOnMainThread() -// -// guard localVideoView.captureSession != captureSession else { -// Logger.debug("ignoring redundant update") -// return -// } -// -// localVideoView.captureSession = captureSession -// let isHidden = captureSession == nil -// -// Logger.info("isHidden: \(isHidden)") -// localVideoView.isHidden = isHidden -// -// updateLocalVideoLayout() -// updateAudioSourceButtonIsSelected() -// -// // Don't animate layout of local video view until it has been presented. -// if !isHidden { -// hasShownLocalVideo = true -// } -// } -// -// var hasRemoteVideoTrack: Bool { -// return self.remoteVideoTrack != nil -// } -// -// var hasUserDismissedVideoHint: Bool = false -// -// internal func updateRemoteVideoTrack(remoteVideoTrack: RTCVideoTrack?) { -// AssertIsOnMainThread() -// -// guard self.remoteVideoTrack != remoteVideoTrack else { -// Logger.debug("ignoring redundant update") -// return -// } -// -// self.remoteVideoTrack?.remove(remoteVideoView) -// self.remoteVideoTrack = nil -// remoteVideoView.renderFrame(nil) -// self.remoteVideoTrack = remoteVideoTrack -// self.remoteVideoTrack?.add(remoteVideoView) -// -// shouldRemoteVideoControlsBeHidden = false -// -// if remoteVideoTrack != nil { -// playRemoteEnabledVideoHapticFeedback() -// } -// -// updateRemoteVideoLayout() -// } -// -// // MARK: Video Haptics -// -// let feedbackGenerator = NotificationHapticFeedback() -// var lastHapticTime: TimeInterval = CACurrentMediaTime() -// func playRemoteEnabledVideoHapticFeedback() { -// let currentTime = CACurrentMediaTime() -// guard currentTime - lastHapticTime > 5 else { -// Logger.debug("ignoring haptic feedback since it's too soon") -// return -// } -// feedbackGenerator.notificationOccurred(.success) -// lastHapticTime = currentTime -// } -// -// // MARK: - Dismiss -// -// internal func dismissIfPossible(shouldDelay: Bool, ignoreNag ignoreNagParam: Bool = false, completion: (() -> Void)? = nil) { -// callUIAdapter.audioService.delegate = nil -// -// let ignoreNag: Bool = { -// // Nothing to nag about on iOS11 -// if #available(iOS 11, *) { -// return true -// } else { -// // otherwise on iOS10, nag as specified -// return ignoreNagParam -// } -// }() -// -// if hasDismissed { -// // Don't dismiss twice. -// return -// } else if !ignoreNag && -// call.direction == .incoming && -// UIDevice.current.supportsCallKit && -// (!Environment.shared.preferences.isCallKitEnabled() || -// Environment.shared.preferences.isCallKitPrivacyEnabled()) { -// -// isShowingSettingsNag = true -// -// // Update the nag view's copy to reflect the settings state. -// if Environment.shared.preferences.isCallKitEnabled() { -// settingsNagDescriptionLabel.text = NSLocalizedString("CALL_VIEW_SETTINGS_NAG_DESCRIPTION_PRIVACY", -// comment: "Reminder to the user of the benefits of disabling CallKit privacy.") -// } else { -// settingsNagDescriptionLabel.text = NSLocalizedString("CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL", -// comment: "Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy.") -// } -// settingsNagDescriptionLabel.superview?.setNeedsLayout() -// -// if Environment.shared.preferences.isCallKitEnabledSet() || -// Environment.shared.preferences.isCallKitPrivacySet() { -// // User has already touched these preferences, only show -// // the "fleeting" nag, not the "blocking" nag. -// -// // Show nag for N seconds. -// DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in -// guard let strongSelf = self else { return } -// strongSelf.dismissIfPossible(shouldDelay: false, ignoreNag: true) -// } -// } -// } else if shouldDelay { -// hasDismissed = true -// DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in -// guard let strongSelf = self else { return } -// strongSelf.dismissImmediately(completion: completion) -// } -// } else { -// hasDismissed = true -// dismissImmediately(completion: completion) -// } -// } -// -// internal func dismissImmediately(completion: (() -> Void)?) { -// if CallViewController.kShowCallViewOnSeparateWindow { -// OWSWindowManager.shared().endCall(self) -// completion?() -// } else { -// self.dismiss(animated: true, completion: completion) -// } -// } -// -// // MARK: - CallServiceObserver -// -// internal func didUpdateCall(call: SignalCall?) { -// // Do nothing. -// } -// -// internal func didUpdateVideoTracks(call: SignalCall?, -// localCaptureSession: AVCaptureSession?, -// remoteVideoTrack: RTCVideoTrack?) { -// AssertIsOnMainThread() -// -// updateLocalVideo(captureSession: localCaptureSession) -// updateRemoteVideoTrack(remoteVideoTrack: remoteVideoTrack) -// } -// -// // MARK: - Proximity Monitoring -// -// func ensureProximityMonitoring() { -// if #available(iOS 11, *) { -// // BUG: Adding `self` as a Weak reference to the proximityMonitoringManager results in -// // the CallViewController never being deallocated, which, besides being a memory leak -// // can interfere with subsequent video capture - presumably because the old capture -// // session is still retained via the callViewController.localVideoView. -// // -// // A code audit has not revealed a retain cycle. -// // -// // Using the XCode memory debugger shows that a strong reference is held by -// // windowManager.callNavigationController->_childViewControllers. -// // Even though, when inspecting via the debugger, the CallViewController is not shown as -// // a childViewController. -// // -// // (lldb) po [[[OWSWindowManager sharedManager] callNavigationController] childViewControllers] -// // <__NSSingleObjectArrayI 0x1c0418bd0>( -// // -// // ) -// // -// // Weirder still, when presenting another CallViewController, the old one remains unallocated -// // and inspecting it in the memory debugger shows _no_ strong references to it (yet it -// // is not deallocated). Some weak references do remain - from the proximityMonitoringManager -// // and the callObserver, both of which use the Weak struct, which could be related. -// // -// // In any case, we can apparently avoid this behavior by not adding self as a Weak lifetime -// // and as of iOS11, the system automatically managages proximityMonitoring -// // via CallKit and AudioSessions. Proximity monitoring will be enabled whenever a call -// // is active, unless we switch to VideoChat audio mode (which is actually desirable -// // behavior), so the proximityMonitoringManager is redundant for calls on iOS11+. -// } else { -// // before iOS11, manually enable proximityMonitoring while we're on a call. -// self.proximityMonitoringManager.add(lifetime: self) -// } -// } -//} -// -//extension CallViewController: CallVideoHintViewDelegate { -// func didTapCallVideoHintView(_ videoHintView: CallVideoHintView) { -// self.hasUserDismissedVideoHint = true -// updateRemoteVideoLayout() -// } -//} diff --git a/Session/Signal/CompareSafetyNumbersActivity.swift b/Session/Signal/CompareSafetyNumbersActivity.swift deleted file mode 100644 index 0d7a22222..000000000 --- a/Session/Signal/CompareSafetyNumbersActivity.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import SignalUtilitiesKit - -let CompareSafetyNumbersActivityType = "org.whispersystems.signal.activity.CompareSafetyNumbers" - -@objc(OWSCompareSafetyNumbersActivityDelegate) -protocol CompareSafetyNumbersActivityDelegate { - func compareSafetyNumbersActivitySucceeded(activity: CompareSafetyNumbersActivity) - func compareSafetyNumbersActivity(_ activity: CompareSafetyNumbersActivity, failedWithError error: Error) -} - -@objc (OWSCompareSafetyNumbersActivity) -class CompareSafetyNumbersActivity: UIActivity { - - var mySafetyNumbers: String? - let delegate: CompareSafetyNumbersActivityDelegate - - @objc - required init(delegate: CompareSafetyNumbersActivityDelegate) { - self.delegate = delegate - super.init() - } - - // MARK: UIActivity - - override class var activityCategory: UIActivity.Category { - get { return .action } - } - - override var activityType: UIActivity.ActivityType? { - get { return UIActivity.ActivityType(rawValue: CompareSafetyNumbersActivityType) } - } - - override var activityTitle: String? { - get { - return NSLocalizedString("COMPARE_SAFETY_NUMBER_ACTION", comment: "Activity Sheet label") - } - } - - override var activityImage: UIImage? { - get { - return #imageLiteral(resourceName: "ic_lock_outline") - } - } - - override func canPerform(withActivityItems activityItems: [Any]) -> Bool { - return stringsFrom(activityItems: activityItems).count > 0 - } - - override func prepare(withActivityItems activityItems: [Any]) { - let myFormattedSafetyNumbers = stringsFrom(activityItems: activityItems).first - mySafetyNumbers = numericOnly(string: myFormattedSafetyNumbers) - } - - override func perform() { - defer { activityDidFinish(true) } - - let pasteboardString = numericOnly(string: UIPasteboard.general.string) - guard (pasteboardString != nil && pasteboardString!.count == 60) else { - Logger.warn("no valid safety numbers found in pasteboard: \(String(describing: pasteboardString))") - let error = OWSErrorWithCodeDescription(OWSErrorCode.userError, - NSLocalizedString("PRIVACY_VERIFICATION_FAILED_NO_SAFETY_NUMBERS_IN_CLIPBOARD", comment: "Alert body for user error")) - - delegate.compareSafetyNumbersActivity(self, failedWithError: error) - return - } - - let pasteboardSafetyNumbers = pasteboardString! - - if pasteboardSafetyNumbers == mySafetyNumbers { - Logger.info("successfully matched safety numbers. local numbers: \(String(describing: mySafetyNumbers)) pasteboard:\(pasteboardSafetyNumbers)") - delegate.compareSafetyNumbersActivitySucceeded(activity: self) - } else { - Logger.warn("local numbers: \(String(describing: mySafetyNumbers)) didn't match pasteboard:\(pasteboardSafetyNumbers)") - let error = OWSErrorWithCodeDescription(OWSErrorCode.privacyVerificationFailure, - NSLocalizedString("PRIVACY_VERIFICATION_FAILED_MISMATCHED_SAFETY_NUMBERS_IN_CLIPBOARD", comment: "Alert body")) - delegate.compareSafetyNumbersActivity(self, failedWithError: error) - } - } - - // MARK: Helpers - - func numericOnly(string: String?) -> String? { - guard let string = string else { - return nil - } - - var numericOnly: String? - if let regex = try? NSRegularExpression(pattern: "\\D", options: .caseInsensitive) { - numericOnly = regex.stringByReplacingMatches(in: string, options: .withTransparentBounds, range: NSRange(location: 0, length: string.utf16.count), withTemplate: "") - } - - return numericOnly - } - - func stringsFrom(activityItems: [Any]) -> [String] { - return activityItems.map { $0 as? String }.filter { $0 != nil }.map { $0! } - } -} diff --git a/Session/Signal/ConversationView/TypingIndicatorInteraction.swift b/Session/Signal/ConversationView/Cells/TypingIndicatorInteraction.swift similarity index 100% rename from Session/Signal/ConversationView/TypingIndicatorInteraction.swift rename to Session/Signal/ConversationView/Cells/TypingIndicatorInteraction.swift diff --git a/Session/Signal/LegacyNotificationsAdaptee.swift b/Session/Signal/LegacyNotificationsAdaptee.swift index 6d92bd199..10cb0e538 100644 --- a/Session/Signal/LegacyNotificationsAdaptee.swift +++ b/Session/Signal/LegacyNotificationsAdaptee.swift @@ -18,30 +18,6 @@ struct LegacyNotificationConfig { static func notificationAction(_ action: AppNotificationAction) -> UIUserNotificationAction { switch action { -// case .answerCall: -// let mutableAction = UIMutableUserNotificationAction() -// mutableAction.identifier = action.identifier -// mutableAction.title = CallStrings.answerCallButtonTitle -// mutableAction.activationMode = .foreground -// mutableAction.isDestructive = false -// mutableAction.isAuthenticationRequired = false -// return mutableAction -// case .callBack: -// let mutableAction = UIMutableUserNotificationAction() -// mutableAction.identifier = action.identifier -// mutableAction.title = CallStrings.callBackButtonTitle -// mutableAction.activationMode = .foreground -// mutableAction.isDestructive = false -// mutableAction.isAuthenticationRequired = true -// return mutableAction -// case .declineCall: -// let mutableAction = UIMutableUserNotificationAction() -// mutableAction.identifier = action.identifier -// mutableAction.title = CallStrings.declineCallButtonTitle -// mutableAction.activationMode = .background -// mutableAction.isDestructive = false -// mutableAction.isAuthenticationRequired = false -// return mutableAction case .markAsRead: let mutableAction = UIMutableUserNotificationAction() mutableAction.identifier = action.identifier @@ -283,12 +259,6 @@ public class LegacyNotificationActionHandler: NSObject { } switch action { -// case .answerCall: -// return try actionHandler.answerCall(userInfo: userInfo) -// case .callBack: -// return try actionHandler.callBack(userInfo: userInfo) -// case .declineCall: -// return try actionHandler.declineCall(userInfo: userInfo) case .markAsRead: return try actionHandler.markAsRead(userInfo: userInfo) case .reply: diff --git a/Session/Signal/MessageDetailViewController.swift b/Session/Signal/MessageDetailViewController.swift index f752c5391..89d6cdcf2 100644 --- a/Session/Signal/MessageDetailViewController.swift +++ b/Session/Signal/MessageDetailViewController.swift @@ -546,8 +546,6 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele private func string(for messageReceiptStatus: MessageReceiptStatus) -> String { switch messageReceiptStatus { - case .calculatingPoW: - return NSLocalizedString("Calculating proof of work", comment: "") case .uploading: return NSLocalizedString("MESSAGE_METADATA_VIEW_MESSAGE_STATUS_UPLOADING", comment: "Status label for messages which are uploading.") diff --git a/Session/Signal/MessageRecipientStatusUtils.swift b/Session/Signal/MessageRecipientStatusUtils.swift index 5e7f8638e..52ac383ff 100644 --- a/Session/Signal/MessageRecipientStatusUtils.swift +++ b/Session/Signal/MessageRecipientStatusUtils.swift @@ -14,7 +14,6 @@ import SignalUtilitiesKit case read case failed case skipped - case calculatingPoW } @objc @@ -165,8 +164,6 @@ public class MessageRecipientStatusUtils: NSObject { return "failed" case .skipped: return "skipped" - case .calculatingPoW: - return "calculatingPoW" } } } diff --git a/Session/Signal/NotificationSettingsOptionsViewController.m b/Session/Signal/NotificationSettingsOptionsViewController.m index 2a15a33ba..8f675bd10 100644 --- a/Session/Signal/NotificationSettingsOptionsViewController.m +++ b/Session/Signal/NotificationSettingsOptionsViewController.m @@ -63,9 +63,6 @@ { [Environment.shared.preferences setNotificationPreviewType:notificationType]; - // rebuild callUIAdapter since notification configuration changed. -// [AppEnvironment.shared.callService createCallUIAdapter]; - [self.navigationController popViewControllerAnimated:YES]; } diff --git a/Session/Signal/OnboardingCaptchaViewController.swift b/Session/Signal/OnboardingCaptchaViewController.swift deleted file mode 100644 index a9f76abcc..000000000 --- a/Session/Signal/OnboardingCaptchaViewController.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -/* -import UIKit -import WebKit - -@objc -public class OnboardingCaptchaViewController: OnboardingBaseViewController { - - private var webView: WKWebView? - - override public func loadView() { - super.loadView() - - view.backgroundColor = Theme.backgroundColor - view.layoutMargins = .zero - - let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_CAPTCHA_TITLE", comment: "Title of the 'onboarding Captcha' view.")) - titleLabel.accessibilityIdentifier = "onboarding.captcha." + "titleLabel" - - let titleRow = UIStackView(arrangedSubviews: [ - titleLabel - ]) - titleRow.axis = .vertical - titleRow.alignment = .fill - titleRow.layoutMargins = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0) - titleRow.isLayoutMarginsRelativeArrangement = true - - // We want the CAPTCHA web content to "fill the screen (honoring margins)". - // The way to do this with WKWebView is to inject a javascript snippet that - // manipulates the viewport. - let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);" - let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true) - let wkUController = WKUserContentController() - wkUController.addUserScript(userScript) - let wkWebConfig = WKWebViewConfiguration() - wkWebConfig.userContentController = wkUController - let webView = WKWebView(frame: self.view.bounds, configuration: wkWebConfig) - self.webView = webView - webView.navigationDelegate = self - webView.allowsBackForwardNavigationGestures = false - webView.customUserAgent = "Signal iOS (+https://signal.org/download)" - webView.allowsLinkPreview = false - webView.scrollView.contentInset = .zero - webView.layoutMargins = .zero - webView.accessibilityIdentifier = "onboarding.captcha." + "webView" - - let stackView = UIStackView(arrangedSubviews: [ - titleRow, - webView - ]) - stackView.axis = .vertical - stackView.alignment = .fill - stackView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - stackView.isLayoutMarginsRelativeArrangement = true - view.addSubview(stackView) - stackView.autoPinWidthToSuperviewMargins() - stackView.autoPinEdge(.top, to: .top, of: view) - autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true) - - NotificationCenter.default.addObserver(self, - selector: #selector(didBecomeActive), - name: NSNotification.Name.OWSApplicationDidBecomeActive, - object: nil) - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - public override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - loadContent() - - webView?.scrollView.contentOffset = .zero - } - - public override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - webView?.scrollView.contentOffset = .zero - } - - fileprivate let contentUrl = "https://signalcaptchas.org/registration/generate.html" - - private func loadContent() { - guard let webView = webView else { - owsFailDebug("Missing webView.") - return - } - guard let url = URL(string: contentUrl) else { - owsFailDebug("Invalid URL.") - return - } - webView.load(URLRequest(url: url)) - webView.scrollView.contentOffset = .zero - } - - // MARK: - Notifications - - @objc func didBecomeActive() { - AssertIsOnMainThread() - - loadContent() - } - - // MARK: - - - private func parseCaptchaAndTryToRegister(url: URL) { - Logger.info("") - - guard let captchaToken = parseCaptcha(url: url) else { - owsFailDebug("Could not parse captcha token: \(url)") - // TODO: Alert? - // - // Reload content so user can try again. - loadContent() - return - } - onboardingController.update(captchaToken: captchaToken) - - onboardingController.tryToRegister(fromViewController: self, smsVerification: true) - } - - private func parseCaptcha(url: URL) -> String? { - Logger.info("") - - // Example URL: - // signalcaptcha://03AF6jDqXgf1PocNNrWRJEENZ9l6RAMIsUoESi2dFKkxTgE2qjdZGVjEW6SZNFQqeRRTgGqOii6zHGG--uLyC1HnhSmRt8wHeKxHcg1hsK4ucTusANIeFXVB8wPPiV7U_0w2jUFVak5clMCvW9_JBfbfzj51_e9sou8DYfwc_R6THuTBTdpSV8Nh0yJalgget-nSukCxh6FPA6hRVbw7lP3r-me1QCykHOfh-V29UVaQ4Fs5upHvwB5rtiViqT_HN8WuGmdIdGcaWxaqy1lQTgFSs2Shdj593wZiXfhJnCWAw9rMn3jSgIZhkFxdXwKOmslQ2E_I8iWkm6 - guard let host = url.host, - host.count > 0 else { - owsFailDebug("Missing host.") - return nil - } - - return host - } -} - -// MARK: - - -extension OnboardingCaptchaViewController: WKNavigationDelegate { - public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - Logger.verbose("navigationAction: \(String(describing: navigationAction.request.url))") - - guard let url: URL = navigationAction.request.url else { - owsFailDebug("Missing URL.") - decisionHandler(.cancel) - return - } - if url.scheme == "signalcaptcha" { - decisionHandler(.cancel) - DispatchQueue.main.async { - self.parseCaptchaAndTryToRegister(url: url) - } - return - } - - // Loading the Captcha content involves a series of actions. - decisionHandler(.allow) - } - - public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { - Logger.verbose("navigationResponse: \(String(describing: navigationResponse))") - - decisionHandler(.allow) - } - - public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { - Logger.verbose("navigation: \(String(describing: navigation))") - } - - public func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) { - Logger.verbose("navigation: \(String(describing: navigation))") - } - - public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - Logger.verbose("navigation: \(String(describing: navigation)), error: \(error)") - } - - public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - Logger.verbose("navigation: \(String(describing: navigation))") - } - - public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - Logger.verbose("navigation: \(String(describing: navigation))") - } - - public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - Logger.verbose("navigation: \(String(describing: navigation)), error: \(error)") - } - - public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { - Logger.verbose("") - } -} - */ - diff --git a/Session/Signal/PeerConnectionClient.swift b/Session/Signal/PeerConnectionClient.swift deleted file mode 100644 index 1376ebd26..000000000 --- a/Session/Signal/PeerConnectionClient.swift +++ /dev/null @@ -1,1378 +0,0 @@ -//// -//// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -//// -// -//import Foundation -//import PromiseKit -//import WebRTC -//import SignalUtilitiesKit -//import SignalUtilitiesKit -// -//// HACK - Seeing crazy SEGFAULTs on iOS9 when accessing these objc externs. -//// iOS10 seems unaffected. Reproducible for ~1 in 3 calls. -//// Binding them to a file constant seems to work around the problem. -//let kAudioTrackType = kRTCMediaStreamTrackKindAudio -//let kVideoTrackType = kRTCMediaStreamTrackKindVideo -//let kMediaConstraintsMinWidth = kRTCMediaConstraintsMinWidth -//let kMediaConstraintsMaxWidth = kRTCMediaConstraintsMaxWidth -//let kMediaConstraintsMinHeight = kRTCMediaConstraintsMinHeight -//let kMediaConstraintsMaxHeight = kRTCMediaConstraintsMaxHeight -// -///** -// * The PeerConnectionClient notifies it's delegate (the CallService) of key events in the call signaling life cycle -// * -// * The delegate's methods will always be called on the main thread. -// */ -//protocol PeerConnectionClientDelegate: class { -// -// /** -// * The connection has been established. The clients can now communicate. -// * This can be called multiple times throughout the call in the event of temporary network disconnects. -// */ -// func peerConnectionClientIceConnected(_ peerconnectionClient: PeerConnectionClient) -// -// /** -// * The connection failed to establish. The clients will not be able to communicate. -// */ -// func peerConnectionClientIceFailed(_ peerconnectionClient: PeerConnectionClient) -// -// /** -// * After initially connecting, the connection disconnected. -// * It maybe be temporary, in which case `peerConnectionClientIceConnected` will be called again once we're reconnected. -// * Otherwise, `peerConnectionClientIceFailed` will eventually called. -// */ -// func peerConnectionClientIceDisconnected(_ peerconnectionClient: PeerConnectionClient) -// -// /** -// * During the Signaling process each client generates IceCandidates locally, which contain information about how to -// * reach the local client via the internet. The delegate must shuttle these IceCandates to the other (remote) client -// * out of band, as part of establishing a connection over WebRTC. -// */ -// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, addedLocalIceCandidate iceCandidate: RTCIceCandidate) -// -// /** -// * Once the peerconnection is established, we can receive messages via the data channel, and notify the delegate. -// */ -// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, received dataChannelMessage: WebRTCProtoData) -// -// /** -// * Fired whenever the local video track become active or inactive. -// */ -// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, didUpdateLocalVideoCaptureSession captureSession: AVCaptureSession?) -// -// /** -// * Fired whenever the remote video track become active or inactive. -// */ -// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, didUpdateRemoteVideoTrack videoTrack: RTCVideoTrack?) -//} -// -//// In Swift (at least in Swift v3.3), weak variables aren't thread safe. It -//// isn't safe to resolve/acquire/lock a weak reference into a strong reference -//// while the instance might be being deallocated on another thread. -//// -//// PeerConnectionProxy provides thread-safe access to a strong reference. -//// PeerConnectionClient has an PeerConnectionProxy to itself that its many async blocks -//// (which run on more than one thread) can use to safely try to acquire a strong -//// reference to the PeerConnectionClient. In ARC we'd normally, we'd avoid -//// having an instance retain a strong reference to itself to avoid retain -//// cycles, but it's safe in this case: PeerConnectionClient is owned (and only -//// used by) a single entity CallService and CallService always calls -//// [PeerConnectionClient terminate] when it is done with a PeerConnectionClient -//// instance, so terminate is a reliable place where we can break the retain cycle. -//// -//// Note that we use the proxy in two ways: -//// -//// * As a delegate for the peer connection and the data channel, -//// safely forwarding delegate method invocations to the PCC. -//// * To safely obtain references to the PCC within the PCC's -//// async blocks. -//// -//// This should be fixed in Swift 4, but it isn't. -//// -//// To test using the following scenarios: -//// -//// * Alice and Bob place simultaneous calls to each other. Both should get busy. -//// Repeat 10-20x. Then verify that they can connect a call by having just one -//// call the other. -//// * Alice or Bob (randomly alternating) calls the other. Recipient (randomly) -//// accepts call or hangs up. If accepted, Alice or Bob (randomly) hangs up. -//// Repeat immediately, as fast as you can, 10-20x. -//class PeerConnectionProxy: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDelegate { -// -// private var value: PeerConnectionClient? -// -// deinit { -// Logger.info("[PeerConnectionProxy] deinit") -// } -// -// func set(value: PeerConnectionClient) { -// objc_sync_enter(self) -// self.value = value -// objc_sync_exit(self) -// } -// -// func get() -> PeerConnectionClient? { -// objc_sync_enter(self) -// let result = value -// objc_sync_exit(self) -// -// if result == nil { -// // Every time this method returns nil is a -// // possible crash avoided. -// Logger.verbose("cleared get.") -// } -// -// return result -// } -// -// func clear() { -// Logger.info("") -// -// objc_sync_enter(self) -// value = nil -// objc_sync_exit(self) -// } -// -// // MARK: - RTCPeerConnectionDelegate -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { -// self.get()?.peerConnection(peerConnection, didChange: stateChanged) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { -// self.get()?.peerConnection(peerConnection, didAdd: stream) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { -// self.get()?.peerConnection(peerConnection, didRemove: stream) -// } -// -// public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { -// self.get()?.peerConnectionShouldNegotiate(peerConnection) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { -// self.get()?.peerConnection(peerConnection, didChange: newState) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { -// self.get()?.peerConnection(peerConnection, didChange: newState) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { -// self.get()?.peerConnection(peerConnection, didGenerate: candidate) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { -// self.get()?.peerConnection(peerConnection, didRemove: candidates) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { -// self.get()?.peerConnection(peerConnection, didOpen: dataChannel) -// } -// -// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange connectionState: RTCPeerConnectionState) { -// self.get()?.peerConnection(peerConnection, didChange: connectionState) -// } -// -// // MARK: - RTCDataChannelDelegate -// -// public func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { -// self.get()?.dataChannelDidChangeState(dataChannel) -// } -// -// public func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { -// self.get()?.dataChannel(dataChannel, didReceiveMessageWith: buffer) -// } -// -// public func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) { -// self.get()?.dataChannel(dataChannel, didChangeBufferedAmount: amount) -// } -//} -// -///** -// * `PeerConnectionClient` is our interface to WebRTC. -// * -// * It is primarily a wrapper around `RTCPeerConnection`, which is responsible for sending and receiving our call data -// * including audio, video, and some post-connected signaling (hangup, add video) -// */ -//class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDelegate, VideoCaptureSettingsDelegate { -// -// enum Identifiers: String { -// case mediaStream = "ARDAMS", -// videoTrack = "ARDAMSv0", -// audioTrack = "ARDAMSa0", -// dataChannelSignaling = "signaling" -// } -// -// // A state in this class should only be accessed on this queue in order to -// // serialize access. -// // -// // This queue is also used to perform expensive calls to the WebRTC API. -// private static let signalingQueue = DispatchQueue(label: "CallServiceSignalingQueue") -// -// // Delegate is notified of key events in the call lifecycle. -// // -// // This property should only be accessed on the main thread. -// private weak var delegate: PeerConnectionClientDelegate? -// -// // Connection -// -// private var peerConnection: RTCPeerConnection? -// private let iceServers: [RTCIceServer] -// private let connectionConstraints: RTCMediaConstraints -// private let configuration: RTCConfiguration -// private let factory: RTCPeerConnectionFactory -// -// // DataChannel -// -// private var dataChannel: RTCDataChannel? -// -// // Audio -// -// private var audioSender: RTCRtpSender? -// private var audioTrack: RTCAudioTrack? -// private var audioConstraints: RTCMediaConstraints -// -// // Video -// -// private var videoCaptureController: VideoCaptureController? -// private var videoSender: RTCRtpSender? -// -// // RTCVideoTrack is fragile and prone to throwing exceptions and/or -// // causing deadlock in its destructor. Therefore we take great care -// // with this property. -// private var localVideoTrack: RTCVideoTrack? -// private var remoteVideoTrack: RTCVideoTrack? -// private var cameraConstraints: RTCMediaConstraints -// -// private let proxy = PeerConnectionProxy() -// // Note that we're deliberately leaking proxy instances using this -// // collection to avoid EXC_BAD_ACCESS. Calls are rare and the proxy -// // is tiny (a single property), so it's better to leak and be safe. -// private static var expiredProxies = [PeerConnectionProxy]() -// -// init(iceServers: [RTCIceServer], delegate: PeerConnectionClientDelegate, callDirection: CallDirection, useTurnOnly: Bool) { -// AssertIsOnMainThread() -// -// self.iceServers = iceServers -// self.delegate = delegate -// -// // Ensure we enable SW decoders to enable VP8 support -// let decoderFactory = RTCDefaultVideoDecoderFactory() -// let encoderFactory = RTCDefaultVideoEncoderFactory() -// let factory = RTCPeerConnectionFactory(encoderFactory: encoderFactory, decoderFactory: decoderFactory) -// -// self.factory = factory -// configuration = RTCConfiguration() -// configuration.iceServers = iceServers -// configuration.bundlePolicy = .maxBundle -// configuration.rtcpMuxPolicy = .require -// if useTurnOnly { -// Logger.debug("using iceTransportPolicy: relay") -// configuration.iceTransportPolicy = .relay -// } else { -// Logger.debug("using iceTransportPolicy: default") -// } -// -// let connectionConstraintsDict = ["DtlsSrtpKeyAgreement": "true"] -// connectionConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: connectionConstraintsDict) -// -// audioConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) -// cameraConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) -// -// super.init() -// -// proxy.set(value: self) -// -// peerConnection = factory.peerConnection(with: configuration, -// constraints: connectionConstraints, -// delegate: proxy) -// createAudioSender() -// createVideoSender() -// -// if callDirection == .outgoing { -// // When placing an outgoing call, it's our responsibility to create the DataChannel. -// // Recipient will not have to do this explicitly. -// createSignalingDataChannel() -// } -// } -// -// deinit { -// // TODO: We can demote this log level to debug once we're confident that -// // this class is always deallocated. -// Logger.info("[PeerConnectionClient] deinit") -// } -// -// // MARK: - Media Streams -// -// private func createSignalingDataChannel() { -// AssertIsOnMainThread() -// guard let peerConnection = peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// -// let configuration = RTCDataChannelConfiguration() -// // Insist upon an "ordered" TCP data channel for delivery reliability. -// configuration.isOrdered = true -// -// guard let dataChannel = peerConnection.dataChannel(forLabel: Identifiers.dataChannelSignaling.rawValue, -// configuration: configuration) else { -// -// // TODO fail outgoing call? -// owsFailDebug("dataChannel was unexpectedly nil") -// return -// } -// dataChannel.delegate = proxy -// -// assert(self.dataChannel == nil) -// self.dataChannel = dataChannel -// } -// -// // MARK: - Video -// -// fileprivate func createVideoSender() { -// AssertIsOnMainThread() -// Logger.debug("") -// assert(self.videoSender == nil, "\(#function) should only be called once.") -// -// guard !Platform.isSimulator else { -// Logger.warn("Refusing to create local video track on simulator which has no capture device.") -// return -// } -// guard let peerConnection = peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// -// let videoSource = factory.videoSource() -// -// let localVideoTrack = factory.videoTrack(with: videoSource, trackId: Identifiers.videoTrack.rawValue) -// self.localVideoTrack = localVideoTrack -// // Disable by default until call is connected. -// // FIXME - do we require mic permissions at this point? -// // if so maybe it would be better to not even add the track until the call is connected -// // instead of creating it and disabling it. -// localVideoTrack.isEnabled = false -// -// let capturer = RTCCameraVideoCapturer(delegate: videoSource) -// self.videoCaptureController = VideoCaptureController(capturer: capturer, settingsDelegate: self) -// -// let videoSender = peerConnection.sender(withKind: kVideoTrackType, streamId: Identifiers.mediaStream.rawValue) -// videoSender.track = localVideoTrack -// self.videoSender = videoSender -// } -// -// public func setCameraSource(isUsingFrontCamera: Bool) { -// AssertIsOnMainThread() -// -// let proxyCopy = self.proxy -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// -// guard let captureController = strongSelf.videoCaptureController else { -// owsFailDebug("captureController was unexpectedly nil") -// return -// } -// -// captureController.switchCamera(isUsingFrontCamera: isUsingFrontCamera) -// } -// } -// -// public func setLocalVideoEnabled(enabled: Bool) { -// AssertIsOnMainThread() -// let proxyCopy = self.proxy -// let completion = { -// guard let strongSelf = proxyCopy.get() else { return } -// guard let strongDelegate = strongSelf.delegate else { return } -// -// let captureSession: AVCaptureSession? = { -// guard enabled else { -// return nil -// } -// -// guard let captureController = strongSelf.videoCaptureController else { -// owsFailDebug("videoCaptureController was unexpectedly nil") -// return nil -// } -// -// return captureController.captureSession -// }() -// -// strongDelegate.peerConnectionClient(strongSelf, didUpdateLocalVideoCaptureSession: captureSession) -// } -// -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// guard strongSelf.peerConnection != nil else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// -// guard let videoCaptureController = strongSelf.videoCaptureController else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// -// guard let localVideoTrack = strongSelf.localVideoTrack else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// localVideoTrack.isEnabled = enabled -// -// if enabled { -// Logger.debug("starting video capture") -// videoCaptureController.startCapture() -// } else { -// Logger.debug("stopping video capture") -// videoCaptureController.stopCapture() -// } -// -// DispatchQueue.main.async(execute: completion) -// } -// } -// -// // MARK: VideoCaptureSettingsDelegate -// -// var videoWidth: Int32 { -// return 400 -// } -// -// var videoHeight: Int32 { -// return 400 -// } -// -// // MARK: - Audio -// -// fileprivate func createAudioSender() { -// AssertIsOnMainThread() -// Logger.debug("") -// assert(self.audioSender == nil, "\(#function) should only be called once.") -// -// guard let peerConnection = peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// -// let audioSource = factory.audioSource(with: self.audioConstraints) -// -// let audioTrack = factory.audioTrack(with: audioSource, trackId: Identifiers.audioTrack.rawValue) -// self.audioTrack = audioTrack -// -// // Disable by default until call is connected. -// // FIXME - do we require mic permissions at this point? -// // if so maybe it would be better to not even add the track until the call is connected -// // instead of creating it and disabling it. -// audioTrack.isEnabled = false -// -// let audioSender = peerConnection.sender(withKind: kAudioTrackType, streamId: Identifiers.mediaStream.rawValue) -// audioSender.track = audioTrack -// self.audioSender = audioSender -// } -// -// public func setAudioEnabled(enabled: Bool) { -// AssertIsOnMainThread() -// let proxyCopy = self.proxy -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// guard strongSelf.peerConnection != nil else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// guard let audioTrack = strongSelf.audioTrack else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// -// audioTrack.isEnabled = enabled -// } -// } -// -// // MARK: - Session negotiation -// -// private var defaultOfferConstraints: RTCMediaConstraints { -// let mandatoryConstraints = [ -// "OfferToReceiveAudio": "true", -// "OfferToReceiveVideo": "true" -// ] -// return RTCMediaConstraints(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil) -// } -// -// public func createOffer() -> Promise { -// AssertIsOnMainThread() -// let proxyCopy = self.proxy -// let (promise, resolver) = Promise.pending() -// let completion: ((RTCSessionDescription?, Error?) -> Void) = { (sdp, error) in -// guard let strongSelf = proxyCopy.get() else { -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// strongSelf.assertOnSignalingQueue() -// guard strongSelf.peerConnection != nil else { -// Logger.debug("Ignoring obsolete event in terminated client") -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// if let error = error { -// resolver.reject(error) -// return -// } -// -// guard let sessionDescription = sdp else { -// Logger.error("No session description was obtained, even though there was no error reported.") -// let error = OWSErrorMakeUnableToProcessServerResponseError() -// resolver.reject(error) -// return -// } -// -// resolver.fulfill(HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription)) -// } -// -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// strongSelf.assertOnSignalingQueue() -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// -// peerConnection.offer(for: strongSelf.defaultOfferConstraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in -// PeerConnectionClient.signalingQueue.async { -// completion(sdp, error) -// } -// }) -// } -// -// return promise -// } -// -// public func setLocalSessionDescriptionInternal(_ sessionDescription: HardenedRTCSessionDescription) -> Promise { -// let proxyCopy = self.proxy -// let (promise, resolver) = Promise.pending() -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// strongSelf.assertOnSignalingQueue() -// -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// -// Logger.verbose("setting local session description: \(sessionDescription)") -// peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, completionHandler: { (error) in -// if let error = error { -// resolver.reject(error) -// } else { -// resolver.fulfill(()) -// } -// }) -// } -// return promise -// } -// -// public func setLocalSessionDescription(_ sessionDescription: HardenedRTCSessionDescription) -> Promise { -// AssertIsOnMainThread() -// let proxyCopy = self.proxy -// let (promise, resolver) = Promise.pending() -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// strongSelf.assertOnSignalingQueue() -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// -// Logger.verbose("setting local session description: \(sessionDescription)") -// peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, -// completionHandler: { error in -// if let error = error { -// resolver.reject(error) -// return -// } -// resolver.fulfill(()) -// }) -// } -// -// return promise -// } -// -// public func negotiateSessionDescription(remoteDescription: RTCSessionDescription, constraints: RTCMediaConstraints) -> Promise { -// AssertIsOnMainThread() -// let proxyCopy = self.proxy -// return setRemoteSessionDescription(remoteDescription) -// .then(on: PeerConnectionClient.signalingQueue) { _ -> Promise in -// guard let strongSelf = proxyCopy.get() else { -// return Promise(error: NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// } -// return strongSelf.negotiateAnswerSessionDescription(constraints: constraints) -// } -// } -// -// public func setRemoteSessionDescription(_ sessionDescription: RTCSessionDescription) -> Promise { -// AssertIsOnMainThread() -// let proxyCopy = self.proxy -// let (promise, resolver) = Promise.pending() -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// strongSelf.assertOnSignalingQueue() -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// Logger.verbose("setting remote description: \(sessionDescription)") -// peerConnection.setRemoteDescription(sessionDescription, -// completionHandler: { error in -// if let error = error { -// resolver.reject(error) -// return -// } -// resolver.fulfill(()) -// }) -// } -// return promise -// } -// -// private func negotiateAnswerSessionDescription(constraints: RTCMediaConstraints) -> Promise { -// assertOnSignalingQueue() -// let proxyCopy = self.proxy -// let (promise, resolver) = Promise.pending() -// let completion: ((RTCSessionDescription?, Error?) -> Void) = { (sdp, error) in -// guard let strongSelf = proxyCopy.get() else { -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// strongSelf.assertOnSignalingQueue() -// guard strongSelf.peerConnection != nil else { -// Logger.debug("Ignoring obsolete event in terminated client") -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// if let error = error { -// resolver.reject(error) -// return -// } -// -// guard let sessionDescription = sdp else { -// Logger.error("unexpected empty session description, even though no error was reported.") -// let error = OWSErrorMakeUnableToProcessServerResponseError() -// resolver.reject(error) -// return -// } -// -// let hardenedSessionDescription = HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription) -// -// firstly { -// strongSelf.setLocalSessionDescriptionInternal(hardenedSessionDescription) -// }.done(on: PeerConnectionClient.signalingQueue) { -// resolver.fulfill(hardenedSessionDescription) -// }.catch { error in -// resolver.reject(error) -// }.retainUntilComplete() -// } -// -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// strongSelf.assertOnSignalingQueue() -// -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil)) -// return -// } -// -// Logger.debug("negotiating answer session.") -// -// peerConnection.answer(for: constraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in -// PeerConnectionClient.signalingQueue.async { -// completion(sdp, error) -// } -// }) -// } -// return promise -// } -// -// public func addRemoteIceCandidate(_ candidate: RTCIceCandidate) { -// let proxyCopy = self.proxy -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// Logger.info("adding remote ICE candidate: \(candidate.sdp)") -// peerConnection.add(candidate) -// } -// } -// -// public func terminate() { -// AssertIsOnMainThread() -// Logger.debug("") -// -// // Clear the delegate immediately so that we can guarantee that -// // no delegate methods are called after terminate() returns. -// delegate = nil -// -// // Clear the proxy immediately so that enqueued work is aborted -// // going forward. -// PeerConnectionClient.expiredProxies.append(proxy) -// proxy.clear() -// -// // Don't use [weak self]; we always want to perform terminateInternal(). -// PeerConnectionClient.signalingQueue.async { -// self.terminateInternal() -// } -// } -// -// private func terminateInternal() { -// assertOnSignalingQueue() -// Logger.debug("") -// -// // Some notes on preventing crashes while disposing of peerConnection for video calls -// // from: https://groups.google.com/forum/#!searchin/discuss-webrtc/objc$20crash$20dealloc%7Csort:relevance/discuss-webrtc/7D-vk5yLjn8/rBW2D6EW4GYJ -// // The sequence to make it work appears to be -// // -// // [capturer stop]; // I had to add this as a method to RTCVideoCapturer -// // [localRenderer stop]; -// // [remoteRenderer stop]; -// // [peerConnection close]; -// -// // audioTrack is a strong property because we need access to it to mute/unmute, but I was seeing it -// // become nil when it was only a weak property. So we retain it and manually nil the reference here, because -// // we are likely to crash if we retain any peer connection properties when the peerconnection is released -// -// localVideoTrack?.isEnabled = false -// remoteVideoTrack?.isEnabled = false -// -// if let dataChannel = self.dataChannel { -// dataChannel.delegate = nil -// } -// -// dataChannel = nil -// audioSender = nil -// audioTrack = nil -// videoSender = nil -// localVideoTrack = nil -// remoteVideoTrack = nil -// videoCaptureController = nil -// -// if let peerConnection = peerConnection { -// peerConnection.delegate = nil -// peerConnection.close() -// } -// peerConnection = nil -// } -// -// // MARK: - Data Channel -// -// // should only be accessed on PeerConnectionClient.signalingQueue -// var pendingDataChannelMessages: [PendingDataChannelMessage] = [] -// struct PendingDataChannelMessage { -// let data: Data -// let description: String -// let isCritical: Bool -// } -// -// public func sendDataChannelMessage(data: Data, description: String, isCritical: Bool) { -// AssertIsOnMainThread() -// let proxyCopy = self.proxy -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// -// guard strongSelf.peerConnection != nil else { -// Logger.debug("Ignoring obsolete event in terminated client: \(description)") -// return -// } -// -// guard let dataChannel = strongSelf.dataChannel else { -// if isCritical { -// Logger.info("enqueuing critical data channel message for after we have a dataChannel: \(description)") -// strongSelf.pendingDataChannelMessages.append(PendingDataChannelMessage(data: data, description: description, isCritical: isCritical)) -// } else { -// Logger.error("ignoring sending \(data) for nil dataChannel: \(description)") -// } -// return -// } -// -// Logger.debug("sendDataChannelMessage trying: \(description)") -// -// let buffer = RTCDataBuffer(data: data, isBinary: false) -// let result = dataChannel.sendData(buffer) -// -// if result { -// Logger.debug("sendDataChannelMessage succeeded: \(description)") -// } else { -// Logger.warn("sendDataChannelMessage failed: \(description)") -// if isCritical { -// OWSProdError(OWSAnalyticsEvents.peerConnectionClientErrorSendDataChannelMessageFailed(), file: #file, function: #function, line: #line) -// } -// } -// } -// } -// -// // MARK: RTCDataChannelDelegate -// -// /** The data channel state changed. */ -// internal func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { -// Logger.debug("dataChannelDidChangeState: \(dataChannel)") -// } -// -// /** The data channel successfully received a data buffer. */ -// internal func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { -// let proxyCopy = self.proxy -// let completion: (WebRTCProtoData) -> Void = { (dataChannelMessage) in -// AssertIsOnMainThread() -// guard let strongSelf = proxyCopy.get() else { return } -// guard let strongDelegate = strongSelf.delegate else { return } -// strongDelegate.peerConnectionClient(strongSelf, received: dataChannelMessage) -// } -// -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// guard strongSelf.peerConnection != nil else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// Logger.debug("dataChannel didReceiveMessageWith buffer:\(buffer)") -// -// var dataChannelMessage: WebRTCProtoData -// do { -// dataChannelMessage = try WebRTCProtoData.parseData(buffer.data) -// } catch { -// Logger.error("failed to parse dataProto") -// return -// } -// -// DispatchQueue.main.async { -// completion(dataChannelMessage) -// } -// } -// } -// -// /** The data channel's |bufferedAmount| changed. */ -// internal func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) { -// Logger.debug("didChangeBufferedAmount: \(amount)") -// } -// -// // MARK: - RTCPeerConnectionDelegate -// -// /** Called when the SignalingState changed. */ -// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { -// Logger.debug("didChange signalingState:\(stateChanged.debugDescription)") -// } -// -// /** Called when media is received on a new stream from remote peer. */ -// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didAdd stream: RTCMediaStream) { -// let proxyCopy = self.proxy -// let completion: (RTCVideoTrack) -> Void = { (remoteVideoTrack) in -// AssertIsOnMainThread() -// guard let strongSelf = proxyCopy.get() else { return } -// guard let strongDelegate = strongSelf.delegate else { return } -// -// // TODO: Consider checking for termination here. -// -// strongDelegate.peerConnectionClient(strongSelf, didUpdateRemoteVideoTrack: remoteVideoTrack) -// } -// -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// guard peerConnection == peerConnectionParam else { -// owsFailDebug("mismatched peerConnection callback.") -// return -// } -// guard stream.videoTracks.count > 0 else { -// owsFailDebug("didAdd stream missing stream.") -// return -// } -// let remoteVideoTrack = stream.videoTracks[0] -// Logger.debug("didAdd stream:\(stream) video tracks: \(stream.videoTracks.count) audio tracks: \(stream.audioTracks.count)") -// -// strongSelf.remoteVideoTrack = remoteVideoTrack -// -// DispatchQueue.main.async { -// completion(remoteVideoTrack) -// } -// } -// } -// -// /** Called when a remote peer closes a stream. */ -// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didRemove stream: RTCMediaStream) { -// Logger.debug("didRemove Stream:\(stream)") -// } -// -// /** Called when negotiation is needed, for example ICE has restarted. */ -// internal func peerConnectionShouldNegotiate(_ peerConnectionParam: RTCPeerConnection) { -// Logger.debug("shouldNegotiate") -// } -// -// /** Called any time the IceConnectionState changes. */ -// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didChange newState: RTCIceConnectionState) { -// let proxyCopy = self.proxy -// let connectedCompletion : () -> Void = { -// AssertIsOnMainThread() -// guard let strongSelf = proxyCopy.get() else { return } -// guard let strongDelegate = strongSelf.delegate else { return } -// strongDelegate.peerConnectionClientIceConnected(strongSelf) -// } -// let failedCompletion : () -> Void = { -// AssertIsOnMainThread() -// guard let strongSelf = proxyCopy.get() else { return } -// guard let strongDelegate = strongSelf.delegate else { return } -// strongDelegate.peerConnectionClientIceFailed(strongSelf) -// } -// let disconnectedCompletion : () -> Void = { -// AssertIsOnMainThread() -// guard let strongSelf = proxyCopy.get() else { return } -// guard let strongDelegate = strongSelf.delegate else { return } -// strongDelegate.peerConnectionClientIceDisconnected(strongSelf) -// } -// -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// guard peerConnection == peerConnectionParam else { -// owsFailDebug("mismatched peerConnection callback.") -// return -// } -// -// Logger.info("didChange IceConnectionState:\(newState.debugDescription)") -// switch newState { -// case .connected, .completed: -// DispatchQueue.main.async(execute: connectedCompletion) -// case .failed: -// Logger.warn("RTCIceConnection failed.") -// DispatchQueue.main.async(execute: failedCompletion) -// case .disconnected: -// Logger.warn("RTCIceConnection disconnected.") -// DispatchQueue.main.async(execute: disconnectedCompletion) -// default: -// Logger.debug("ignoring change IceConnectionState:\(newState.debugDescription)") -// } -// } -// } -// -// /** Called any time the IceGatheringState changes. */ -// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didChange newState: RTCIceGatheringState) { -// Logger.info("didChange IceGatheringState:\(newState.debugDescription)") -// } -// -// /** New ice candidate has been found. */ -// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { -// let proxyCopy = self.proxy -// let completion: (RTCIceCandidate) -> Void = { (candidate) in -// AssertIsOnMainThread() -// guard let strongSelf = proxyCopy.get() else { return } -// guard let strongDelegate = strongSelf.delegate else { return } -// strongDelegate.peerConnectionClient(strongSelf, addedLocalIceCandidate: candidate) -// } -// -// PeerConnectionClient.signalingQueue.async { -// guard let strongSelf = proxyCopy.get() else { return } -// guard let peerConnection = strongSelf.peerConnection else { -// Logger.debug("Ignoring obsolete event in terminated client") -// return -// } -// guard peerConnection == peerConnectionParam else { -// owsFailDebug("mismatched peerConnection callback.") -// return -// } -// Logger.info("adding local ICE candidate:\(candidate.sdp)") -// DispatchQueue.main.async { -// completion(candidate) -// } -// } -// } -// -// /** Called when a group of local Ice candidates have been removed. */ -// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { -// Logger.debug("didRemove IceCandidates:\(candidates)") -// } -// -//