Restyle return to call view
This commit is contained in:
parent
7f935a2f0e
commit
dc543ce24f
@ -31,6 +31,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
private lazy var blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||
private lazy var backgroundAvatarView = UIImageView()
|
||||
private lazy var dateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
@ -190,6 +191,13 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
|
||||
self.shouldUseTheme = false
|
||||
}
|
||||
|
||||
deinit {
|
||||
// These views might be in the return to call PIP's hierarchy,
|
||||
// we want to remove them so they are free'd when the call ends
|
||||
remoteVideoView.removeFromSuperview()
|
||||
localVideoView.removeFromSuperview()
|
||||
}
|
||||
|
||||
@objc func didBecomeActive() {
|
||||
if self.isViewLoaded {
|
||||
shouldRemoteVideoControlsBeHidden = false
|
||||
@ -213,6 +221,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
|
||||
|
||||
override func loadView() {
|
||||
view = UIView()
|
||||
view.clipsToBounds = true
|
||||
view.backgroundColor = UIColor.black
|
||||
view.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20)
|
||||
|
||||
@ -271,8 +280,6 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
|
||||
backgroundAvatarView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
// Dark blurred background.
|
||||
let blurEffect = UIBlurEffect(style: .dark)
|
||||
let blurView = UIVisualEffectView(effect: blurEffect)
|
||||
blurView.isUserInteractionEnabled = false
|
||||
view.addSubview(blurView)
|
||||
blurView.autoPinEdgesToSuperviewEdges()
|
||||
@ -587,11 +594,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
|
||||
private func nearestValidLocalVideoFrame(for origin: CGPoint) -> CGRect {
|
||||
var newFrame = CGRect(
|
||||
origin: origin,
|
||||
// The PIP has a fixed size for iPhone and iPad
|
||||
size: CGSize(
|
||||
width: UIDevice.current.isIPad ? 272 : 90,
|
||||
height: UIDevice.current.isIPad ? 154 : 160
|
||||
)
|
||||
size: ReturnToCallViewController.pipSize
|
||||
)
|
||||
|
||||
let boundingRect = localVideoBoundingRect
|
||||
@ -631,6 +634,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
|
||||
}
|
||||
|
||||
private func updateLocalVideoLayout() {
|
||||
guard localVideoView.superview == view else { return }
|
||||
|
||||
guard !isRenderingLocalVanityVideo else {
|
||||
view.layoutIfNeeded()
|
||||
localVideoView.frame = view.frame
|
||||
@ -1431,3 +1436,57 @@ extension CallViewController: UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CallViewController: CallViewControllerWindowReference {
|
||||
var remoteVideoViewReference: UIView { remoteVideoView }
|
||||
var localVideoViewReference: UIView { localVideoView }
|
||||
|
||||
@objc
|
||||
public func returnFromPip(pipWindow: UIWindow) {
|
||||
// The call "pip" uses our remote and local video views since only
|
||||
// one `AVCaptureVideoPreviewLayer` per capture session is supported.
|
||||
// We need to re-add them when we return to this view.
|
||||
guard remoteVideoView.superview != view && localVideoView.superview != view else {
|
||||
return owsFailDebug("unexpectedly returned to call while we own the video views")
|
||||
}
|
||||
|
||||
guard let splitViewSnapshot = SignalApp.shared().snapshotSplitViewController(afterScreenUpdates: false) else {
|
||||
return owsFailDebug("failed to snapshot rootViewController")
|
||||
}
|
||||
|
||||
guard let pipSnapshot = pipWindow.snapshotView(afterScreenUpdates: false) else {
|
||||
return owsFailDebug("failed to snapshot pip")
|
||||
}
|
||||
|
||||
view.insertSubview(remoteVideoView, aboveSubview: blurView)
|
||||
remoteVideoView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
view.insertSubview(localVideoView, aboveSubview: contactAvatarContainerView)
|
||||
|
||||
shouldRemoteVideoControlsBeHidden = false
|
||||
|
||||
animateReturnFromPip(pipSnapshot: pipSnapshot, pipFrame: pipWindow.frame, splitViewSnapshot: splitViewSnapshot)
|
||||
}
|
||||
|
||||
private func animateReturnFromPip(pipSnapshot: UIView, pipFrame: CGRect, splitViewSnapshot: UIView) {
|
||||
guard let window = view.window else { return owsFailDebug("missing window") }
|
||||
view.superview?.insertSubview(splitViewSnapshot, belowSubview: view)
|
||||
splitViewSnapshot.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
view.frame = pipFrame
|
||||
view.addSubview(pipSnapshot)
|
||||
pipSnapshot.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
view.layoutIfNeeded()
|
||||
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
pipSnapshot.alpha = 0
|
||||
self.view.frame = window.frame
|
||||
self.view.layoutIfNeeded()
|
||||
}) { _ in
|
||||
self.updateCallUI()
|
||||
splitViewSnapshot.removeFromSuperview()
|
||||
pipSnapshot.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (BOOL)receivedVerificationCode:(NSString *)verificationCode;
|
||||
- (void)applicationWillTerminate;
|
||||
|
||||
- (nullable UIView *)snapshotSplitViewControllerAfterScreenUpdates:(BOOL)afterScreenUpdates;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -365,6 +365,11 @@ NSString *const kNSUserDefaults_DidTerminateKey = @"kNSUserDefaults_DidTerminate
|
||||
[self.conversationSplitViewController showNewConversationView];
|
||||
}
|
||||
|
||||
- (nullable UIView *)snapshotSplitViewControllerAfterScreenUpdates:(BOOL)afterScreenUpdates
|
||||
{
|
||||
return [self.conversationSplitViewController.view snapshotViewAfterScreenUpdates:afterScreenUpdates];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UINavigationController (OWSNavigationController) <UINavigationBarDelegate, NavBarLayoutDelegate>
|
||||
@interface UINavigationController (OWSNavigationController) <UINavigationBarDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@ -30,7 +30,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
[self setupNavbar];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(themeDidChange:)
|
||||
name:ThemeDidChangeNotification
|
||||
object:nil];
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -77,24 +81,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return [super prefersStatusBarHidden];
|
||||
}
|
||||
|
||||
#pragma mark - UINavigationBarDelegate
|
||||
|
||||
- (void)setupNavbar
|
||||
{
|
||||
if (![self.navigationBar isKindOfClass:[OWSNavigationBar class]]) {
|
||||
OWSFailDebug(@"navigationBar was unexpected class: %@", self.navigationBar);
|
||||
return;
|
||||
}
|
||||
OWSNavigationBar *navbar = (OWSNavigationBar *)self.navigationBar;
|
||||
navbar.navBarLayoutDelegate = self;
|
||||
[self updateLayoutForNavbar:navbar];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(themeDidChange:)
|
||||
name:ThemeDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
// All OWSNavigationController serve as the UINavigationBarDelegate for their navbar.
|
||||
// We override shouldPopItem: in order to cancel some back button presses - for example,
|
||||
// if a view has unsaved changes.
|
||||
@ -148,21 +134,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NavBarLayoutDelegate
|
||||
|
||||
- (void)navBarCallLayoutDidChangeWithNavbar:(OWSNavigationBar *)navbar
|
||||
{
|
||||
[self updateLayoutForNavbar:navbar];
|
||||
[self setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
if (!CurrentAppContext().isMainApp) {
|
||||
return super.preferredStatusBarStyle;
|
||||
} else if (OWSWindowManager.sharedManager.hasCall) {
|
||||
// Status bar is overlaying the green "call banner"
|
||||
return UIStatusBarStyleLightContent;
|
||||
} else {
|
||||
UIViewController *presentedViewController = self.presentedViewController;
|
||||
if (presentedViewController != nil && !presentedViewController.isBeingDismissed) {
|
||||
@ -173,26 +148,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateLayoutForNavbar:(OWSNavigationBar *)navbar
|
||||
{
|
||||
OWSLogDebug(@"");
|
||||
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
|
||||
if (!CurrentAppContext().isMainApp) {
|
||||
self.additionalSafeAreaInsets = UIEdgeInsetsZero;
|
||||
} else if (OWSWindowManager.sharedManager.hasCall) {
|
||||
self.additionalSafeAreaInsets = UIEdgeInsetsMake(20, 0, 0, 0);
|
||||
} else {
|
||||
self.additionalSafeAreaInsets = UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
// we have to ensure the navbar frame *in* layoutSubviews.
|
||||
[navbar layoutSubviews];
|
||||
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
|
||||
@ -1,9 +1,19 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc
|
||||
public protocol CallViewControllerWindowReference: class {
|
||||
var localVideoViewReference: UIView { get }
|
||||
var remoteVideoViewReference: UIView { get }
|
||||
var thread: TSContactThread { get }
|
||||
var view: UIView { get }
|
||||
|
||||
func returnFromPip(pipWindow: UIWindow)
|
||||
}
|
||||
|
||||
@objc
|
||||
public protocol ReturnToCallViewControllerDelegate: class {
|
||||
func returnToCallWasTapped(_ viewController: ReturnToCallViewController)
|
||||
@ -12,79 +22,207 @@ public protocol ReturnToCallViewControllerDelegate: class {
|
||||
@objc
|
||||
public class ReturnToCallViewController: UIViewController {
|
||||
|
||||
@objc
|
||||
public static var pipSize = UIDevice.current.isIPad ? CGSize(width: 272, height: 204) : CGSize(width: 90, height: 160)
|
||||
|
||||
@objc
|
||||
public weak var delegate: ReturnToCallViewControllerDelegate?
|
||||
|
||||
let returnToCallLabel = UILabel()
|
||||
private weak var callViewController: CallViewControllerWindowReference?
|
||||
|
||||
@objc
|
||||
public func startAnimating() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didTapStatusBar(notification:)), name: .TappedStatusBar, object: nil)
|
||||
self.returnToCallLabel.layer.removeAllAnimations()
|
||||
self.returnToCallLabel.alpha = 1
|
||||
UIView.animate(withDuration: 1,
|
||||
delay: 0,
|
||||
options: [.repeat, .autoreverse],
|
||||
animations: { self.returnToCallLabel.alpha = 0 },
|
||||
completion: { _ in self.returnToCallLabel.alpha = 1 })
|
||||
public func displayForCallViewController(_ callViewController: CallViewControllerWindowReference) {
|
||||
guard callViewController !== self.callViewController else { return }
|
||||
|
||||
guard let callViewSnapshot = callViewController.view.snapshotView(afterScreenUpdates: false) else {
|
||||
return owsFailDebug("failed to snapshot call view")
|
||||
}
|
||||
|
||||
self.callViewController = callViewController
|
||||
|
||||
view.addSubview(callViewController.remoteVideoViewReference)
|
||||
callViewController.remoteVideoViewReference.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
view.addSubview(callViewController.localVideoViewReference)
|
||||
callViewController.localVideoViewReference.layer.cornerRadius = 6
|
||||
|
||||
let localVideoSize = CGSizeScale(Self.pipSize, 0.3)
|
||||
callViewController.localVideoViewReference.frame = CGRect(
|
||||
origin: CGPoint(
|
||||
x: Self.pipSize.width - 6 - localVideoSize.width,
|
||||
y: Self.pipSize.height - 6 - localVideoSize.height
|
||||
),
|
||||
size: localVideoSize
|
||||
)
|
||||
|
||||
backgroundAvatarView.image = Environment.shared.contactsManager.profileImageForAddress(
|
||||
withSneakyTransaction: callViewController.thread.contactAddress
|
||||
)
|
||||
avatarView.image = OWSAvatarBuilder.buildImage(thread: callViewController.thread, diameter: 60)
|
||||
|
||||
animatePipPresentation(snapshot: callViewSnapshot)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func stopAnimating() {
|
||||
NotificationCenter.default.removeObserver(self, name: .TappedStatusBar, object: nil)
|
||||
self.returnToCallLabel.layer.removeAllAnimations()
|
||||
}
|
||||
private lazy var avatarView = AvatarImageView()
|
||||
private lazy var backgroundAvatarView = UIImageView()
|
||||
private lazy var blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||
|
||||
override public func loadView() {
|
||||
self.view = UIView()
|
||||
view = UIView()
|
||||
|
||||
// This is the color of the iOS "return to call" banner.
|
||||
view.backgroundColor = UIColor(rgbHex: 0x4cd964)
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
|
||||
view.addGestureRecognizer(tapGesture)
|
||||
view.backgroundColor = .black
|
||||
view.clipsToBounds = true
|
||||
view.layer.cornerRadius = 8
|
||||
|
||||
view.addSubview(returnToCallLabel)
|
||||
backgroundAvatarView.contentMode = .scaleAspectFill
|
||||
view.addSubview(backgroundAvatarView)
|
||||
backgroundAvatarView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
// System UI doesn't use dynamic type for status bar; neither do we.
|
||||
returnToCallLabel.font = UIFont.ows_regularFont(withSize: 14)
|
||||
returnToCallLabel.text = NSLocalizedString("CALL_WINDOW_RETURN_TO_CALL", comment: "Label for the 'return to call' banner.")
|
||||
returnToCallLabel.textColor = .white
|
||||
view.addSubview(blurView)
|
||||
blurView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
returnToCallLabel.autoPinEdge(toSuperviewEdge: .bottom, withInset: 2)
|
||||
returnToCallLabel.setCompressionResistanceHigh()
|
||||
returnToCallLabel.setContentHuggingHigh()
|
||||
returnToCallLabel.autoHCenterInSuperview()
|
||||
view.addSubview(avatarView)
|
||||
avatarView.autoSetDimensions(to: CGSize(square: 60))
|
||||
avatarView.autoCenterInSuperview()
|
||||
|
||||
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
|
||||
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan)))
|
||||
}
|
||||
|
||||
public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { _ in
|
||||
OWSWindowManager.shared.ensureReturnToCallWindowFrame()
|
||||
coordinator.animate(alongsideTransition: { [weak self] _ in
|
||||
self?.updatePipLayout()
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didTapView(gestureRecognizer: UITapGestureRecognizer) {
|
||||
self.delegate?.returnToCallWasTapped(self)
|
||||
// MARK: -
|
||||
|
||||
private func animatePipPresentation(snapshot: UIView) {
|
||||
guard let window = view.window else { return owsFailDebug("missing window") }
|
||||
let previousOrigin = window.frame.origin
|
||||
window.frame = OWSWindowManager.shared.rootWindow.bounds
|
||||
|
||||
view.addSubview(snapshot)
|
||||
snapshot.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
window.layoutIfNeeded()
|
||||
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
snapshot.alpha = 0
|
||||
window.frame = self.nearestValidPipFrame(for: previousOrigin)
|
||||
window.layoutIfNeeded()
|
||||
}) { _ in
|
||||
snapshot.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
private var pipBoundingRect: CGRect {
|
||||
let padding: CGFloat = 4
|
||||
var rect = CurrentAppContext().frame
|
||||
|
||||
let safeAreaInsets = OWSWindowManager.shared.rootWindow.safeAreaInsets
|
||||
|
||||
let leftInset = safeAreaInsets.left + padding
|
||||
let rightInset = safeAreaInsets.right + padding
|
||||
rect.origin.x += leftInset
|
||||
rect.size.width -= leftInset + rightInset
|
||||
|
||||
let topInset = safeAreaInsets.top + padding
|
||||
let bottomInset = safeAreaInsets.bottom + padding
|
||||
rect.origin.y += topInset
|
||||
rect.size.height -= topInset + bottomInset
|
||||
|
||||
return rect
|
||||
}
|
||||
|
||||
private func nearestValidPipFrame(for origin: CGPoint) -> CGRect {
|
||||
var newFrame = CGRect(origin: origin, size: Self.pipSize)
|
||||
|
||||
let boundingRect = pipBoundingRect
|
||||
|
||||
// If the origin is zero, we always want to position
|
||||
// the pip in the top right
|
||||
let hasZeroOrigin = newFrame.origin == .zero
|
||||
|
||||
// If we're positioned outside of the vertical bounds, we
|
||||
// want to position the pip at the nearest bound
|
||||
let positionedOutOfVerticalBounds = newFrame.minY < boundingRect.minY || newFrame.maxY > boundingRect.maxY
|
||||
|
||||
// If we're position anywhere but exactly at the horizontal
|
||||
// edges, we want to position the pip at the nearest edge
|
||||
let positionedAwayFromHorizontalEdges = boundingRect.minX != newFrame.minX && boundingRect.maxX != newFrame.maxX
|
||||
|
||||
if positionedOutOfVerticalBounds {
|
||||
if newFrame.minY < boundingRect.minY || hasZeroOrigin {
|
||||
newFrame.origin.y = boundingRect.minY
|
||||
} else {
|
||||
newFrame.origin.y = boundingRect.maxY - newFrame.height
|
||||
}
|
||||
}
|
||||
|
||||
if positionedAwayFromHorizontalEdges {
|
||||
let distanceFromLeading = newFrame.minX - boundingRect.minX
|
||||
let distanceFromTrailing = boundingRect.maxX - newFrame.maxX
|
||||
|
||||
if distanceFromLeading > distanceFromTrailing || hasZeroOrigin {
|
||||
newFrame.origin.x = boundingRect.maxX - newFrame.width
|
||||
} else {
|
||||
newFrame.origin.x = boundingRect.minX
|
||||
}
|
||||
}
|
||||
|
||||
return newFrame
|
||||
}
|
||||
|
||||
private func updatePipLayout() {
|
||||
guard let window = view.window else { return owsFailDebug("missing window") }
|
||||
let newFrame = nearestValidPipFrame(for: window.frame.origin)
|
||||
UIView.animate(withDuration: 0.25) { window.frame = newFrame }
|
||||
}
|
||||
|
||||
private var startingTranslation: CGPoint?
|
||||
@objc func handlePan(sender: UIPanGestureRecognizer) {
|
||||
guard let window = view.window else { return owsFailDebug("missing window") }
|
||||
|
||||
switch sender.state {
|
||||
case .began, .changed:
|
||||
let translation = sender.translation(in: view)
|
||||
sender.setTranslation(.zero, in: view)
|
||||
|
||||
window.frame.origin.y += translation.y
|
||||
window.frame.origin.x += translation.x
|
||||
case .ended, .cancelled, .failed:
|
||||
let velocity = sender.velocity(in: view)
|
||||
|
||||
// TODO: maybe do more sophisticated deceleration
|
||||
|
||||
let duration: CGFloat = 0.35
|
||||
|
||||
let additionalDistanceX = velocity.x * duration
|
||||
let additionalDistanceY = velocity.y * duration
|
||||
|
||||
let finalDestination = CGPoint(
|
||||
x: window.frame.origin.x + additionalDistanceX,
|
||||
y: window.frame.origin.y + additionalDistanceY
|
||||
)
|
||||
|
||||
let finalFrame = nearestValidPipFrame(for: finalDestination)
|
||||
|
||||
UIView.animate(withDuration: TimeInterval(duration)) { window.frame = finalFrame }
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didTapStatusBar(notification: Notification) {
|
||||
private func handleTap(sender: UITapGestureRecognizer) {
|
||||
callViewController?.localVideoViewReference.removeFromSuperview()
|
||||
callViewController?.remoteVideoViewReference.removeFromSuperview()
|
||||
callViewController = nil
|
||||
self.delegate?.returnToCallWasTapped(self)
|
||||
}
|
||||
|
||||
override public func viewWillLayoutSubviews() {
|
||||
Logger.debug("")
|
||||
|
||||
super.viewWillLayoutSubviews()
|
||||
}
|
||||
|
||||
override public func viewDidLayoutSubviews() {
|
||||
Logger.debug("")
|
||||
|
||||
super.viewDidLayoutSubviews()
|
||||
}
|
||||
|
||||
// MARK: Orientation
|
||||
|
||||
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
|
||||
@ -37,7 +37,6 @@ public class StickerPackViewController: OWSViewController {
|
||||
stickerCollectionView.show(dataSource: dataSource)
|
||||
dataSource.add(delegate: self)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(callDidChange), name: .OWSWindowManagerCallDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didChangeStatusBarFrame), name: UIApplication.didChangeStatusBarFrameNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(stickersOrPacksDidChange),
|
||||
@ -409,13 +408,6 @@ public class StickerPackViewController: OWSViewController {
|
||||
StickerSharingViewController.shareStickerPack(stickerPack.info, from: self)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func callDidChange() {
|
||||
Logger.debug("")
|
||||
|
||||
updateContent()
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didChangeStatusBarFrame() {
|
||||
Logger.debug("")
|
||||
|
||||
@ -5,25 +5,12 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
@objc
|
||||
public protocol NavBarLayoutDelegate: class {
|
||||
func navBarCallLayoutDidChange(navbar: OWSNavigationBar)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class OWSNavigationBar: UINavigationBar {
|
||||
|
||||
@objc
|
||||
public weak var navBarLayoutDelegate: NavBarLayoutDelegate?
|
||||
|
||||
@objc
|
||||
public let navbarWithoutStatusHeight: CGFloat = 44
|
||||
|
||||
@objc
|
||||
public var callBannerHeight: CGFloat {
|
||||
return OWSWindowManagerCallBannerHeight()
|
||||
}
|
||||
|
||||
@objc
|
||||
public var statusBarHeight: CGFloat {
|
||||
return CurrentAppContext().statusBarHeight
|
||||
@ -47,8 +34,6 @@ public class OWSNavigationBar: UINavigationBar {
|
||||
|
||||
applyTheme()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(callDidChange), name: .OWSWindowManagerCallDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didChangeStatusBarFrame), name: UIApplication.didChangeStatusBarFrameNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(themeDidChange),
|
||||
name: .ThemeDidChange,
|
||||
@ -110,36 +95,6 @@ public class OWSNavigationBar: UINavigationBar {
|
||||
}
|
||||
}
|
||||
|
||||
public func snapshotViewIncludingBackground(afterScreenUpdates: Bool) -> UIView? {
|
||||
let originalFrame = self.frame
|
||||
let originalBounds = self.bounds
|
||||
|
||||
// NavigationBars are weird because though it appears as though the status bar
|
||||
// content is "in" or maybe "above" (z-index) the navbar, the navbar frame is strictly
|
||||
// lower (y-index) than the status bar.
|
||||
// To work with that, the navbar background, including the blur effect when transparency
|
||||
// is enabled, extends beyond the navbars's bounds. This allows the background to extend
|
||||
// up (y-index) and under (z-index) the status bar, without clips to bounds.
|
||||
//
|
||||
// Snapshots, however, clip to bounds. So as to capture the full size of the background
|
||||
// in our snapshot we temporarily adjust the navbars frame.
|
||||
self.frame = CGRect(x: 0, y: callBannerHeight, width: fullWidth, height: navbarWithoutStatusHeight + statusBarHeight)
|
||||
self.bounds = self.frame
|
||||
defer {
|
||||
self.frame = originalFrame
|
||||
self.bounds = originalBounds
|
||||
}
|
||||
|
||||
guard let barSnapshot = self.snapshotView(afterScreenUpdates: afterScreenUpdates) else {
|
||||
owsFailDebug("barSnapshot was unexpectedly nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
barSnapshot.frame.origin = .zero
|
||||
|
||||
return barSnapshot
|
||||
}
|
||||
|
||||
@objc
|
||||
public func themeDidChange() {
|
||||
Logger.debug("")
|
||||
@ -153,60 +108,6 @@ public class OWSNavigationBar: UINavigationBar {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Layout
|
||||
|
||||
@objc
|
||||
public func callDidChange() {
|
||||
Logger.debug("")
|
||||
self.navBarLayoutDelegate?.navBarCallLayoutDidChange(navbar: self)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didChangeStatusBarFrame() {
|
||||
Logger.debug("")
|
||||
self.navBarLayoutDelegate?.navBarCallLayoutDidChange(navbar: self)
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
guard CurrentAppContext().isMainApp else {
|
||||
super.layoutSubviews()
|
||||
return
|
||||
}
|
||||
guard OWSWindowManager.shared.hasCall else {
|
||||
super.layoutSubviews()
|
||||
return
|
||||
}
|
||||
|
||||
super.layoutSubviews()
|
||||
|
||||
self.frame = CGRect(x: 0, y: callBannerHeight, width: fullWidth, height: navbarWithoutStatusHeight)
|
||||
self.bounds = CGRect(x: 0, y: 0, width: fullWidth, height: navbarWithoutStatusHeight)
|
||||
|
||||
// This is only necessary on iOS11, which has some private views within that lay outside of the navbar.
|
||||
// They aren't actually visible behind the call status bar, but they looks strange during present/dismiss
|
||||
// animations for modal VC's
|
||||
for subview in self.subviews {
|
||||
let stringFromClass = NSStringFromClass(subview.classForCoder)
|
||||
if stringFromClass.contains("BarBackground") {
|
||||
subview.frame = self.bounds
|
||||
} else if stringFromClass.contains("BarContentView") {
|
||||
subview.frame = self.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
|
||||
// `fullWidth` will not be accurate until the navbar has a defined
|
||||
// superview. By forcing a re-layout after moving to a superview, we
|
||||
// ensure that the correct width is always used. This works around a bug
|
||||
// on iPad where the navigation bar is larger than the application frame
|
||||
// when in slide over mode.
|
||||
setNeedsLayout()
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
// MARK: Override Theme
|
||||
|
||||
@objc
|
||||
|
||||
@ -16,10 +16,10 @@ extern NSString *const IsScreenBlockActiveDidChangeNotification;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
const CGFloat OWSWindowManagerCallBannerHeight(void);
|
||||
|
||||
extern const UIWindowLevel UIWindowLevel_Background;
|
||||
|
||||
@protocol CallViewControllerWindowReference;
|
||||
|
||||
@interface OWSWindowManager : NSObject
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
@ -36,15 +36,14 @@ extern const UIWindowLevel UIWindowLevel_Background;
|
||||
- (BOOL)isAppWindow:(UIWindow *)window;
|
||||
|
||||
- (void)updateWindowFrames;
|
||||
- (void)ensureReturnToCallWindowFrame;
|
||||
|
||||
#pragma mark - Calls
|
||||
|
||||
@property (nonatomic, readonly) BOOL shouldShowCallView;
|
||||
@property (nonatomic, readonly) UIWindow *callViewWindow;
|
||||
|
||||
- (void)startCall:(UIViewController *)callViewController;
|
||||
- (void)endCall:(UIViewController *)callViewController;
|
||||
- (void)startCall:(UIViewController<CallViewControllerWindowReference> *)callViewController;
|
||||
- (void)endCall:(UIViewController<CallViewControllerWindowReference> *)callViewController;
|
||||
- (void)leaveCallView;
|
||||
@property (nonatomic, readonly) BOOL hasCall;
|
||||
|
||||
|
||||
@ -14,26 +14,6 @@ NSString *const OWSWindowManagerCallDidChangeNotification = @"OWSWindowManagerCa
|
||||
|
||||
NSString *const IsScreenBlockActiveDidChangeNotification = @"IsScreenBlockActiveDidChangeNotification";
|
||||
|
||||
const CGFloat OWSWindowManagerCallBannerHeight(void)
|
||||
{
|
||||
if (@available(iOS 11.4, *)) {
|
||||
return CurrentAppContext().statusBarHeight + 20;
|
||||
}
|
||||
|
||||
if (![UIDevice currentDevice].hasIPhoneXNotch) {
|
||||
return CurrentAppContext().statusBarHeight + 20;
|
||||
}
|
||||
|
||||
// Hardcode CallBanner height for iPhone X's on older iOS.
|
||||
//
|
||||
// As of iOS11.4 and iOS12, this no longer seems to be an issue, but previously statusBarHeight returned
|
||||
// something like 20pts (IIRC), meaning our call banner did not extend sufficiently past the iPhone X notch.
|
||||
//
|
||||
// Before noticing that this behavior changed, I actually assumed that notch height was intentionally excluded from
|
||||
// the statusBarHeight, and that this was not a bug, else I'd have taken better notes.
|
||||
return 64;
|
||||
}
|
||||
|
||||
// Behind everything, especially the root window.
|
||||
const UIWindowLevel UIWindowLevel_Background = -1.f;
|
||||
|
||||
@ -110,7 +90,7 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
|
||||
@property (nonatomic) BOOL shouldShowCallView;
|
||||
|
||||
@property (nonatomic, nullable) UIViewController *callViewController;
|
||||
@property (nonatomic, nullable) UIViewController<CallViewControllerWindowReference> *callViewController;
|
||||
|
||||
@end
|
||||
|
||||
@ -153,39 +133,15 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
self.returnToCallWindow = [self createReturnToCallWindow:rootWindow];
|
||||
self.callViewWindow = [self createCallViewWindow:rootWindow];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(didChangeStatusBarFrame:)
|
||||
name:UIApplicationDidChangeStatusBarFrameNotification
|
||||
object:nil];
|
||||
|
||||
[self ensureWindowState];
|
||||
}
|
||||
|
||||
- (void)didChangeStatusBarFrame:(NSNotification *)notification
|
||||
{
|
||||
// Apple bug? Upon returning from landscape, this method *is* fired, but both the notification and [UIApplication
|
||||
// sharedApplication].statusBarFrame still show a height of 0. So to work around we also call
|
||||
// `ensureReturnToCallWindowFrame` before showing the call banner.
|
||||
[self ensureReturnToCallWindowFrame];
|
||||
}
|
||||
|
||||
- (void)ensureReturnToCallWindowFrame
|
||||
{
|
||||
CGRect newFrame = CurrentAppContext().frame;
|
||||
newFrame.size.height = OWSWindowManagerCallBannerHeight();
|
||||
OWSLogDebug(@"returnToCallWindowFrame: %@", NSStringFromCGRect(newFrame));
|
||||
self.returnToCallWindow.frame = newFrame;
|
||||
}
|
||||
|
||||
- (UIWindow *)createReturnToCallWindow:(UIWindow *)rootWindow
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(rootWindow);
|
||||
|
||||
// "Return to call" should remain at the top of the screen.
|
||||
CGRect applicationFrame = CurrentAppContext().frame;
|
||||
applicationFrame.size.height = OWSWindowManagerCallBannerHeight();
|
||||
UIWindow *window = [[OWSWindow alloc] initWithFrame:applicationFrame];
|
||||
UIWindow *window = [[OWSWindow alloc] initWithFrame:rootWindow.bounds];
|
||||
window.hidden = YES;
|
||||
window.windowLevel = UIWindowLevel_ReturnToCall();
|
||||
window.opaque = YES;
|
||||
@ -266,7 +222,7 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
|
||||
#pragma mark - Calls
|
||||
|
||||
- (void)setCallViewController:(nullable UIViewController *)callViewController
|
||||
- (void)setCallViewController:(nullable UIViewController<CallViewControllerWindowReference> *)callViewController
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
@ -279,7 +235,7 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
[NSNotificationCenter.defaultCenter postNotificationName:OWSWindowManagerCallDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)startCall:(UIViewController *)callViewController
|
||||
- (void)startCall:(UIViewController<CallViewControllerWindowReference> *)callViewController
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(callViewController);
|
||||
@ -298,7 +254,7 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
[self ensureWindowState];
|
||||
}
|
||||
|
||||
- (void)endCall:(UIViewController *)callViewController
|
||||
- (void)endCall:(UIViewController<CallViewControllerWindowReference> *)callViewController
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(callViewController);
|
||||
@ -337,6 +293,7 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
|
||||
self.shouldShowCallView = YES;
|
||||
|
||||
[self.callViewController returnFromPipWithPipWindow:self.returnToCallWindow];
|
||||
[self ensureWindowState];
|
||||
}
|
||||
|
||||
@ -373,14 +330,13 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
// Show Call View.
|
||||
|
||||
[self ensureRootWindowHidden];
|
||||
[self ensureReturnToCallWindowHidden];
|
||||
[self ensureCallViewWindowShown];
|
||||
[self ensureReturnToCallWindowHidden];
|
||||
[self ensureScreenBlockWindowHidden];
|
||||
} else {
|
||||
// Show Root Window
|
||||
|
||||
[self ensureRootWindowShown];
|
||||
[self ensureCallViewWindowHidden];
|
||||
[self ensureScreenBlockWindowHidden];
|
||||
|
||||
if (self.callViewController) {
|
||||
@ -390,6 +346,8 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
} else {
|
||||
[self ensureReturnToCallWindowHidden];
|
||||
}
|
||||
|
||||
[self ensureCallViewWindowHidden];
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,10 +388,9 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
return;
|
||||
}
|
||||
|
||||
[self ensureReturnToCallWindowFrame];
|
||||
OWSLogInfo(@"showing 'return to call' window.");
|
||||
self.returnToCallWindow.hidden = NO;
|
||||
[self.returnToCallViewController startAnimating];
|
||||
[self.returnToCallViewController displayForCallViewController:self.callViewController];
|
||||
}
|
||||
|
||||
- (void)ensureReturnToCallWindowHidden
|
||||
@ -446,7 +403,6 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void)
|
||||
|
||||
OWSLogInfo(@"hiding 'return to call' window.");
|
||||
self.returnToCallWindow.hidden = YES;
|
||||
[self.returnToCallViewController stopAnimating];
|
||||
}
|
||||
|
||||
- (void)ensureCallViewWindowShown
|
||||
|
||||
Loading…
Reference in New Issue
Block a user