mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Add window manager. Move call view to a separate window.
This commit is contained in:
parent
9364af9b85
commit
7345ab2e4e
|
@ -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 = "<group>"; };
|
||||
34612A041FD7238500532771 /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = "<group>"; };
|
||||
34612A051FD7238500532771 /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = "<group>"; };
|
||||
34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWindowManager.m; sourceTree = "<group>"; };
|
||||
34641E1120878FB000E2EDE5 /* OWSWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSWindowManager.h; sourceTree = "<group>"; };
|
||||
34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSScreenLock.swift; sourceTree = "<group>"; };
|
||||
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 */,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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)
|
||||
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) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// TODO: Rename to window manager or somesuch.
|
||||
@interface OWSScreenLockUI : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
|
||||
#import "OWSScreenLockUI.h"
|
||||
#import "OWSWindowManager.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalMessaging/ScreenLockViewController.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
|
@ -10,11 +11,10 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
const UIWindowLevel UIWindowLevel_Background = -1.f;
|
||||
|
||||
@interface OWSScreenLockUI () <ScreenLockViewDelegate>
|
||||
|
||||
@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
|
||||
|
|
36
Signal/src/util/OWSWindowManager.h
Normal file
36
Signal/src/util/OWSWindowManager.h
Normal file
|
@ -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
|
460
Signal/src/util/OWSWindowManager.m
Normal file
460
Signal/src/util/OWSWindowManager.m
Normal file
|
@ -0,0 +1,460 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSWindowManager.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalMessaging/UIColor+OWS.h>
|
||||
#import <SignalMessaging/UIFont+OWS.h>
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
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
|
Loading…
Reference in a new issue