diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 0dba75b4d..2b4c0a184 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -506,6 +506,7 @@ 4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */; }; 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC613352227A00400E21A3A /* ConversationSearch.swift */; }; 4CEB78C92178EBAB00F315D2 /* OWSSessionResetJobRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CEB78C82178EBAB00F315D2 /* OWSSessionResetJobRecord.m */; }; + 4CFD151D22415AA400F2450F /* CallVideoHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFD151C22415AA400F2450F /* CallVideoHintView.swift */; }; 4CFE6B6C21F92BA700006701 /* LegacyNotificationsAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFE6B6B21F92BA700006701 /* LegacyNotificationsAdaptee.swift */; }; 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; }; 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; }; @@ -1256,6 +1257,7 @@ 4CEB78C72178EBAB00F315D2 /* OWSSessionResetJobRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWSSessionResetJobRecord.h; sourceTree = ""; }; 4CEB78C82178EBAB00F315D2 /* OWSSessionResetJobRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWSSessionResetJobRecord.m; sourceTree = ""; }; 4CFB4E9B220BC56D00ECB4DE /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = translations/nb.lproj/Localizable.strings; sourceTree = ""; }; + 4CFD151C22415AA400F2450F /* CallVideoHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVideoHintView.swift; sourceTree = ""; }; 4CFE6B6B21F92BA700006701 /* LegacyNotificationsAdaptee.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LegacyNotificationsAdaptee.swift; path = UserInterface/Notifications/LegacyNotificationsAdaptee.swift; sourceTree = ""; }; 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuActionsViewController.swift; sourceTree = ""; }; 69349DE607F5BA6036C9AC60 /* Pods-SignalShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.debug.xcconfig"; sourceTree = ""; }; @@ -1883,11 +1885,11 @@ 34B3F8331E8DF1700035BE1A /* ViewControllers */ = { isa = PBXGroup; children = ( + 4CFD151B22415A6C00F2450F /* Call */, 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */, 340FC87A204DAC8C007AEB0F /* AppSettings */, 34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */, 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */, - 34B3F83B1E8DF1700035BE1A /* CallViewController.swift */, 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */, 348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */, 34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */, @@ -2335,6 +2337,15 @@ path = SSKTests; sourceTree = ""; }; + 4CFD151B22415A6C00F2450F /* Call */ = { + isa = PBXGroup; + children = ( + 34B3F83B1E8DF1700035BE1A /* CallViewController.swift */, + 4CFD151C22415AA400F2450F /* CallVideoHintView.swift */, + ); + path = Call; + sourceTree = ""; + }; 76EB03C118170B33006006FC /* src */ = { isa = PBXGroup; children = ( @@ -3551,6 +3562,7 @@ 348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */, 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */, 34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */, + 4CFD151D22415AA400F2450F /* CallVideoHintView.swift in Sources */, 34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */, 343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */, 34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */, diff --git a/Signal/src/ViewControllers/Call/CallVideoHintView.swift b/Signal/src/ViewControllers/Call/CallVideoHintView.swift new file mode 100644 index 000000000..4a6809e97 --- /dev/null +++ b/Signal/src/ViewControllers/Call/CallVideoHintView.swift @@ -0,0 +1,83 @@ +// +// 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() + 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/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/Call/CallViewController.swift similarity index 97% rename from Signal/src/ViewControllers/CallViewController.swift rename to Signal/src/ViewControllers/Call/CallViewController.swift index cb6b5360b..758914d33 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/Call/CallViewController.swift @@ -234,8 +234,10 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, func createViews() { self.view.isUserInteractionEnabled = true - self.view.addGestureRecognizer(OWSAnyTouchGestureRecognizer(target: self, - action: #selector(didTouchRootView))) + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, + action: #selector(didTouchRootView))) + + videoHintView.delegate = self // Dark blurred background. let blurEffect = UIBlurEffect(style: .dark) @@ -596,6 +598,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, updateCallUI(callState: call.state) } + let videoHintView = CallVideoHintView() + internal func updateLocalVideoLayout() { if !localVideoView.isHidden { localVideoView.superview?.bringSubview(toFront: localVideoView) @@ -744,10 +748,20 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, 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 = { @@ -1040,6 +1054,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, return self.remoteVideoTrack != nil } + var hasUserDismissedVideoHint: Bool = false + internal func updateRemoteVideoTrack(remoteVideoTrack: RTCVideoTrack?) { AssertIsOnMainThread() guard self.remoteVideoTrack != remoteVideoTrack else { @@ -1051,6 +1067,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, remoteVideoView.renderFrame(nil) self.remoteVideoTrack = remoteVideoTrack self.remoteVideoTrack?.add(remoteVideoView) + shouldRemoteVideoControlsBeHidden = false updateRemoteVideoLayout() @@ -1174,3 +1191,10 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } } + +extension CallViewController: CallVideoHintViewDelegate { + func didTapCallVideoHintView(_ videoHintView: CallVideoHintView) { + self.hasUserDismissedVideoHint = true + updateRemoteVideoLayout() + } +} diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 8b2e4cdeb..dfbc5329e 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -350,6 +350,9 @@ /* Accessibility label for declining incoming calls */ "CALL_VIEW_DECLINE_INCOMING_CALL_LABEL" = "Decline incoming call"; +/* tooltip label when remote party has enabled their video */ +"CALL_VIEW_ENABLE_VIDEO_HINT" = "Tap here to turn on your video"; + /* Accessibility label for hang up call */ "CALL_VIEW_HANGUP_LABEL" = "End call";