From 9364af9b85f0a8d56b47548afb19404bc9cba3df Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 17 Apr 2018 09:45:02 -0400 Subject: [PATCH 01/12] Make screen block view first responder. --- Signal/src/util/OWSScreenLockUI.m | 2 ++ SignalMessaging/ViewControllers/ScreenLockViewController.m | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Signal/src/util/OWSScreenLockUI.m b/Signal/src/util/OWSScreenLockUI.m index b5123d364..37733f2a9 100644 --- a/Signal/src/util/OWSScreenLockUI.m +++ b/Signal/src/util/OWSScreenLockUI.m @@ -438,6 +438,8 @@ const UIWindowLevel UIWindowLevel_Background = -1.f; // Show the blocking window in front of the status bar. self.screenBlockingWindow.windowLevel = UIWindowLevelStatusBar + 1; self.rootWindow.hidden = YES; + [self.screenBlockingWindow becomeFirstResponder]; + OWSAssert([self.screenBlockingWindow isFirstResponder]); } else { self.screenBlockingWindow.windowLevel = UIWindowLevel_Background; [self.rootWindow makeKeyAndVisible]; diff --git a/SignalMessaging/ViewControllers/ScreenLockViewController.m b/SignalMessaging/ViewControllers/ScreenLockViewController.m index 772f21df7..cf2a961fb 100644 --- a/SignalMessaging/ViewControllers/ScreenLockViewController.m +++ b/SignalMessaging/ViewControllers/ScreenLockViewController.m @@ -136,4 +136,9 @@ NSString *NSStringForScreenLockUIState(ScreenLockUIState value) [self.delegate unlockButtonWasTapped]; } +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + @end From 7345ab2e4ea8f371a50dba3adf855474654d747c Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 18 Apr 2018 11:23:57 -0400 Subject: [PATCH 02/12] Add window manager. Move call view to a separate window. --- Signal.xcodeproj/project.pbxproj | 8 +- Signal/src/Signal-Bridging-Header.h | 1 + .../ViewControllers/CallViewController.swift | 16 +- .../call/UserInterface/CallUIAdapter.swift | 23 +- Signal/src/util/OWSScreenLockUI.h | 1 + Signal/src/util/OWSScreenLockUI.m | 76 +-- Signal/src/util/OWSWindowManager.h | 36 ++ Signal/src/util/OWSWindowManager.m | 460 ++++++++++++++++++ 8 files changed, 544 insertions(+), 77 deletions(-) create mode 100644 Signal/src/util/OWSWindowManager.h create mode 100644 Signal/src/util/OWSWindowManager.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 85ee16cc0..7ba4351de 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -142,6 +142,7 @@ 34612A011FD5F31400532771 /* OWS104CreateRecipientIdentities.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */; }; 34612A061FD7238600532771 /* OWSContactsSyncing.h in Headers */ = {isa = PBXBuildFile; fileRef = 34612A041FD7238500532771 /* OWSContactsSyncing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34612A071FD7238600532771 /* OWSContactsSyncing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34612A051FD7238500532771 /* OWSContactsSyncing.m */; }; + 34641E1220878FB000E2EDE5 /* OWSWindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */; }; 34641E182088D7E900E2EDE5 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */; }; 34641E1B2088DA4100E2EDE5 /* ScreenLockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34641E192088DA3F00E2EDE5 /* ScreenLockViewController.m */; }; 34641E1C2088DA4100E2EDE5 /* ScreenLockViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 34641E1A2088DA4000E2EDE5 /* ScreenLockViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -730,6 +731,8 @@ 346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS104CreateRecipientIdentities.h; sourceTree = ""; }; 34612A041FD7238500532771 /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = ""; }; 34612A051FD7238500532771 /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = ""; }; + 34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWindowManager.m; sourceTree = ""; }; + 34641E1120878FB000E2EDE5 /* OWSWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSWindowManager.h; sourceTree = ""; }; 34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSScreenLock.swift; sourceTree = ""; }; 34641E192088DA3F00E2EDE5 /* ScreenLockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ScreenLockViewController.m; path = SignalMessaging/ViewControllers/ScreenLockViewController.m; sourceTree = SOURCE_ROOT; }; 34641E1A2088DA4000E2EDE5 /* ScreenLockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScreenLockViewController.h; path = SignalMessaging/ViewControllers/ScreenLockViewController.h; sourceTree = SOURCE_ROOT; }; @@ -2045,9 +2048,11 @@ 340FC8CE205BF2FA007AEB0F /* OWSBackupIO.m */, 340FC8CB20518C76007AEB0F /* OWSBackupJob.h */, 340FC8CC20518C76007AEB0F /* OWSBackupJob.m */, + 34D2CCD120618B2F00CB1A14 /* OWSBackupLazyRestoreJob.swift */, 34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */, 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */, - 34D2CCD120618B2F00CB1A14 /* OWSBackupLazyRestoreJob.swift */, + 34641E1120878FB000E2EDE5 /* OWSWindowManager.h */, + 34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */, 4579431C1E7C8CE9008ED0C0 /* Pastelog.h */, 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */, 450DF2041E0D74AC003D14BE /* Platform.swift */, @@ -3201,6 +3206,7 @@ 45D308AD2049A439000189E4 /* PinEntryView.m in Sources */, 340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */, 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */, + 34641E1220878FB000E2EDE5 /* OWSWindowManager.m in Sources */, 45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */, 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */, 34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */, diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 5779fddd5..a35411e07 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -31,6 +31,7 @@ #import "OWSProgressView.h" #import "OWSQuotedMessageView.h" #import "OWSWebRTCDataProtos.pb.h" +#import "OWSWindowManager.h" #import "PinEntryView.h" #import "PrivacySettingsTableViewController.h" #import "ProfileViewController.h" diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 7f7a9f02b..2a8a1ccf2 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -19,6 +19,9 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, return SignalApp.shared().callUIAdapter } + // Feature Flag + @objc public static let kShowCallViewOnSeparateWindow = true + let contactsManager: OWSContactsManager // MARK: Properties @@ -1103,11 +1106,20 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, hasDismissed = true DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in guard let strongSelf = self else { return } - strongSelf.dismiss(animated: true, completion: completion) + strongSelf.dismissImmediately(completion: completion) } } else { hasDismissed = true - self.dismiss(animated: false, completion: completion) + dismissImmediately(completion: completion) + } + } + + internal func dismissImmediately(completion: (() -> Void)?) { + if CallViewController.kShowCallViewOnSeparateWindow { + OWSWindowManager.shared().endCall(self) + completion?() + } else { + self.dismiss(animated: true, completion: completion) } } diff --git a/Signal/src/call/UserInterface/CallUIAdapter.swift b/Signal/src/call/UserInterface/CallUIAdapter.swift index 5e6520368..d7a755658 100644 --- a/Signal/src/call/UserInterface/CallUIAdapter.swift +++ b/Signal/src/call/UserInterface/CallUIAdapter.swift @@ -39,15 +39,22 @@ extension CallUIAdaptee { let callViewController = CallViewController(call: call) callViewController.modalTransitionStyle = .crossDissolve - guard let presentingViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts else { - owsFail("in \(#function) view controller unexpectedly nil") - return - } - - if let presentedViewController = presentingViewController.presentedViewController { - presentedViewController.dismiss(animated: false) + if CallViewController.kShowCallViewOnSeparateWindow { + OWSWindowManager.shared().startCall(callViewController) + } else { + guard let presentingViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts else { + owsFail("in \(#function) view controller unexpectedly nil") + return + } + + if let presentedViewController = presentingViewController.presentedViewController { + presentedViewController.dismiss(animated: false) { + presentingViewController.present(callViewController, animated: true) + } + } else { + presentingViewController.present(callViewController, animated: true) + } } - presentingViewController.present(callViewController, animated: true) } internal func reportMissedCall(_ call: SignalCall, callerName: String) { diff --git a/Signal/src/util/OWSScreenLockUI.h b/Signal/src/util/OWSScreenLockUI.h index 90317d713..bbeeb3500 100644 --- a/Signal/src/util/OWSScreenLockUI.h +++ b/Signal/src/util/OWSScreenLockUI.h @@ -4,6 +4,7 @@ NS_ASSUME_NONNULL_BEGIN +// TODO: Rename to window manager or somesuch. @interface OWSScreenLockUI : NSObject - (instancetype)init NS_UNAVAILABLE; diff --git a/Signal/src/util/OWSScreenLockUI.m b/Signal/src/util/OWSScreenLockUI.m index 37733f2a9..b83f60cba 100644 --- a/Signal/src/util/OWSScreenLockUI.m +++ b/Signal/src/util/OWSScreenLockUI.m @@ -3,6 +3,7 @@ // #import "OWSScreenLockUI.h" +#import "OWSWindowManager.h" #import "Signal-Swift.h" #import #import @@ -10,11 +11,10 @@ NS_ASSUME_NONNULL_BEGIN -const UIWindowLevel UIWindowLevel_Background = -1.f; - @interface OWSScreenLockUI () @property (nonatomic) UIWindow *screenBlockingWindow; +@property (nonatomic) ScreenLockViewController *screenBlockingViewController; // Unlike UIApplication.applicationState, this state reflects the // notifications, i.e. "did become active", "will resign active", @@ -29,7 +29,6 @@ const UIWindowLevel UIWindowLevel_Background = -1.f; // inactive in order for it to be reflected in the app switcher. @property (nonatomic) BOOL appIsInactiveOrBackground; @property (nonatomic) BOOL appIsInBackground; -@property (nonatomic) ScreenLockViewController *screenBlockingViewController; @property (nonatomic) BOOL isShowingScreenLockUI; @@ -52,11 +51,6 @@ const UIWindowLevel UIWindowLevel_Background = -1.f; // The "countdown" until screen lock takes effect. @property (nonatomic, nullable) NSDate *screenLockCountdownDate; -@property (nonatomic) UIWindow *rootWindow; - -@property (nonatomic, nullable) UIResponder *rootWindowResponder; -@property (nonatomic, nullable, weak) UIViewController *rootFrontmostViewController; - @end #pragma mark - @@ -130,9 +124,12 @@ const UIWindowLevel UIWindowLevel_Background = -1.f; OWSAssertIsOnMainThread(); OWSAssert(rootWindow); - self.rootWindow = rootWindow; + [self createScreenBlockingWindowWithRootWindow:rootWindow]; + OWSAssert(self.screenBlockingWindow); + [[OWSWindowManager sharedManager] setupWithRootWindow:rootWindow screenBlockingWindow:self.screenBlockingWindow]; - [self prepareScreenProtectionWithRootWindow:rootWindow]; + // Default to screen protection until we know otherwise. + [self updateScreenBlockingWindow:ScreenLockUIStateNone animated:NO]; // Initialize the screen lock state. // @@ -372,14 +369,14 @@ const UIWindowLevel UIWindowLevel_Background = -1.f; // After the alert, update the UI. [self ensureUI]; } - fromViewController:self.screenBlockingViewController]; + fromViewController:self.screenBlockingWindow.rootViewController]; } // 'Screen Blocking' window obscures the app screen: // // * In the app switcher. // * During 'Screen Lock' unlock process. -- (void)prepareScreenProtectionWithRootWindow:(UIWindow *)rootWindow +- (void)createScreenBlockingWindowWithRootWindow:(UIWindow *)rootWindow { OWSAssertIsOnMainThread(); OWSAssert(rootWindow); @@ -413,61 +410,8 @@ const UIWindowLevel UIWindowLevel_Background = -1.f; OWSAssertIsOnMainThread(); BOOL shouldShowBlockWindow = desiredUIState != ScreenLockUIStateNone; - if (self.rootWindow.hidden != shouldShowBlockWindow) { - DDLogInfo(@"%@, %@.", self.logTag, shouldShowBlockWindow ? @"showing block window" : @"hiding block window"); - } - - // When we show the block window, try to capture the first responder of - // the root window before it is hidden. - // - // When we hide the root window, its first responder will resign. - if (shouldShowBlockWindow && !self.rootWindow.hidden) { - self.rootWindowResponder = [UIResponder currentFirstResponder]; - self.rootFrontmostViewController = [UIApplication.sharedApplication frontmostViewControllerIgnoringAlerts]; - DDLogInfo(@"%@ trying to capture self.rootWindowResponder: %@ (%@)", - self.logTag, - self.rootWindowResponder, - [self.rootFrontmostViewController class]); - } - - // * Show/hide the app's root window as necessary. - // * Never hide the blocking window (that can lead to bad frames). - // Instead, manipulate its window level to move it in front of - // or behind the root window. - if (shouldShowBlockWindow) { - // Show the blocking window in front of the status bar. - self.screenBlockingWindow.windowLevel = UIWindowLevelStatusBar + 1; - self.rootWindow.hidden = YES; - [self.screenBlockingWindow becomeFirstResponder]; - OWSAssert([self.screenBlockingWindow isFirstResponder]); - } else { - self.screenBlockingWindow.windowLevel = UIWindowLevel_Background; - [self.rootWindow makeKeyAndVisible]; - - // When we hide the block window, try to restore the first - // responder of the root window. - // - // It's important we restore first responder status once the user completes - // In some cases, (RegistrationLock Reminder) it just puts the keyboard back where - // the user needs it, saving them a tap. - // But in the case of an inputAccessoryView, like the ConversationViewController, - // failing to restore firstResponder could hide the input toolbar. - - UIViewController *rootFrontmostViewController = - [UIApplication.sharedApplication frontmostViewControllerIgnoringAlerts]; - DDLogInfo(@"%@ trying to restore self.rootWindowResponder: %@ (%@ ? %@ == %d)", - self.logTag, - self.rootWindowResponder, - [self.rootFrontmostViewController class], - rootFrontmostViewController, - self.rootFrontmostViewController == rootFrontmostViewController); - if (self.rootFrontmostViewController == rootFrontmostViewController) { - [self.rootWindowResponder becomeFirstResponder]; - } - self.rootWindowResponder = nil; - self.rootFrontmostViewController = nil; - } + [OWSWindowManager.sharedManager setIsScreenBlockActive:shouldShowBlockWindow]; [self.screenBlockingViewController updateUIWithState:desiredUIState isLogoAtTop:self.isShowingScreenLockUI diff --git a/Signal/src/util/OWSWindowManager.h b/Signal/src/util/OWSWindowManager.h new file mode 100644 index 000000000..c1300ecff --- /dev/null +++ b/Signal/src/util/OWSWindowManager.h @@ -0,0 +1,36 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +// This VC can become first responder +// when presented to ensure that the input accessory is updated. +@interface OWSWindowRootViewController : UIViewController + +@end + +#pragma mark - + +extern const UIWindowLevel UIWindowLevel_Background; + +@interface OWSWindowManager : NSObject + +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype)sharedManager; + +- (void)setupWithRootWindow:(UIWindow *)rootWindow screenBlockingWindow:(UIWindow *)screenBlockingWindow; + +- (void)setIsScreenBlockActive:(BOOL)isScreenBlockActive; + +#pragma mark - Calls + +- (void)startCall:(UIViewController *)callViewController; +- (void)endCall:(UIViewController *)callViewController; +- (void)leaveCallView; +- (void)returnToCallView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/OWSWindowManager.m b/Signal/src/util/OWSWindowManager.m new file mode 100644 index 000000000..d197dbef1 --- /dev/null +++ b/Signal/src/util/OWSWindowManager.m @@ -0,0 +1,460 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSWindowManager.h" +#import "Signal-Swift.h" +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +const UIWindowLevel UIWindowLevel_Background = -1.f; +const UIWindowLevel UIWindowLevel_ReturnToCall(void); +const UIWindowLevel UIWindowLevel_ReturnToCall(void) +{ + return UIWindowLevelNormal + 1.f; +} +const UIWindowLevel UIWindowLevel_CallView(void); +const UIWindowLevel UIWindowLevel_CallView(void) +{ + return UIWindowLevelNormal + 2.f; +} +const UIWindowLevel UIWindowLevel_ScreenBlocking(void); +const UIWindowLevel UIWindowLevel_ScreenBlocking(void) +{ + return UIWindowLevelStatusBar + 1.f; +} + +const int kReturnToCallWindowHeight = 40.f; + +@implementation OWSWindowRootViewController + +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +@end + +#pragma mark - + +@interface OWSWindowManager () + +// UIWindowLevelNormal +@property (nonatomic) UIWindow *rootWindow; + +// UIWindowLevel_ReturnToCall +@property (nonatomic) UIWindow *returnToCallWindow; + +// UIWindowLevel_CallView +@property (nonatomic) UIWindow *callViewWindow; + +// UIWindowLevel_Background if inactive, +// UIWindowLevel_ScreenBlocking() if active. +@property (nonatomic) UIWindow *screenBlockingWindow; + +@property (nonatomic) BOOL isScreenBlockActive; + +@property (nonatomic) BOOL isCallViewActive; + +@property (nonatomic, nullable) UIViewController *callViewController; + +@property (nonatomic, nullable) UIResponder *rootWindowResponder; +@property (nonatomic, nullable, weak) UIViewController *rootFrontmostViewController; + +@end + +#pragma mark - + +@implementation OWSWindowManager + ++ (instancetype)sharedManager +{ + static OWSWindowManager *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] initDefault]; + }); + return instance; +} + +- (instancetype)initDefault +{ + self = [super init]; + + if (!self) { + return self; + } + + OWSAssertIsOnMainThread(); + OWSSingletonAssert(); + + return self; +} + +- (void)setupWithRootWindow:(UIWindow *)rootWindow screenBlockingWindow:(UIWindow *)screenBlockingWindow +{ + OWSAssertIsOnMainThread(); + OWSAssert(rootWindow); + OWSAssert(!self.rootWindow); + OWSAssert(screenBlockingWindow); + OWSAssert(!self.screenBlockingWindow); + + self.rootWindow = rootWindow; + self.screenBlockingWindow = screenBlockingWindow; + + self.returnToCallWindow = [OWSWindowManager createReturnToCallWindow:rootWindow]; + self.callViewWindow = [OWSWindowManager createCallViewWindow:rootWindow]; + + [self ensureWindowState]; +} + ++ (UIWindow *)createReturnToCallWindow:(UIWindow *)rootWindow +{ + OWSAssertIsOnMainThread(); + OWSAssert(rootWindow); + + // "Return to call" should remain at the top of the screen. + // + // TODO: Extend below the status bar. + CGRect windowFrame = rootWindow.bounds; + windowFrame.size.height = kReturnToCallWindowHeight; + UIWindow *window = [[UIWindow alloc] initWithFrame:windowFrame]; + window.hidden = YES; + window.windowLevel = UIWindowLevel_ReturnToCall(); + window.opaque = YES; + // TODO: + window.backgroundColor = UIColor.ows_materialBlueColor; + window.backgroundColor = [UIColor redColor]; + + UIViewController *viewController = [OWSWindowRootViewController new]; + viewController.view.backgroundColor = UIColor.ows_materialBlueColor; + viewController.view.backgroundColor = [UIColor redColor]; + + UIView *rootView = viewController.view; + rootView.userInteractionEnabled = YES; + [rootView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(returnToCallWasTapped:)]]; + + UILabel *label = [UILabel new]; + label.text = NSLocalizedString(@"CALL_WINDOW_RETURN_TO_CALL", @"Label for the 'return to call' indicator."); + label.textColor = [UIColor whiteColor]; + // TODO: Dynamic type? + label.font = [UIFont ows_mediumFontWithSize:18.f]; + [rootView addSubview:label]; + [label autoCenterInSuperview]; + + window.rootViewController = viewController; + + return window; +} + ++ (UIWindow *)createCallViewWindow:(UIWindow *)rootWindow +{ + OWSAssertIsOnMainThread(); + OWSAssert(rootWindow); + + UIWindow *window = [[UIWindow alloc] initWithFrame:rootWindow.bounds]; + window.hidden = YES; + window.windowLevel = UIWindowLevel_CallView(); + window.opaque = YES; + // TODO: + window.backgroundColor = UIColor.ows_materialBlueColor; + window.backgroundColor = [UIColor yellowColor]; + + UIViewController *viewController = [OWSWindowRootViewController new]; + viewController.view.backgroundColor = UIColor.ows_materialBlueColor; + viewController.view.backgroundColor = [UIColor yellowColor]; + + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:viewController]; + navigationController.navigationBarHidden = YES; + + window.rootViewController = navigationController; + + return window; +} + +- (void)setIsScreenBlockActive:(BOOL)isScreenBlockActive +{ + OWSAssertIsOnMainThread(); + + _isScreenBlockActive = isScreenBlockActive; + + [self ensureWindowState]; +} + +#pragma mark - Calls + +- (void)startCall:(UIViewController *)callViewController +{ + OWSAssertIsOnMainThread(); + OWSAssert(callViewController); + OWSAssert(!self.callViewController); + + self.callViewController = callViewController; + // Attach callViewController from window. + [self.callViewWindow.rootViewController.navigationController pushViewController:callViewController animated:NO]; + self.isCallViewActive = YES; + + [self ensureWindowState]; +} + +- (void)endCall:(UIViewController *)callViewController +{ + OWSAssertIsOnMainThread(); + OWSAssert(callViewController); + OWSAssert(self.callViewController); + + if (self.callViewController != callViewController) { + DDLogWarn(@"%@ Ignoring end call request from obsolete call view controller.", self.logTag); + return; + } + + // Dettach callViewController from window. + [self.callViewWindow.rootViewController.navigationController popToRootViewControllerAnimated:NO]; + self.callViewController = nil; + self.isCallViewActive = NO; + + [self ensureWindowState]; +} + +- (void)leaveCallView +{ + OWSAssertIsOnMainThread(); + OWSAssert(self.callViewController); + OWSAssert(self.isCallViewActive); + + self.isCallViewActive = NO; + + [self ensureWindowState]; +} + +- (void)returnToCallView +{ + OWSAssertIsOnMainThread(); + OWSAssert(self.callViewController); + OWSAssert(!self.isCallViewActive); + + self.isCallViewActive = YES; + + [self ensureWindowState]; +} + +#pragma mark - Window State + +- (void)ensureWindowState +{ + OWSAssertIsOnMainThread(); + OWSAssert(self.rootWindow); + OWSAssert(self.returnToCallWindow); + OWSAssert(self.callViewWindow); + OWSAssert(self.screenBlockingWindow); + + if (self.isScreenBlockActive) { + // Show Screen Block. + + [self hideRootWindowIfNecessary]; + [self hideReturnToCallWindowIfNecessary]; + [self hideCallViewWindowIfNecessary]; + [self showScreenBlockWindowIfNecessary]; + } else if (self.callViewController && self.isCallViewActive) { + // Show Call View. + + [self hideRootWindowIfNecessary]; + [self hideReturnToCallWindowIfNecessary]; + [self showCallViewWindowIfNecessary]; + [self hideScreenBlockWindowIfNecessary]; + } else if (self.callViewController) { + // Show Root Window + "Return to Call". + + [self showRootWindowIfNecessary]; + [self showReturnToCallWindowIfNecessary]; + [self hideCallViewWindowIfNecessary]; + [self hideScreenBlockWindowIfNecessary]; + } else { + // Show Root Window + + [self showRootWindowIfNecessary]; + [self hideReturnToCallWindowIfNecessary]; + [self hideCallViewWindowIfNecessary]; + [self hideScreenBlockWindowIfNecessary]; + } + + DDLogVerbose(@"%@ rootWindow: %d %f", self.logTag, self.rootWindow.hidden, self.rootWindow.windowLevel); + DDLogVerbose(@"%@ returnToCallWindow: %d %f", + self.logTag, + self.returnToCallWindow.hidden, + self.returnToCallWindow.windowLevel); + DDLogVerbose(@"%@ callViewWindow: %d %f", self.logTag, self.callViewWindow.hidden, self.callViewWindow.windowLevel); + DDLogVerbose(@"%@ screenBlockingWindow: %d %f", + self.logTag, + self.screenBlockingWindow.hidden, + self.screenBlockingWindow.windowLevel); + + dispatch_async(dispatch_get_main_queue(), ^{ + DDLogVerbose(@"%@ ...rootWindow: %d %f", self.logTag, self.rootWindow.hidden, self.rootWindow.windowLevel); + DDLogVerbose(@"%@ ...returnToCallWindow: %d %f", + self.logTag, + self.returnToCallWindow.hidden, + self.returnToCallWindow.windowLevel); + DDLogVerbose( + @"%@ ...callViewWindow: %d %f", self.logTag, self.callViewWindow.hidden, self.callViewWindow.windowLevel); + DDLogVerbose(@"%@ ...screenBlockingWindow: %d %f", + self.logTag, + self.screenBlockingWindow.hidden, + self.screenBlockingWindow.windowLevel); + }); +} + +- (void)showRootWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (self.rootWindow.hidden) { + DDLogInfo(@"%@ showing root window.", self.logTag); + } + + BOOL shouldTryToRestoreFirstResponder = self.rootWindow.hidden; + + [self.rootWindow makeKeyAndVisible]; + + // When we hide the block window, try to restore the first + // responder of the root window. + // + // It's important we restore first responder status once the user completes + // In some cases, (RegistrationLock Reminder) it just puts the keyboard back where + // the user needs it, saving them a tap. + // But in the case of an inputAccessoryView, like the ConversationViewController, + // failing to restore firstResponder could hide the input toolbar. + if (shouldTryToRestoreFirstResponder) { + UIViewController *rootFrontmostViewController = + [UIApplication.sharedApplication frontmostViewControllerIgnoringAlerts]; + + DDLogInfo(@"%@ trying to restore self.rootWindowResponder: %@ (%@ ? %@ == %d)", + self.logTag, + self.rootWindowResponder, + [self.rootFrontmostViewController class], + rootFrontmostViewController, + self.rootFrontmostViewController == rootFrontmostViewController); + if (self.rootFrontmostViewController == rootFrontmostViewController) { + [self.rootWindowResponder becomeFirstResponder]; + } else { + [rootFrontmostViewController becomeFirstResponder]; + } + } + + self.rootWindowResponder = nil; + self.rootFrontmostViewController = nil; +} + +- (void)hideRootWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (!self.rootWindow.hidden) { + DDLogInfo(@"%@ hiding root window.", self.logTag); + } + + // When we hide the root window, try to capture its first responder and + // current vc before it is hidden. + if (!self.rootWindow.hidden) { + self.rootWindowResponder = [UIResponder currentFirstResponder]; + self.rootFrontmostViewController = [UIApplication.sharedApplication frontmostViewControllerIgnoringAlerts]; + DDLogInfo(@"%@ trying to capture self.rootWindowResponder: %@ (%@)", + self.logTag, + self.rootWindowResponder, + [self.rootFrontmostViewController class]); + } + + self.rootWindow.hidden = YES; +} + +- (void)showReturnToCallWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (self.returnToCallWindow.hidden) { + DDLogInfo(@"%@ showing 'return to call' window.", self.logTag); + } + + self.returnToCallWindow.hidden = NO; +} + +- (void)hideReturnToCallWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (!self.returnToCallWindow.hidden) { + DDLogInfo(@"%@ hiding 'return to call' window.", self.logTag); + } + + self.returnToCallWindow.hidden = YES; +} + +- (void)showCallViewWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (self.callViewWindow.hidden) { + DDLogInfo(@"%@ showing call window.", self.logTag); + } + + [self.callViewWindow makeKeyAndVisible]; + [self.callViewWindow.rootViewController becomeFirstResponder]; +} + +- (void)hideCallViewWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (!self.callViewWindow.hidden) { + DDLogInfo(@"%@ hiding call window.", self.logTag); + } + + self.callViewWindow.hidden = YES; +} + +- (void)showScreenBlockWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (self.screenBlockingWindow.windowLevel != UIWindowLevel_ScreenBlocking()) { + DDLogInfo(@"%@ showing block window.", self.logTag); + } + + self.screenBlockingWindow.windowLevel = UIWindowLevel_ScreenBlocking(); + [self.screenBlockingWindow.rootViewController becomeFirstResponder]; +} + +- (void)hideScreenBlockWindowIfNecessary +{ + OWSAssertIsOnMainThread(); + + if (self.screenBlockingWindow.windowLevel != UIWindowLevel_Background) { + DDLogInfo(@"%@ hiding block window.", self.logTag); + } + + // Never hide the blocking window (that can lead to bad frames). + // Instead, manipulate its window level to move it in front of + // or behind the root window. + self.screenBlockingWindow.windowLevel = UIWindowLevel_Background; + [self.screenBlockingWindow resignFirstResponder]; +} + +#pragma mark - Events + +- (void)returnToCallWasTapped:(UIGestureRecognizer *)sender +{ + if (sender.state != UIGestureRecognizerStateRecognized) { + return; + } + + [self returnToCallView]; +} + +@end + +NS_ASSUME_NONNULL_END From 6a8796ad04688a49ff8d075ff2c6fa12a8ea827d Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 18 Apr 2018 12:09:42 -0400 Subject: [PATCH 03/12] Present calls using window manager. --- Signal/src/call/CallService.swift | 6 ++---- Signal/src/util/OWSWindowManager.h | 1 + Signal/src/util/OWSWindowManager.m | 22 +++++++++++++++------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 4c24807a6..d996aee07 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -1687,11 +1687,9 @@ protocol CallServiceObserver: class { return } - let frontmostViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts - - guard nil != frontmostViewController as? CallViewController else { + if !OWSWindowManager.shared().hasCall() { OWSProdError(OWSAnalyticsEvents.callServiceCallViewCouldNotPresent(), file: #file, function: #function, line: #line) - owsFail("\(self.logTag) in \(#function) Call terminated due to call view presentation delay: \(frontmostViewController.debugDescription).") + owsFail("\(self.logTag) in \(#function) Call terminated due to missing call view.") self.handleFailedCall(failedCall: call, error: CallError.assertionError(description: "Call view didn't present after \(kMaxViewPresentationDelay) seconds")) return } diff --git a/Signal/src/util/OWSWindowManager.h b/Signal/src/util/OWSWindowManager.h index c1300ecff..15dde5100 100644 --- a/Signal/src/util/OWSWindowManager.h +++ b/Signal/src/util/OWSWindowManager.h @@ -30,6 +30,7 @@ extern const UIWindowLevel UIWindowLevel_Background; - (void)endCall:(UIViewController *)callViewController; - (void)leaveCallView; - (void)returnToCallView; +- (BOOL)hasCall; @end diff --git a/Signal/src/util/OWSWindowManager.m b/Signal/src/util/OWSWindowManager.m index d197dbef1..c46e2b9ef 100644 --- a/Signal/src/util/OWSWindowManager.m +++ b/Signal/src/util/OWSWindowManager.m @@ -160,13 +160,10 @@ const int kReturnToCallWindowHeight = 40.f; window.hidden = YES; window.windowLevel = UIWindowLevel_CallView(); window.opaque = YES; - // TODO: - window.backgroundColor = UIColor.ows_materialBlueColor; - window.backgroundColor = [UIColor yellowColor]; + window.backgroundColor = [UIColor ows_materialBlueColor]; UIViewController *viewController = [OWSWindowRootViewController new]; - viewController.view.backgroundColor = UIColor.ows_materialBlueColor; - viewController.view.backgroundColor = [UIColor yellowColor]; + viewController.view.backgroundColor = [UIColor ows_materialBlueColor]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; @@ -196,7 +193,9 @@ const int kReturnToCallWindowHeight = 40.f; self.callViewController = callViewController; // Attach callViewController from window. - [self.callViewWindow.rootViewController.navigationController pushViewController:callViewController animated:NO]; + OWSAssert([self.callViewWindow.rootViewController isKindOfClass:[UINavigationController class]]); + UINavigationController *navigationController = (UINavigationController *)self.callViewWindow.rootViewController; + [navigationController pushViewController:callViewController animated:NO]; self.isCallViewActive = YES; [self ensureWindowState]; @@ -214,7 +213,9 @@ const int kReturnToCallWindowHeight = 40.f; } // Dettach callViewController from window. - [self.callViewWindow.rootViewController.navigationController popToRootViewControllerAnimated:NO]; + OWSAssert([self.callViewWindow.rootViewController isKindOfClass:[UINavigationController class]]); + UINavigationController *navigationController = (UINavigationController *)self.callViewWindow.rootViewController; + [navigationController popToRootViewControllerAnimated:NO]; self.callViewController = nil; self.isCallViewActive = NO; @@ -243,6 +244,13 @@ const int kReturnToCallWindowHeight = 40.f; [self ensureWindowState]; } +- (BOOL)hasCall +{ + OWSAssertIsOnMainThread(); + + return self.callViewController != nil; +} + #pragma mark - Window State - (void)ensureWindowState From 17fce2fdd2e52430353def859a9a9e2e2d0cb940 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 18 Apr 2018 12:48:31 -0400 Subject: [PATCH 04/12] Present calls using window manager. --- .../ViewControllers/CallViewController.swift | 12 ++++ Signal/src/util/OWSWindowManager.m | 65 +++++++++++++------ .../translations/en.lproj/Localizable.strings | 3 + 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 2a8a1ccf2..42e4bdb75 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -237,6 +237,9 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, if !remoteVideoView.isHidden { shouldRemoteVideoControlsBeHidden = !shouldRemoteVideoControlsBeHidden } + + // TODO: Until we have a proper design, use background taps to leave call view. + OWSWindowManager.shared().leaveCallView() } func createVideoViews() { @@ -1137,4 +1140,13 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, updateLocalVideoTrack(localVideoTrack: localVideoTrack) updateRemoteVideoTrack(remoteVideoTrack: remoteVideoTrack) } + + // MARK: - Event Handlers + + func leaveCallViewTapped(sender: UIGestureRecognizer) { + guard sender.state == .recognized else { + return + } + OWSWindowManager.shared().leaveCallView() + } } diff --git a/Signal/src/util/OWSWindowManager.m b/Signal/src/util/OWSWindowManager.m index c46e2b9ef..f117efe99 100644 --- a/Signal/src/util/OWSWindowManager.m +++ b/Signal/src/util/OWSWindowManager.m @@ -10,24 +10,32 @@ NS_ASSUME_NONNULL_BEGIN +// Behind everything, especially the root window. const UIWindowLevel UIWindowLevel_Background = -1.f; + +// In front of the root window _and_ status bar +// but behind the screen blocking window. const UIWindowLevel UIWindowLevel_ReturnToCall(void); const UIWindowLevel UIWindowLevel_ReturnToCall(void) { - return UIWindowLevelNormal + 1.f; + return UIWindowLevelStatusBar + 1.f; } +// In front of the root window, behind the screen blocking window. const UIWindowLevel UIWindowLevel_CallView(void); const UIWindowLevel UIWindowLevel_CallView(void) { - return UIWindowLevelNormal + 2.f; + return UIWindowLevelNormal + 1.f; } +// In front of everything, including the status bar. const UIWindowLevel UIWindowLevel_ScreenBlocking(void); const UIWindowLevel UIWindowLevel_ScreenBlocking(void) { - return UIWindowLevelStatusBar + 1.f; + return UIWindowLevelStatusBar + 2.f; } -const int kReturnToCallWindowHeight = 40.f; +// TODO: Verify that this is the correct height for: +// CallKit, non-CallKit, incoming and outgoing calls. +const int kReturnToCallWindowHeight = 20.f; @implementation OWSWindowRootViewController @@ -47,9 +55,11 @@ const int kReturnToCallWindowHeight = 40.f; // UIWindowLevel_ReturnToCall @property (nonatomic) UIWindow *returnToCallWindow; +@property (nonatomic) UILabel *returnToCallLabel; // UIWindowLevel_CallView @property (nonatomic) UIWindow *callViewWindow; +@property (nonatomic) UINavigationController *callNavigationController; // UIWindowLevel_Background if inactive, // UIWindowLevel_ScreenBlocking() if active. @@ -105,13 +115,13 @@ const int kReturnToCallWindowHeight = 40.f; self.rootWindow = rootWindow; self.screenBlockingWindow = screenBlockingWindow; - self.returnToCallWindow = [OWSWindowManager createReturnToCallWindow:rootWindow]; - self.callViewWindow = [OWSWindowManager createCallViewWindow:rootWindow]; + self.returnToCallWindow = [self createReturnToCallWindow:rootWindow]; + self.callViewWindow = [self createCallViewWindow:rootWindow]; [self ensureWindowState]; } -+ (UIWindow *)createReturnToCallWindow:(UIWindow *)rootWindow +- (UIWindow *)createReturnToCallWindow:(UIWindow *)rootWindow { OWSAssertIsOnMainThread(); OWSAssert(rootWindow); @@ -125,13 +135,13 @@ const int kReturnToCallWindowHeight = 40.f; window.hidden = YES; window.windowLevel = UIWindowLevel_ReturnToCall(); window.opaque = YES; - // TODO: - window.backgroundColor = UIColor.ows_materialBlueColor; - window.backgroundColor = [UIColor redColor]; + // This is the color of the iOS "return to call" banner. + // TODO: What's the right color to use here? + UIColor *backgroundColor = [UIColor colorWithRGBHex:0x4cd964]; + window.backgroundColor = backgroundColor; UIViewController *viewController = [OWSWindowRootViewController new]; - viewController.view.backgroundColor = UIColor.ows_materialBlueColor; - viewController.view.backgroundColor = [UIColor redColor]; + viewController.view.backgroundColor = backgroundColor; UIView *rootView = viewController.view; rootView.userInteractionEnabled = YES; @@ -139,19 +149,20 @@ const int kReturnToCallWindowHeight = 40.f; action:@selector(returnToCallWasTapped:)]]; UILabel *label = [UILabel new]; - label.text = NSLocalizedString(@"CALL_WINDOW_RETURN_TO_CALL", @"Label for the 'return to call' indicator."); + label.text = NSLocalizedString(@"CALL_WINDOW_RETURN_TO_CALL", @"Label for the 'return to call' banner."); label.textColor = [UIColor whiteColor]; // TODO: Dynamic type? - label.font = [UIFont ows_mediumFontWithSize:18.f]; + label.font = [UIFont ows_regularFontWithSize:14.f]; [rootView addSubview:label]; [label autoCenterInSuperview]; + self.returnToCallLabel = label; window.rootViewController = viewController; return window; } -+ (UIWindow *)createCallViewWindow:(UIWindow *)rootWindow +- (UIWindow *)createCallViewWindow:(UIWindow *)rootWindow { OWSAssertIsOnMainThread(); OWSAssert(rootWindow); @@ -160,6 +171,7 @@ const int kReturnToCallWindowHeight = 40.f; window.hidden = YES; window.windowLevel = UIWindowLevel_CallView(); window.opaque = YES; + // TODO: What's the right color to use here? window.backgroundColor = [UIColor ows_materialBlueColor]; UIViewController *viewController = [OWSWindowRootViewController new]; @@ -168,6 +180,7 @@ const int kReturnToCallWindowHeight = 40.f; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; navigationController.navigationBarHidden = YES; + self.callNavigationController = navigationController; window.rootViewController = navigationController; @@ -193,9 +206,7 @@ const int kReturnToCallWindowHeight = 40.f; self.callViewController = callViewController; // Attach callViewController from window. - OWSAssert([self.callViewWindow.rootViewController isKindOfClass:[UINavigationController class]]); - UINavigationController *navigationController = (UINavigationController *)self.callViewWindow.rootViewController; - [navigationController pushViewController:callViewController animated:NO]; + [self.callNavigationController pushViewController:callViewController animated:NO]; self.isCallViewActive = YES; [self ensureWindowState]; @@ -213,9 +224,7 @@ const int kReturnToCallWindowHeight = 40.f; } // Dettach callViewController from window. - OWSAssert([self.callViewWindow.rootViewController isKindOfClass:[UINavigationController class]]); - UINavigationController *navigationController = (UINavigationController *)self.callViewWindow.rootViewController; - [navigationController popToRootViewControllerAnimated:NO]; + [self.callNavigationController popToRootViewControllerAnimated:NO]; self.callViewController = nil; self.isCallViewActive = NO; @@ -386,6 +395,19 @@ const int kReturnToCallWindowHeight = 40.f; if (self.returnToCallWindow.hidden) { DDLogInfo(@"%@ showing 'return to call' window.", self.logTag); + + [self.returnToCallLabel.layer removeAllAnimations]; + self.returnToCallLabel.alpha = 1.f; + [UIView animateWithDuration:1.f + delay:0.f + options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse + | UIViewAnimationOptionBeginFromCurrentState) + animations:^{ + self.returnToCallLabel.alpha = 0.f; + } + completion:^(BOOL finished) { + self.returnToCallLabel.alpha = 1.f; + }]; } self.returnToCallWindow.hidden = NO; @@ -400,6 +422,7 @@ const int kReturnToCallWindowHeight = 40.f; } self.returnToCallWindow.hidden = YES; + [self.returnToCallLabel.layer removeAllAnimations]; } - (void)showCallViewWindowIfNecessary diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 0008b922b..24f9d1be8 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -319,6 +319,9 @@ /* Accessibility label to switch to video call */ "CALL_VIEW_SWITCH_TO_VIDEO_LABEL" = "Switch to video call"; +/* Label for the 'return to call' indicator. */ +"CALL_WINDOW_RETURN_TO_CALL" = "Touch to return to call"; + /* notification action */ "CALLBACK_BUTTON_TITLE" = "Call Back"; From f4439f8af8f140580e10163c3ed7f5fbc5729e2f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 18 Apr 2018 13:33:29 -0400 Subject: [PATCH 05/12] Present calls using window manager. --- Signal/src/util/OWSWindowManager.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Signal/src/util/OWSWindowManager.m b/Signal/src/util/OWSWindowManager.m index f117efe99..9545c6177 100644 --- a/Signal/src/util/OWSWindowManager.m +++ b/Signal/src/util/OWSWindowManager.m @@ -205,7 +205,8 @@ const int kReturnToCallWindowHeight = 20.f; OWSAssert(!self.callViewController); self.callViewController = callViewController; - // Attach callViewController from window. + // Attach callViewController to window. + [self.callNavigationController popToRootViewControllerAnimated:NO]; [self.callNavigationController pushViewController:callViewController animated:NO]; self.isCallViewActive = YES; From 9e1021f1a43c794d383915531978a356a168c7f7 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 24 Apr 2018 10:55:28 -0400 Subject: [PATCH 06/12] Clean up ahead of PR. --- Signal/src/ViewControllers/CallViewController.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 42e4bdb75..6346ca9ce 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -237,9 +237,6 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, if !remoteVideoView.isHidden { shouldRemoteVideoControlsBeHidden = !shouldRemoteVideoControlsBeHidden } - - // TODO: Until we have a proper design, use background taps to leave call view. - OWSWindowManager.shared().leaveCallView() } func createVideoViews() { From 882d1ac61f4c53ee3eb5a821129f309b9182fbbc Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 24 Apr 2018 10:57:46 -0400 Subject: [PATCH 07/12] Clean up ahead of PR. --- Signal/src/util/OWSScreenLockUI.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Signal/src/util/OWSScreenLockUI.h b/Signal/src/util/OWSScreenLockUI.h index bbeeb3500..90317d713 100644 --- a/Signal/src/util/OWSScreenLockUI.h +++ b/Signal/src/util/OWSScreenLockUI.h @@ -4,7 +4,6 @@ NS_ASSUME_NONNULL_BEGIN -// TODO: Rename to window manager or somesuch. @interface OWSScreenLockUI : NSObject - (instancetype)init NS_UNAVAILABLE; From 6786c77237cc4c6e68093cc79a76d20d0fe3eead Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 24 Apr 2018 11:20:11 -0400 Subject: [PATCH 08/12] Update cocoapods. --- Gemfile.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d490c3783..d035415a7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,12 +12,12 @@ GEM atomos (0.1.2) babosa (1.0.2) claide (1.0.2) - cocoapods (1.4.0) + cocoapods (1.5.0) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.4.0) + cocoapods-core (= 1.5.0) cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.1.3, < 2.0) + cocoapods-downloader (>= 1.2.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) @@ -27,16 +27,16 @@ GEM escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.6.4) + molinillo (~> 0.6.5) nap (~> 1.0) ruby-macho (~> 1.1) - xcodeproj (>= 1.5.4, < 2.0) - cocoapods-core (1.4.0) + xcodeproj (>= 1.5.7, < 2.0) + cocoapods-core (1.5.0) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.1.3) + cocoapods-downloader (1.2.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) @@ -100,7 +100,7 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fourflusher (2.0.1) fuzzy_match (2.0.4) - gh_inspector (1.1.2) + gh_inspector (1.1.3) google-api-client (0.13.6) addressable (~> 2.5, >= 2.5.1) googleauth (~> 0.5) @@ -134,11 +134,11 @@ GEM mime-types-data (3.2016.0521) mini_magick (4.5.1) minitest (5.11.3) - molinillo (0.6.4) + molinillo (0.6.5) multi_json (1.12.2) multi_xml (0.6.0) multipart-post (2.0.0) - nanaimo (0.2.3) + nanaimo (0.2.5) nap (1.1.0) netrc (0.11.0) os (0.9.6) @@ -172,12 +172,12 @@ GEM unf_ext (0.0.7.4) unicode-display_width (1.3.0) word_wrap (1.0.0) - xcodeproj (1.5.6) - CFPropertyList (~> 2.3.3) + xcodeproj (1.5.7) + CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.2) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.3) + nanaimo (~> 0.2.4) xcpretty (0.2.8) rouge (~> 2.0.7) xcpretty-travis-formatter (0.0.4) From 1d94c2da75ec319f784594bbb382fbad3c388fd8 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 24 Apr 2018 14:00:16 -0400 Subject: [PATCH 09/12] Improve comments. --- Signal/src/ViewControllers/CallViewController.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 6346ca9ce..20c373205 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -410,6 +410,9 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, 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. self.present(actionSheetController, animated: true) } @@ -931,7 +934,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, dismissIfPossible(shouldDelay: false, ignoreNag: true, completion: { // Find the frontmost presented UIViewController from which to present the // settings views. - let fromViewController = UIApplication.shared.frontmostViewController + let fromViewController = UIApplication.shared.findFrontmostViewController(ignoringAlerts: true) assert(fromViewController != nil) // Construct the "settings" view & push the "privacy settings" view. From d7ae6fbbf43d2705957491c3622f7d9780805327 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 24 Apr 2018 17:00:39 -0400 Subject: [PATCH 10/12] Respond to CR. --- Signal/src/AppDelegate.m | 3 + .../ViewControllers/CallViewController.swift | 38 ++++----- Signal/src/util/OWSScreenLockUI.h | 4 + Signal/src/util/OWSScreenLockUI.m | 17 ++-- Signal/src/util/OWSWindowManager.m | 82 ++++++++----------- 5 files changed, 65 insertions(+), 79 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 25831337f..c048c3160 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -189,6 +189,9 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; } [OWSScreenLockUI.sharedManager setupWithRootWindow:self.window]; + [[OWSWindowManager sharedManager] setupWithRootWindow:self.window + screenBlockingWindow:OWSScreenLockUI.sharedManager.screenBlockingWindow]; + [OWSScreenLockUI.sharedManager startObserving]; // Ensure OWSContactsSyncing is instantiated. [OWSContactsSyncing sharedManager]; diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 20c373205..7130f7da9 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -24,19 +24,19 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, let contactsManager: OWSContactsManager - // MARK: Properties + // MARK: - Properties let thread: TSContactThread let call: SignalCall var hasDismissed = false - // MARK: Views + // MARK: - Views var hasConstraints = false var blurView: UIVisualEffectView! var dateFormatter: DateFormatter? - // MARK: Contact Views + // MARK: - Contact Views var contactNameLabel: MarqueeLabel! var contactAvatarView: AvatarImageView! @@ -44,7 +44,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, var callStatusLabel: UILabel! var callDurationTimer: Timer? - // MARK: Ongoing Call Controls + // MARK: - Ongoing Call Controls var ongoingCallView: UIView! @@ -59,14 +59,14 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, // call. // var textMessageButton: UIButton! - // MARK: Incoming Call Controls + // MARK: - Incoming Call Controls var incomingCallView: UIView! var acceptIncomingButton: UIButton! var declineIncomingButton: UIButton! - // MARK: Video Views + // MARK: - Video Views var remoteVideoView: RemoteVideoView! var localVideoView: RTCCameraPreviewView! @@ -84,7 +84,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } - // MARK: Settings Nag Views + // MARK: - Settings Nag Views var isShowingSettingsNag = false { didSet { @@ -96,7 +96,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, var settingsNagView: UIView! var settingsNagDescriptionLabel: UILabel! - // MARK: Audio Source + // MARK: - Audio Source var hasAlternateAudioSources: Bool { Logger.info("\(TAG) available audio sources: \(allAudioSources)") @@ -129,7 +129,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } - // MARK: Initializers + // MARK: - Initializers @available(*, unavailable, message: "use init(call:) constructor instead.") required init?(coder aDecoder: NSCoder) { @@ -155,7 +155,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } - // MARK: View Lifecycle + // MARK: - View Lifecycle override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) @@ -968,6 +968,13 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, preferences.setIsCallKitPrivacyEnabled(preferences.isCallKitPrivacyEnabled()) } + func didTapLeaveCall(sender: UIGestureRecognizer) { + guard sender.state == .recognized else { + return + } + OWSWindowManager.shared().leaveCallView() + } + // MARK: - CallObserver internal func stateDidChange(call: SignalCall, state: CallState) { @@ -996,7 +1003,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, self.updateCallUI(callState: call.state) } - // MARK: CallAudioServiceDelegate + // MARK: - CallAudioServiceDelegate func callAudioService(_ callAudioService: CallAudioService, didUpdateIsSpeakerphoneEnabled isSpeakerphoneEnabled: Bool) { SwiftAssertIsOnMainThread(#function) @@ -1140,13 +1147,4 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, updateLocalVideoTrack(localVideoTrack: localVideoTrack) updateRemoteVideoTrack(remoteVideoTrack: remoteVideoTrack) } - - // MARK: - Event Handlers - - func leaveCallViewTapped(sender: UIGestureRecognizer) { - guard sender.state == .recognized else { - return - } - OWSWindowManager.shared().leaveCallView() - } } diff --git a/Signal/src/util/OWSScreenLockUI.h b/Signal/src/util/OWSScreenLockUI.h index 90317d713..d8fad7c96 100644 --- a/Signal/src/util/OWSScreenLockUI.h +++ b/Signal/src/util/OWSScreenLockUI.h @@ -6,12 +6,16 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSScreenLockUI : NSObject +@property (nonatomic, readonly) UIWindow *screenBlockingWindow; + - (instancetype)init NS_UNAVAILABLE; + (instancetype)sharedManager; - (void)setupWithRootWindow:(UIWindow *)rootWindow; +- (void)startObserving; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/OWSScreenLockUI.m b/Signal/src/util/OWSScreenLockUI.m index b83f60cba..8f92c49a9 100644 --- a/Signal/src/util/OWSScreenLockUI.m +++ b/Signal/src/util/OWSScreenLockUI.m @@ -77,10 +77,6 @@ NS_ASSUME_NONNULL_BEGIN OWSAssertIsOnMainThread(); - _appIsInactiveOrBackground = [UIApplication sharedApplication].applicationState != UIApplicationStateActive; - - [self observeNotifications]; - OWSSingletonAssert(); return self; @@ -126,10 +122,16 @@ NS_ASSUME_NONNULL_BEGIN [self createScreenBlockingWindowWithRootWindow:rootWindow]; OWSAssert(self.screenBlockingWindow); - [[OWSWindowManager sharedManager] setupWithRootWindow:rootWindow screenBlockingWindow:self.screenBlockingWindow]; +} + +- (void)startObserving +{ + _appIsInactiveOrBackground = [UIApplication sharedApplication].applicationState != UIApplicationStateActive; + + [self observeNotifications]; // Default to screen protection until we know otherwise. - [self updateScreenBlockingWindow:ScreenLockUIStateNone animated:NO]; + [self updateScreenBlockingWindow:ScreenLockUIStateScreenProtection animated:NO]; // Initialize the screen lock state. // @@ -393,9 +395,6 @@ NS_ASSUME_NONNULL_BEGIN self.screenBlockingWindow = window; self.screenBlockingViewController = viewController; - - // Default to screen protection until we know otherwise. - [self updateScreenBlockingWindow:ScreenLockUIStateNone animated:NO]; } // The "screen blocking" window has three possible states: diff --git a/Signal/src/util/OWSWindowManager.m b/Signal/src/util/OWSWindowManager.m index 9545c6177..063f30cf1 100644 --- a/Signal/src/util/OWSWindowManager.m +++ b/Signal/src/util/OWSWindowManager.m @@ -151,10 +151,11 @@ const int kReturnToCallWindowHeight = 20.f; UILabel *label = [UILabel new]; label.text = NSLocalizedString(@"CALL_WINDOW_RETURN_TO_CALL", @"Label for the 'return to call' banner."); label.textColor = [UIColor whiteColor]; - // TODO: Dynamic type? + // System UI doesn't use dynamic type; neither do we. label.font = [UIFont ows_regularFontWithSize:14.f]; [rootView addSubview:label]; [label autoCenterInSuperview]; + OWSAssert(!self.returnToCallLabel); self.returnToCallLabel = label; window.rootViewController = viewController; @@ -180,6 +181,7 @@ const int kReturnToCallWindowHeight = 20.f; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; navigationController.navigationBarHidden = YES; + OWSAssert(!self.callNavigationController); self.callNavigationController = navigationController; window.rootViewController = navigationController; @@ -271,63 +273,43 @@ const int kReturnToCallWindowHeight = 20.f; OWSAssert(self.callViewWindow); OWSAssert(self.screenBlockingWindow); + // To avoid bad frames, we never want to hide the blocking window, so we manipulate + // its window level to "hide" it behind other windows. The other windows have fixed + // window level and are shown/hidden as necessary. + // + // Note that we always "hide" before we "show". if (self.isScreenBlockActive) { // Show Screen Block. - [self hideRootWindowIfNecessary]; - [self hideReturnToCallWindowIfNecessary]; - [self hideCallViewWindowIfNecessary]; - [self showScreenBlockWindowIfNecessary]; + [self ensureRootWindowHidden]; + [self ensureReturnToCallWindowHidden]; + [self ensureCallViewWindowHidden]; + [self ensureScreenBlockWindowShown]; } else if (self.callViewController && self.isCallViewActive) { // Show Call View. - [self hideRootWindowIfNecessary]; - [self hideReturnToCallWindowIfNecessary]; - [self showCallViewWindowIfNecessary]; - [self hideScreenBlockWindowIfNecessary]; + [self ensureRootWindowHidden]; + [self ensureReturnToCallWindowHidden]; + [self ensureCallViewWindowShown]; + [self ensureScreenBlockWindowHidden]; } else if (self.callViewController) { // Show Root Window + "Return to Call". - [self showRootWindowIfNecessary]; - [self showReturnToCallWindowIfNecessary]; - [self hideCallViewWindowIfNecessary]; - [self hideScreenBlockWindowIfNecessary]; + [self ensureRootWindowShown]; + [self ensureReturnToCallWindowShown]; + [self ensureCallViewWindowHidden]; + [self ensureScreenBlockWindowHidden]; } else { // Show Root Window - [self showRootWindowIfNecessary]; - [self hideReturnToCallWindowIfNecessary]; - [self hideCallViewWindowIfNecessary]; - [self hideScreenBlockWindowIfNecessary]; + [self ensureRootWindowShown]; + [self ensureReturnToCallWindowHidden]; + [self ensureCallViewWindowHidden]; + [self ensureScreenBlockWindowHidden]; } - - DDLogVerbose(@"%@ rootWindow: %d %f", self.logTag, self.rootWindow.hidden, self.rootWindow.windowLevel); - DDLogVerbose(@"%@ returnToCallWindow: %d %f", - self.logTag, - self.returnToCallWindow.hidden, - self.returnToCallWindow.windowLevel); - DDLogVerbose(@"%@ callViewWindow: %d %f", self.logTag, self.callViewWindow.hidden, self.callViewWindow.windowLevel); - DDLogVerbose(@"%@ screenBlockingWindow: %d %f", - self.logTag, - self.screenBlockingWindow.hidden, - self.screenBlockingWindow.windowLevel); - - dispatch_async(dispatch_get_main_queue(), ^{ - DDLogVerbose(@"%@ ...rootWindow: %d %f", self.logTag, self.rootWindow.hidden, self.rootWindow.windowLevel); - DDLogVerbose(@"%@ ...returnToCallWindow: %d %f", - self.logTag, - self.returnToCallWindow.hidden, - self.returnToCallWindow.windowLevel); - DDLogVerbose( - @"%@ ...callViewWindow: %d %f", self.logTag, self.callViewWindow.hidden, self.callViewWindow.windowLevel); - DDLogVerbose(@"%@ ...screenBlockingWindow: %d %f", - self.logTag, - self.screenBlockingWindow.hidden, - self.screenBlockingWindow.windowLevel); - }); } -- (void)showRootWindowIfNecessary +- (void)ensureRootWindowShown { OWSAssertIsOnMainThread(); @@ -368,7 +350,7 @@ const int kReturnToCallWindowHeight = 20.f; self.rootFrontmostViewController = nil; } -- (void)hideRootWindowIfNecessary +- (void)ensureRootWindowHidden { OWSAssertIsOnMainThread(); @@ -390,7 +372,7 @@ const int kReturnToCallWindowHeight = 20.f; self.rootWindow.hidden = YES; } -- (void)showReturnToCallWindowIfNecessary +- (void)ensureReturnToCallWindowShown { OWSAssertIsOnMainThread(); @@ -414,7 +396,7 @@ const int kReturnToCallWindowHeight = 20.f; self.returnToCallWindow.hidden = NO; } -- (void)hideReturnToCallWindowIfNecessary +- (void)ensureReturnToCallWindowHidden { OWSAssertIsOnMainThread(); @@ -426,7 +408,7 @@ const int kReturnToCallWindowHeight = 20.f; [self.returnToCallLabel.layer removeAllAnimations]; } -- (void)showCallViewWindowIfNecessary +- (void)ensureCallViewWindowShown { OWSAssertIsOnMainThread(); @@ -438,7 +420,7 @@ const int kReturnToCallWindowHeight = 20.f; [self.callViewWindow.rootViewController becomeFirstResponder]; } -- (void)hideCallViewWindowIfNecessary +- (void)ensureCallViewWindowHidden { OWSAssertIsOnMainThread(); @@ -449,7 +431,7 @@ const int kReturnToCallWindowHeight = 20.f; self.callViewWindow.hidden = YES; } -- (void)showScreenBlockWindowIfNecessary +- (void)ensureScreenBlockWindowShown { OWSAssertIsOnMainThread(); @@ -461,7 +443,7 @@ const int kReturnToCallWindowHeight = 20.f; [self.screenBlockingWindow.rootViewController becomeFirstResponder]; } -- (void)hideScreenBlockWindowIfNecessary +- (void)ensureScreenBlockWindowHidden { OWSAssertIsOnMainThread(); From 61cd5805f2596de0fcc5033e1af1d4cb955e7866 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 25 Apr 2018 09:45:32 -0400 Subject: [PATCH 11/12] Improve 'return to call' window layout. --- Signal/src/util/OWSWindowManager.m | 62 +++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/Signal/src/util/OWSWindowManager.m b/Signal/src/util/OWSWindowManager.m index 063f30cf1..8fc9a8faa 100644 --- a/Signal/src/util/OWSWindowManager.m +++ b/Signal/src/util/OWSWindowManager.m @@ -33,10 +33,6 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) return UIWindowLevelStatusBar + 2.f; } -// TODO: Verify that this is the correct height for: -// CallKit, non-CallKit, incoming and outgoing calls. -const int kReturnToCallWindowHeight = 20.f; - @implementation OWSWindowRootViewController - (BOOL)canBecomeFirstResponder @@ -118,6 +114,8 @@ const int kReturnToCallWindowHeight = 20.f; self.returnToCallWindow = [self createReturnToCallWindow:rootWindow]; self.callViewWindow = [self createCallViewWindow:rootWindow]; + [self updateReturnToCallWindowLayout]; + [self ensureWindowState]; } @@ -126,11 +124,15 @@ const int kReturnToCallWindowHeight = 20.f; OWSAssertIsOnMainThread(); OWSAssert(rootWindow); + DDLogVerbose(@"%@ updateReturnToCallWindowLayout", self.logTag); + + CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame; + DDLogVerbose(@"%@ statusBarFrame: %@", self.logTag, NSStringFromCGRect(statusBarFrame)); + // "Return to call" should remain at the top of the screen. - // - // TODO: Extend below the status bar. CGRect windowFrame = rootWindow.bounds; - windowFrame.size.height = kReturnToCallWindowHeight; + // Use zero height until updateReturnToCallWindowLayout. + windowFrame.size.height = 0; UIWindow *window = [[UIWindow alloc] initWithFrame:windowFrame]; window.hidden = YES; window.windowLevel = UIWindowLevel_ReturnToCall(); @@ -147,6 +149,7 @@ const int kReturnToCallWindowHeight = 20.f; rootView.userInteractionEnabled = YES; [rootView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(returnToCallWasTapped:)]]; + rootView.layoutMargins = UIEdgeInsetsZero; UILabel *label = [UILabel new]; label.text = NSLocalizedString(@"CALL_WINDOW_RETURN_TO_CALL", @"Label for the 'return to call' banner."); @@ -154,8 +157,11 @@ const int kReturnToCallWindowHeight = 20.f; // System UI doesn't use dynamic type; neither do we. label.font = [UIFont ows_regularFontWithSize:14.f]; [rootView addSubview:label]; - [label autoCenterInSuperview]; - OWSAssert(!self.returnToCallLabel); + + // returnToCallLabel uses manual layout. + // + // TODO: Is there a better way to do this? + label.translatesAutoresizingMaskIntoConstraints = NO; self.returnToCallLabel = label; window.rootViewController = viewController; @@ -163,6 +169,40 @@ const int kReturnToCallWindowHeight = 20.f; return window; } +- (void)updateReturnToCallWindowLayout +{ + OWSAssertIsOnMainThread(); + + CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame; + DDLogVerbose(@"%@ statusBarFrame: %@", self.logTag, NSStringFromCGRect(statusBarFrame)); + CGFloat statusBarHeight = statusBarFrame.size.height; + + CGRect windowFrame = self.rootWindow.bounds; + windowFrame.size.height = statusBarHeight + 20.f; + DDLogVerbose(@"%@ windowFrame: %@", self.logTag, NSStringFromCGRect(windowFrame)); + self.returnToCallWindow.frame = windowFrame; + self.returnToCallWindow.rootViewController.view.frame = windowFrame; + + [self.returnToCallLabel sizeToFit]; + CGRect labelFrame = self.returnToCallLabel.frame; + labelFrame.origin.x = floor(windowFrame.size.width - labelFrame.size.width); + self.returnToCallLabel.frame = labelFrame; + + UIView *rootView = self.returnToCallWindow.rootViewController.view; + + [rootView setNeedsLayout]; + [rootView layoutIfNeeded]; + + [self.returnToCallWindow logFrameWithLabel:@"returnToCallWindow"]; + [rootView logFrameWithLabel:@"returnToCallWindow view"]; + for (UIView *subview in rootView.subviews) { + [subview logFrameWithLabel:@"returnToCallWindow subview"]; + + [subview setNeedsLayout]; + [subview layoutIfNeeded]; + } +} + - (UIWindow *)createCallViewWindow:(UIWindow *)rootWindow { OWSAssertIsOnMainThread(); @@ -212,6 +252,8 @@ const int kReturnToCallWindowHeight = 20.f; [self.callNavigationController pushViewController:callViewController animated:NO]; self.isCallViewActive = YES; + [self updateReturnToCallWindowLayout]; + [self ensureWindowState]; } @@ -394,6 +436,8 @@ const int kReturnToCallWindowHeight = 20.f; } self.returnToCallWindow.hidden = NO; + + [self updateReturnToCallWindowLayout]; } - (void)ensureReturnToCallWindowHidden From baa6f27138281671d71148bfdcda502f96657d76 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 25 Apr 2018 09:58:22 -0400 Subject: [PATCH 12/12] Update Cocoapods. --- Pods | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pods b/Pods index 2913d5430..99c60d31e 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 2913d54307377858992ee731984fd662a438cab7 +Subproject commit 99c60d31efccb927361f1aa0c3d2cca238ca515c