From 91cd1af3f9d5111b0c1ebe395c7868ec33f5ba06 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 16 May 2018 10:02:08 -0400 Subject: [PATCH] Extract ReturnToCallViewController // FREEBIE --- Signal.xcodeproj/project.pbxproj | 4 + .../ReturnToCallViewController.swift | 70 ++++++++++ Signal/src/util/OWSWindowManager.h | 1 - Signal/src/util/OWSWindowManager.m | 128 +++--------------- 4 files changed, 94 insertions(+), 109 deletions(-) create mode 100644 Signal/src/ViewControllers/ReturnToCallViewController.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index dd0637e62..0cb6fc3e7 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -352,6 +352,7 @@ 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */; }; 459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 45A2F005204473A3002E978A /* NewMessage.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45A2F004204473A3002E978A /* NewMessage.aifc */; }; + 45A60E7320AC674100FB1ABF /* ReturnToCallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A60E7220AC674100FB1ABF /* ReturnToCallViewController.swift */; }; 45A663C51F92EC760027B59E /* GroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A663C41F92EC760027B59E /* GroupTableViewCell.swift */; }; 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; }; 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; }; @@ -998,6 +999,7 @@ 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWSQuotedReplyModel.h; sourceTree = ""; }; 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedReplyModel.m; sourceTree = ""; }; 45A2F004204473A3002E978A /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; name = NewMessage.aifc; path = Signal/AudioFiles/NewMessage.aifc; sourceTree = SOURCE_ROOT; }; + 45A60E7220AC674100FB1ABF /* ReturnToCallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReturnToCallViewController.swift; sourceTree = ""; }; 45A663C41F92EC760027B59E /* GroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupTableViewCell.swift; sourceTree = ""; }; 45A6DAD51EBBF85500893231 /* ReminderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReminderView.swift; sourceTree = ""; }; 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; @@ -1686,6 +1688,7 @@ 340FC897204DAC8D007AEB0F /* ThreadSettings */, 34D1F0BE1F8EC1760066283D /* Utils */, 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */, + 45A60E7220AC674100FB1ABF /* ReturnToCallViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -3331,6 +3334,7 @@ 340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */, 4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */, 340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */, + 45A60E7320AC674100FB1ABF /* ReturnToCallViewController.swift in Sources */, 340FC8B3204DAC8D007AEB0F /* AppSettingsViewController.m in Sources */, 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */, 45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */, diff --git a/Signal/src/ViewControllers/ReturnToCallViewController.swift b/Signal/src/ViewControllers/ReturnToCallViewController.swift new file mode 100644 index 000000000..2b5873d05 --- /dev/null +++ b/Signal/src/ViewControllers/ReturnToCallViewController.swift @@ -0,0 +1,70 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public protocol ReturnToCallViewControllerDelegate: class { + func returnToCallWasTapped(_ viewController: ReturnToCallViewController) +} + +@objc +public class ReturnToCallViewController: UIViewController { + + public weak var delegate: ReturnToCallViewControllerDelegate? + + let returnToCallLabel = UILabel() + + public func startAnimating() { + self.returnToCallLabel.layer.removeAllAnimations() + self.returnToCallLabel.alpha = 1 + UIView.animate(withDuration: 1, + delay: 0, + options: [.repeat, .autoreverse], + animations: { self.returnToCallLabel.alpha = 0 }, + completion: { _ in self.returnToCallLabel.alpha = 1 }) + } + + public func stopAnimating() { + self.returnToCallLabel.layer.removeAllAnimations() + } + + override public func loadView() { + self.view = UIView() + + // This is the color of the iOS "return to call" banner. + view.backgroundColor = UIColor(rgbHex: 0x4cd964) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView)) + view.addGestureRecognizer(tapGesture) + + view.addSubview(returnToCallLabel) + + // System UI doesn't use dynamic type for status bar; neither do we. + returnToCallLabel.font = UIFont.ows_regularFont(withSize: 14) + returnToCallLabel.text = NSLocalizedString("CALL_WINDOW_RETURN_TO_CALL", comment: "Label for the 'return to call' banner.") + returnToCallLabel.textColor = .white + + returnToCallLabel.autoPinEdge(toSuperviewEdge: .bottom, withInset: 2) + returnToCallLabel.setCompressionResistanceHigh() + returnToCallLabel.setContentHuggingHigh() + returnToCallLabel.autoHCenterInSuperview() + } + + @objc + public func didTapView(gestureRecognizer: UITapGestureRecognizer) { + self.delegate?.returnToCallWasTapped(self) + } + + override public func viewWillLayoutSubviews() { + Logger.debug("\(self.logTag) in \(#function)") + + super.viewWillLayoutSubviews() + } + + override public func viewDidLayoutSubviews() { + Logger.debug("\(self.logTag) in \(#function)") + + super.viewDidLayoutSubviews() + } +} diff --git a/Signal/src/util/OWSWindowManager.h b/Signal/src/util/OWSWindowManager.h index 15dde5100..6877c6514 100644 --- a/Signal/src/util/OWSWindowManager.h +++ b/Signal/src/util/OWSWindowManager.h @@ -29,7 +29,6 @@ extern const UIWindowLevel UIWindowLevel_Background; - (void)startCall:(UIViewController *)callViewController; - (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 832623158..0d5ba0e59 100644 --- a/Signal/src/util/OWSWindowManager.m +++ b/Signal/src/util/OWSWindowManager.m @@ -10,6 +10,8 @@ NS_ASSUME_NONNULL_BEGIN +const CGFloat OWSWindowManagerCallScreenHeight = 40; + // Behind everything, especially the root window. const UIWindowLevel UIWindowLevel_Background = -1.f; @@ -50,14 +52,14 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) #pragma mark - -@interface OWSWindowManager () +@interface OWSWindowManager () // UIWindowLevelNormal @property (nonatomic) UIWindow *rootWindow; // UIWindowLevel_ReturnToCall @property (nonatomic) UIWindow *returnToCallWindow; -@property (nonatomic) UILabel *returnToCallLabel; +@property (nonatomic) ReturnToCallViewController *returnToCallViewController; // UIWindowLevel_CallView @property (nonatomic) UIWindow *callViewWindow; @@ -117,8 +119,6 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) self.returnToCallWindow = [self createReturnToCallWindow:rootWindow]; self.callViewWindow = [self createCallViewWindow:rootWindow]; - [self updateReturnToCallWindowLayout]; - [self ensureWindowState]; } @@ -128,80 +128,22 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) OWSAssert(rootWindow); // "Return to call" should remain at the top of the screen. - CGRect windowFrame = rootWindow.bounds; - // Use zero height until updateReturnToCallWindowLayout. - windowFrame.size.height = 0; + CGRect windowFrame = UIScreen.mainScreen.bounds; + windowFrame.size.height = OWSWindowManagerCallScreenHeight; UIWindow *window = [[UIWindow alloc] initWithFrame:windowFrame]; window.hidden = YES; window.windowLevel = UIWindowLevel_ReturnToCall(); window.opaque = YES; - // 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 = backgroundColor; - - UIView *rootView = viewController.view; - 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."); - label.textColor = [UIColor whiteColor]; - // System UI doesn't use dynamic type; neither do we. - label.font = [UIFont ows_regularFontWithSize:14.f]; - [rootView addSubview:label]; - - [label autoPinBottomToSuperviewMargin]; - [label setCompressionResistanceHigh]; - [label setContentHuggingHigh]; - [label autoHCenterInSuperview]; - - // TODO animate... - - // returnToCallLabel uses manual layout. - // - // TODO: Is there a better way to do this? - label.translatesAutoresizingMaskIntoConstraints = NO; - self.returnToCallLabel = label; + ReturnToCallViewController *viewController = [ReturnToCallViewController new]; + self.returnToCallViewController = viewController; + viewController.delegate = self; window.rootViewController = viewController; return window; } -- (void)updateReturnToCallWindowLayout -{ - OWSAssertIsOnMainThread(); - - CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame; - CGFloat statusBarHeight = statusBarFrame.size.height; - - CGRect windowFrame = self.rootWindow.bounds; - windowFrame.size.height = statusBarHeight + 20.f; - 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]; - - for (UIView *subview in rootView.subviews) { - [subview setNeedsLayout]; - [subview layoutIfNeeded]; - } -} - - (UIWindow *)createCallViewWindow:(UIWindow *)rootWindow { OWSAssertIsOnMainThread(); @@ -251,8 +193,6 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) [self.callNavigationController pushViewController:callViewController animated:NO]; self.isCallViewActive = YES; - [self updateReturnToCallWindowLayout]; - [self ensureWindowState]; } @@ -286,7 +226,7 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) [self ensureWindowState]; } -- (void)returnToCallView +- (void)showCallView { OWSAssertIsOnMainThread(); OWSAssert(self.callViewController); @@ -358,18 +298,12 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) DDLogInfo(@"%@ showing root window.", self.logTag); } - static CGRect defaultFrame; - static CGRect frameWithActiveCall; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultFrame = self.rootWindow.frame; - - CGFloat callBannerHeight = self.returnToCallWindow.frame.size.height; - frameWithActiveCall - = CGRectMake(0, callBannerHeight, defaultFrame.size.width, defaultFrame.size.height - callBannerHeight); - }); - + CGRect defaultFrame = [UIScreen mainScreen].bounds; if (isActiveCall) { + CGRect frameWithActiveCall = CGRectMake(0, + OWSWindowManagerCallScreenHeight, + defaultFrame.size.width, + defaultFrame.size.height - OWSWindowManagerCallScreenHeight); self.rootWindow.frame = frameWithActiveCall; } else { self.rootWindow.frame = defaultFrame; @@ -396,26 +330,8 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) { OWSAssertIsOnMainThread(); - 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; - - [self updateReturnToCallWindowLayout]; + [self.returnToCallViewController startAnimating]; } - (void)ensureReturnToCallWindowHidden @@ -427,7 +343,7 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) } self.returnToCallWindow.hidden = YES; - [self.returnToCallLabel.layer removeAllAnimations]; + [self.returnToCallViewController stopAnimating]; } - (void)ensureCallViewWindowShown @@ -478,15 +394,11 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) self.screenBlockingWindow.windowLevel = UIWindowLevel_Background; } -#pragma mark - Events +#pragma mark - ReturnToCallViewControllerDelegate -- (void)returnToCallWasTapped:(UIGestureRecognizer *)sender +- (void)returnToCallWasTapped:(ReturnToCallViewController *)viewController { - if (sender.state != UIGestureRecognizerStateRecognized) { - return; - } - - [self returnToCallView]; + [self showCallView]; } @end