diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 820d90aec..ce211210d 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -159,7 +159,6 @@ 45A6DAD71EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; }; 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; }; 45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; }; - 45B201761DAECBFE00C461E0 /* HighlightableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B201751DAECBFE00C461E0 /* HighlightableLabel.swift */; }; 45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; }; 45BB93391E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; }; 45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BD60811DE9547E00A8F436 /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -582,7 +581,6 @@ 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 = ""; }; 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = ""; }; - 45B201751DAECBFE00C461E0 /* HighlightableLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableLabel.swift; sourceTree = ""; }; 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+featureSupport.swift"; sourceTree = ""; }; 45BD60811DE9547E00A8F436 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; 45BFFFA61D898AF0004A12A7 /* OWSStaleNotificationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSStaleNotificationObserver.h; path = Observers/OWSStaleNotificationObserver.h; sourceTree = ""; }; @@ -1355,7 +1353,6 @@ 451764281DE939FD00EDB8B9 /* ContactCell.xib */, 76EB052E18170B33006006FC /* ContactTableViewCell.h */, 76EB052F18170B33006006FC /* ContactTableViewCell.m */, - 45B201751DAECBFE00C461E0 /* HighlightableLabel.swift */, 4531C9C21DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.h */, 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */, 3453D8E91EC0D4ED003F9E6F /* OWSAlerts.swift */, @@ -2218,7 +2215,6 @@ 45666F7B1D9C0533008FE134 /* OWSDatabaseMigration.m in Sources */, B90418E6183E9DD40038554A /* DateUtil.m in Sources */, 34F3089C1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m in Sources */, - 45B201761DAECBFE00C461E0 /* HighlightableLabel.swift in Sources */, 459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Signal/src/Storyboard/Main.storyboard b/Signal/src/Storyboard/Main.storyboard index 58eecda8f..bcf158ee1 100644 --- a/Signal/src/Storyboard/Main.storyboard +++ b/Signal/src/Storyboard/Main.storyboard @@ -1,21 +1,16 @@ - + - + - - - Menlo-Regular - - @@ -172,215 +167,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -594,29 +386,8 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Signal/src/UIStoryboard+OWS.swift b/Signal/src/UIStoryboard+OWS.swift index 0b57c7342..0b93a7ea9 100644 --- a/Signal/src/UIStoryboard+OWS.swift +++ b/Signal/src/UIStoryboard+OWS.swift @@ -12,8 +12,4 @@ extension UIStoryboard { class var main: UIStoryboard { return UIStoryboard(name: StoryboardName.main.rawValue, bundle: Bundle.main) } - - class func instantiateFingerprintViewController() -> FingerprintViewController { - return self.main.instantiateViewController(withIdentifier: "FingerprintViewController") as! FingerprintViewController - } } diff --git a/Signal/src/UIView+OWS.h b/Signal/src/UIView+OWS.h index 2e31f28d7..a9230dfa3 100644 --- a/Signal/src/UIView+OWS.h +++ b/Signal/src/UIView+OWS.h @@ -28,6 +28,11 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value); - (void)autoHCenterInSuperview; - (void)autoVCenterInSuperview; +- (void)autoPinWidthToWidthOfView:(UIView *)view; +- (void)autoPinHeightToHeightOfView:(UIView *)view; + +- (NSLayoutConstraint *)autoPinToSquareAspectRatio; + #pragma mark - Content Hugging and Compression Resistance - (void)setContentHuggingLow; diff --git a/Signal/src/UIView+OWS.m b/Signal/src/UIView+OWS.m index 01623dfc0..f0379e3b5 100644 --- a/Signal/src/UIView+OWS.m +++ b/Signal/src/UIView+OWS.m @@ -65,6 +65,37 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) [self autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.superview]; } +- (void)autoPinWidthToWidthOfView:(UIView *)view +{ + OWSAssert(view); + + [self autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:view]; + [self autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:view]; +} + +- (void)autoPinHeightToHeightOfView:(UIView *)view +{ + OWSAssert(view); + + [self autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:view]; + [self autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:view]; +} + +- (NSLayoutConstraint *)autoPinToSquareAspectRatio +{ + self.translatesAutoresizingMaskIntoConstraints = NO; + + NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeHeight + multiplier:1.f + constant:0.f]; + [constraint autoInstall]; + return constraint; +} + #pragma mark - Content Hugging and Compression Resistance - (void)setContentHuggingLow diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index 612f777ef..efb399ac4 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -1180,16 +1180,11 @@ typedef enum : NSUInteger { // return from FingerprintViewController. [self dismissKeyBoard]; - UIViewController *viewController = - [[UIStoryboard main] instantiateViewControllerWithIdentifier:@"FingerprintViewController"]; - if (![viewController isKindOfClass:[FingerprintViewController class]]) { - OWSAssert(NO); - DDLogError(@"%@ expecting fingerprint view controller, but got: %@", self.tag, viewController); - return; - } - FingerprintViewController *fingerprintViewController = (FingerprintViewController *)viewController; + FingerprintViewController *fingerprintViewController = [FingerprintViewController new]; [fingerprintViewController configureWithRecipientId:recipientId]; - [self presentViewController:fingerprintViewController animated:YES completion:nil]; + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:fingerprintViewController]; + [self presentViewController:navigationController animated:YES completion:nil]; } #pragma mark - Calls diff --git a/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.h b/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.h index d90462ea9..080608b18 100644 --- a/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.h +++ b/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.h @@ -4,6 +4,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @class SignalAttachment; @protocol OWSTextViewPasteDelegate @@ -21,3 +23,5 @@ @property (weak, nonatomic) id textViewPasteDelegate; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.m b/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.m index e92ea090e..27081c8e3 100644 --- a/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.m +++ b/Signal/src/ViewControllers/ConversationView/OWSMessagesComposerTextView.m @@ -5,6 +5,8 @@ #import "OWSMessagesComposerTextView.h" #import "Signal-Swift.h" +NS_ASSUME_NONNULL_BEGIN + @implementation OWSMessagesComposerTextView - (BOOL)canBecomeFirstResponder @@ -19,7 +21,7 @@ return ([SignalAttachment pasteboardHasPossibleAttachment] && ![SignalAttachment pasteboardHasText]); } -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender { if (action == @selector(paste:)) { if ([self pasteboardHasPossibleAttachment]) { @@ -29,7 +31,7 @@ return [super canPerformAction:action withSender:sender]; } -- (void)paste:(id)sender +- (void)paste:(nullable id)sender { if ([self pasteboardHasPossibleAttachment]) { SignalAttachment *attachment = [SignalAttachment attachmentFromPasteboard]; @@ -66,3 +68,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.h b/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.h index 03b18140b..f5dac38c8 100644 --- a/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.h +++ b/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.h @@ -4,6 +4,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface OWSMessagesInputToolbar : JSQMessagesInputToolbar - (void)showVoiceMemoUI; @@ -13,3 +15,5 @@ - (void)setVoiceMemoUICancelAlpha:(CGFloat)cancelAlpha; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.m b/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.m index a31bbba0b..550e5c5a7 100644 --- a/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/OWSMessagesInputToolbar.m @@ -10,6 +10,8 @@ #import "ViewControllerUtils.h" #import +NS_ASSUME_NONNULL_BEGIN + @interface OWSMessagesInputToolbar () @property (nonatomic) UIView *voiceMemoUI; @@ -229,3 +231,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.h b/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.h index 03665698a..8853d2d1b 100644 --- a/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.h +++ b/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.h @@ -4,6 +4,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @protocol OWSVoiceMemoGestureDelegate - (void)voiceMemoGestureDidStart; @@ -39,3 +41,5 @@ - (void)cancelVoiceMemoIfNecessary; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.m b/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.m index 44977ae91..22ffca174 100644 --- a/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.m +++ b/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentView.m @@ -5,6 +5,8 @@ #import "OWSMessagesToolbarContentView.h" #import "UIColor+OWS.h" +NS_ASSUME_NONNULL_BEGIN + @interface OWSMessagesToolbarContentView () @property (nonatomic) BOOL shouldShowVoiceMemoButton; @@ -216,3 +218,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/FingerprintViewController.m b/Signal/src/ViewControllers/FingerprintViewController.m index fc51c7a8c..11d25d73d 100644 --- a/Signal/src/ViewControllers/FingerprintViewController.m +++ b/Signal/src/ViewControllers/FingerprintViewController.m @@ -4,7 +4,9 @@ #import "FingerprintViewController.h" #import "Environment.h" +#import "OWSBezierPathView.h" #import "OWSConversationSettingsTableViewController.h" +#import "OWSQRCodeScanningViewController.h" #import "Signal-Swift.h" #import "UIUtil.h" #import "UIViewController+CameraPermissions.h" @@ -19,26 +21,41 @@ NS_ASSUME_NONNULL_BEGIN -@interface FingerprintViewController () +typedef void (^CustomLayoutBlock)(); + +@interface CustomLayoutView : UIView + +@property (nonatomic) CustomLayoutBlock layoutBlock; + +@end + +#pragma mark - + +@implementation CustomLayoutView + +- (void)layoutSubviews +{ + self.layoutBlock(); +} + +@end + +#pragma mark - + +@interface FingerprintViewController () @property (nonatomic) TSStorageManager *storageManager; @property (nonatomic) OWSFingerprint *fingerprint; @property (nonatomic) NSString *contactName; @property (nonatomic) OWSQRCodeScanningViewController *qrScanningController; -@property (nonatomic) IBOutlet UINavigationBar *modalNavigationBar; -@property (nonatomic) IBOutlet UIBarButtonItem *dismissModalButton; -@property (nonatomic) IBOutlet UIBarButtonItem *shareButton; -@property (nonatomic) IBOutlet UIView *qrScanningView; -@property (nonatomic) IBOutlet UILabel *scanningInstructions; -@property (nonatomic) IBOutlet UIView *scanningContainer; -@property (nonatomic) IBOutlet UIView *instructionsContainer; -@property (nonatomic) IBOutlet UIView *qrContainer; -@property (nonatomic) IBOutlet UIView *privacyVerificationQRCodeFrame; -@property (nonatomic) IBOutlet UIImageView *privacyVerificationQRCode; -@property (nonatomic) IBOutlet OWSHighlightableLabel *privacyVerificationFingerprint; -@property (nonatomic) IBOutlet UILabel *instructionsLabel; -@property (nonatomic) IBOutlet UIButton *scanButton; +@property (nonatomic) UIBarButtonItem *shareButton; +@property (nonatomic) UIView *mainView; +@property (nonatomic) UIView *referenceView; +@property (nonatomic) UIView *cameraView; + +@property (nonatomic) NSLayoutConstraint *verticalAlignmentConstraint; +@property (nonatomic) BOOL isScanning; @end @@ -63,59 +80,234 @@ NS_ASSUME_NONNULL_BEGIN [builder fingerprintWithTheirSignalId:recipientId theirIdentityKey:recipientIdentity.identityKey]; } -- (void)viewDidLoad +- (void)loadView { - [super viewDidLoad]; + [super loadView]; + + self.title = NSLocalizedString(@"PRIVACY_VERIFICATION_TITLE", @"Navbar title"); - self.navigationItem.leftBarButtonItem = self.dismissModalButton; + self.navigationItem.leftBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop + target:self + action:@selector(closeButton)]; + self.shareButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction + target:self + action:@selector(didTapShareButton)]; self.navigationItem.rightBarButtonItem = self.shareButton; - [self.modalNavigationBar pushNavigationItem:self.navigationItem animated:NO]; - - // HACK for transparent navigation bar. - [self.modalNavigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault]; - self.modalNavigationBar.shadowImage = [UIImage new]; - self.modalNavigationBar.translucent = YES; - - // HACK to get full width preview layer - CGRect oldFrame = self.qrScanningView.frame; - CGRect newFrame = CGRectMake(oldFrame.origin.x, - oldFrame.origin.y, - self.view.frame.size.width, - self.view.frame.size.height / 2.0f - oldFrame.origin.y); - self.qrScanningView.frame = newFrame; - // END HACK to get full width preview layer - self.title = NSLocalizedString(@"PRIVACY_VERIFICATION_TITLE", @"Navbar title"); - NSString *instructionsFormat = NSLocalizedString(@"PRIVACY_VERIFICATION_INSTRUCTIONS", - @"Paragraph(s) shown alongside the safety number when verifying privacy with {{contact name}}"); - self.instructionsLabel.text = [NSString stringWithFormat:instructionsFormat, self.contactName]; + [self createViews]; +} - NSString *scanTitle = NSLocalizedString(@"SCAN_CODE_ACTION", - @"Button label presented with camera icon while verifying privacy credentials. Shows the camera interface."); - [self.scanButton setTitle:scanTitle forState:UIControlStateNormal]; - self.scanningInstructions.text +- (void)createViews +{ + UIColor *darkGrey = [UIColor colorWithRGBHex:0x404040]; + + self.view.backgroundColor = [UIColor whiteColor]; + + UIView *referenceView = [UIView new]; + self.referenceView = referenceView; + [self.view addSubview:referenceView]; + [referenceView autoPinWidthToSuperview]; + [referenceView autoPinToTopLayoutGuideOfViewController:self withInset:0]; + [referenceView autoPinToBottomLayoutGuideOfViewController:self withInset:0]; + + UIView *mainView = [UIView new]; + self.mainView = mainView; + mainView.backgroundColor = [UIColor whiteColor]; + [self.view addSubview:mainView]; + [mainView autoPinWidthToSuperview]; + [[NSLayoutConstraint constraintWithItem:mainView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:referenceView + attribute:NSLayoutAttributeHeight + multiplier:1.0 + constant:0.f] autoInstall]; + [mainView + addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(mainViewTapped:)]]; + mainView.userInteractionEnabled = YES; + + UIView *cameraView = [UIView new]; + self.cameraView = cameraView; + cameraView.backgroundColor = [UIColor whiteColor]; + [self.view addSubview:cameraView]; + [cameraView autoPinWidthToSuperview]; + [cameraView autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:mainView]; + [cameraView autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:mainView]; + + self.qrScanningController = [OWSQRCodeScanningViewController new]; + self.qrScanningController.scanDelegate = self; + [cameraView addSubview:self.qrScanningController.view]; + [self.qrScanningController.view autoPinWidthToSuperview]; + [self.qrScanningController.view autoSetDimension:ALDimensionHeight toSize:270]; + [self.qrScanningController.view autoPinEdgeToSuperviewEdge:ALEdgeTop]; + + UILabel *cameraInstructionLabel = [UILabel new]; + cameraInstructionLabel.text = NSLocalizedString(@"SCAN_CODE_INSTRUCTIONS", @"label presented once scanning (camera) view is visible."); + cameraInstructionLabel.font = [UIFont ows_regularFontWithSize:14.f]; + cameraInstructionLabel.textColor = darkGrey; + cameraInstructionLabel.textAlignment = NSTextAlignmentCenter; + cameraInstructionLabel.numberOfLines = 0; + cameraInstructionLabel.lineBreakMode = NSLineBreakByWordWrapping; + [cameraView addSubview:cameraInstructionLabel]; + [cameraInstructionLabel autoPinWidthToSuperviewWithMargin:16.f]; + [cameraInstructionLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10.f]; + [cameraInstructionLabel autoPinEdge:ALEdgeTop + toEdge:ALEdgeBottom + ofView:self.qrScanningController.view + withOffset:10.f]; + + // Scan Button + UIView *scanButton = [UIView new]; + [scanButton + addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scanButtonTapped:)]]; + [mainView addSubview:scanButton]; + [scanButton autoPinWidthToSuperview]; + [scanButton autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:ScaleFromIPhone5To7Plus(12.f, 25.f)]; + + UILabel *scanButtonLabel = [UILabel new]; + scanButtonLabel.text = NSLocalizedString(@"SCAN_CODE_ACTION", + @"Button label presented with camera icon while verifying privacy credentials. Shows the camera interface."); + scanButtonLabel.font = [UIFont ows_regularFontWithSize:18.f]; + scanButtonLabel.textColor = darkGrey; + [scanButton addSubview:scanButtonLabel]; + [scanButtonLabel autoHCenterInSuperview]; + [scanButtonLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom]; + + UIImage *scanButtonImage = [UIImage imageNamed:@"btnCamera--white"]; + OWSAssert(scanButtonImage); + UIImageView *scanButtonImageView = [UIImageView new]; + scanButtonImageView.image = [scanButtonImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + scanButtonImageView.tintColor = darkGrey; + + [scanButton addSubview:scanButtonImageView]; + [scanButtonImageView autoHCenterInSuperview]; + [scanButtonImageView autoPinEdgeToSuperviewEdge:ALEdgeTop]; + [scanButtonImageView autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:scanButtonLabel withOffset:-5.f]; + + // Instructions + NSString *instructionsFormat = NSLocalizedString(@"PRIVACY_VERIFICATION_INSTRUCTIONS", + @"Paragraph(s) shown alongside the safety number when verifying privacy with {{contact name}}"); + UILabel *instructionsLabel = [UILabel new]; + instructionsLabel.text = [NSString stringWithFormat:instructionsFormat, self.contactName]; + instructionsLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 16.f)]; + instructionsLabel.textColor = darkGrey; + instructionsLabel.textAlignment = NSTextAlignmentCenter; + instructionsLabel.numberOfLines = 0; + instructionsLabel.lineBreakMode = NSLineBreakByWordWrapping; + [mainView addSubview:instructionsLabel]; + [instructionsLabel autoPinWidthToSuperviewWithMargin:16.f]; + [instructionsLabel autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:scanButton withOffset:-ScaleFromIPhone5To7Plus(20.f, 30.f)]; + + // Fingerprint Label + UILabel *fingerprintLabel = [UILabel new]; + fingerprintLabel.text = self.fingerprint.displayableText; + fingerprintLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:23.f]; + fingerprintLabel.textColor = darkGrey; + fingerprintLabel.numberOfLines = 3; + fingerprintLabel.lineBreakMode = NSLineBreakByTruncatingTail; + fingerprintLabel.adjustsFontSizeToFitWidth = YES; + [fingerprintLabel + addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(fingerprintLabelTapped:)]]; + fingerprintLabel.userInteractionEnabled = YES; + [mainView addSubview:fingerprintLabel]; + [fingerprintLabel autoPinWidthToSuperviewWithMargin:36.f]; + [fingerprintLabel autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:instructionsLabel withOffset:-ScaleFromIPhone5To7Plus(8.f, 15.f)]; + + // Fingerprint Image + CustomLayoutView *fingerprintView = [CustomLayoutView new]; + [mainView addSubview:fingerprintView]; + [fingerprintView autoPinWidthToSuperview]; + [fingerprintView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:ScaleFromIPhone5To7Plus(10.f, 25.f)]; + [fingerprintView autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:fingerprintLabel withOffset:-ScaleFromIPhone5To7Plus(10.f, 15.f)]; + [fingerprintView + addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(fingerprintViewTapped:)]]; + fingerprintView.userInteractionEnabled = YES; + + OWSBezierPathView *fingerprintCircle = [OWSBezierPathView new]; + [fingerprintCircle setConfigureShapeLayerBlock:^(CAShapeLayer *layer, CGRect bounds) { + layer.fillColor = darkGrey.CGColor; + CGFloat size = MIN(bounds.size.width, bounds.size.height); + CGRect circle = CGRectMake((bounds.size.width - size) * 0.5f, (bounds.size.height - size) * 0.5f, size, size); + layer.path = [UIBezierPath bezierPathWithOvalInRect:circle].CGPath; + }]; + [fingerprintView addSubview:fingerprintCircle]; + [fingerprintCircle autoPinWidthToSuperview]; + [fingerprintCircle autoPinHeightToSuperview]; + + UIImageView *fingerprintImageView = [UIImageView new]; + fingerprintImageView.image = self.fingerprint.image; + fingerprintImageView.layer.magnificationFilter = kCAFilterNearest; + fingerprintImageView.layer.minificationFilter = kCAFilterNearest; + [fingerprintView addSubview:fingerprintImageView]; + + fingerprintView.layoutBlock = ^{ + CGFloat size = round(MIN(fingerprintView.width, fingerprintView.height) * 0.65f); + fingerprintImageView.frame = CGRectMake( + round((fingerprintView.width - size) * 0.5f), round((fingerprintView.height - size) * 0.5f), size, size); + }; - // Safety numbers and QR Code - self.privacyVerificationFingerprint.text = self.fingerprint.displayableText; - self.privacyVerificationQRCode.image = self.fingerprint.image; + [self updateLayoutForIsScanning:NO animated:NO]; +} - // Don't antialias QRCode - self.privacyVerificationQRCode.layer.magnificationFilter = kCAFilterNearest; +- (void)showScanningViews +{ + [self updateLayoutForIsScanning:YES animated:YES]; +} - self.privacyVerificationFingerprint.delegate = self; +- (void)hideScanningViews +{ + [self updateLayoutForIsScanning:NO animated:YES]; } -- (void)viewDidLayoutSubviews +- (void)updateLayoutForIsScanning:(BOOL)isScanning animated:(BOOL)animated { - [super viewDidLayoutSubviews]; - - // On iOS8, the QRCodeFrame hasn't been layed out yet (causing the QRCode to be hidden), force it here. - [self.privacyVerificationQRCodeFrame setNeedsLayout]; - [self.privacyVerificationQRCodeFrame layoutIfNeeded]; - // Round QR Code. - self.privacyVerificationQRCodeFrame.layer.masksToBounds = YES; - self.privacyVerificationQRCodeFrame.layer.cornerRadius = self.privacyVerificationQRCodeFrame.frame.size.height / 2; + self.isScanning = isScanning; + + if (self.verticalAlignmentConstraint) { + [NSLayoutConstraint deactivateConstraints:@[ self.verticalAlignmentConstraint ]]; + } + if (isScanning) { + self.verticalAlignmentConstraint = + [self.cameraView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.referenceView]; + } else { + self.verticalAlignmentConstraint = + [self.mainView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.referenceView]; + } + [self.view setNeedsLayout]; + + // Show scanning views immediately. + if (isScanning) { + self.shareButton.enabled = NO; + self.cameraView.hidden = NO; + } + + void (^completion)() = ^{ + if (!isScanning) { + // Hide scanning views after they are offscreen. + self.shareButton.enabled = YES; + self.cameraView.hidden = YES; + } + }; + + if (animated) { + [UIView animateWithDuration:0.4 + delay:0.0 + options:UIViewAnimationOptionCurveEaseInOut + animations:^{ + [self.view layoutSubviews]; + } + completion:^(BOOL finished) { + if (finished) { + completion(); + } + }]; + } else { + completion(); + } } - (void)viewWillAppear:(BOOL)animated @@ -132,23 +324,8 @@ NS_ASSUME_NONNULL_BEGIN } } -- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(nullable id)sender -{ - if ([segue.identifier isEqualToString:@"embedIdentityQRScanner"]) { - OWSQRCodeScanningViewController *qrScanningController - = (OWSQRCodeScanningViewController *)segue.destinationViewController; - self.qrScanningController = qrScanningController; - qrScanningController.scanDelegate = self; - } -} - #pragma mark - HilightableLableDelegate -- (void)didHighlightLabel:(OWSHighlightableLabel *)label completion:(nullable void (^)(void))completionHandler -{ - [self showSharingActivityWithCompletion:completionHandler]; -} - - (void)showSharingActivityWithCompletion:(nullable void (^)(void))completionHandler { DDLogDebug(@"%@ Sharing safety numbers", self.tag); @@ -197,21 +374,16 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Action -- (IBAction)closeButtonAction:(id)sender +- (void)closeButton { [self dismissViewControllerAnimated:YES completion:nil]; } -- (IBAction)didTapShareButton +- (void)didTapShareButton { [self showSharingActivityWithCompletion:nil]; } -- (IBAction)didTouchUpInsideScanButton:(id)sender -{ - [self showScanner]; -} - - (void)showScanner { [self ows_askForCameraPermissions:^{ @@ -219,21 +391,11 @@ NS_ASSUME_NONNULL_BEGIN // Camera stops capturing when "sharing" while in capture mode. // Also, it's less obvious whats being "shared" at this point, // so just disable sharing when in capture mode. - self.shareButton.enabled = NO; DDLogInfo(@"%@ Showing Scanner", self.tag); - self.qrScanningView.hidden = NO; - self.scanningInstructions.hidden = NO; - [UIView animateWithDuration:0.4 - delay:0.0 - options:UIViewAnimationOptionCurveEaseInOut - animations:^{ - self.scanningContainer.frame = self.qrContainer.frame; - self.qrContainer.frame = self.instructionsContainer.frame; - self.instructionsContainer.alpha = 0.0f; - } - completion:nil]; - + + [self showScanningViews]; + [self.qrScanningController startCapture]; } alertActionHandler:nil]; @@ -293,17 +455,7 @@ NS_ASSUME_NONNULL_BEGIN UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:dismissText style:UIAlertActionStyleCancel handler: ^(UIAlertAction *action){ // Restore previous layout - self.shareButton.enabled = YES; - self.qrScanningView.hidden = YES; - self.scanningInstructions.hidden = YES; - [UIView animateWithDuration:0.4 - delay:0.0 - options:UIViewAnimationOptionCurveEaseInOut - animations:^{ - self.instructionsContainer.alpha = 1.0f; - self.qrContainer.frame = self.scanningContainer.frame; - } - completion:nil]; + [self hideScanningViews]; }]; [failureAlertController addAction:dismissAction]; @@ -321,10 +473,48 @@ NS_ASSUME_NONNULL_BEGIN - (void)dismissViewControllerAnimated:(BOOL)flag completion:(nullable void (^)(void))completion { - self.qrScanningView.hidden = YES; + [self updateLayoutForIsScanning:NO animated:NO]; [super dismissViewControllerAnimated:flag completion:completion]; } +- (void)scanButtonTapped:(UIGestureRecognizer *)gestureRecognizer +{ + if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) { + [self showScanner]; + } +} + +- (void)fingerprintLabelTapped:(UIGestureRecognizer *)gestureRecognizer +{ + if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) { + if (self.isScanning) { + [self hideScanningViews]; + return; + } + [self showSharingActivityWithCompletion:nil]; + } +} + +- (void)fingerprintViewTapped:(UIGestureRecognizer *)gestureRecognizer +{ + if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) { + if (self.isScanning) { + [self hideScanningViews]; + return; + } + [self showSharingActivityWithCompletion:nil]; + } +} + +- (void)mainViewTapped:(UIGestureRecognizer *)gestureRecognizer +{ + if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) { + if (self.isScanning) { + [self hideScanningViews]; + } + } +} + #pragma mark - Logging + (NSString *)tag diff --git a/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m b/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m index c0eb88322..21a1761dc 100644 --- a/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m +++ b/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m @@ -222,11 +222,12 @@ NS_ASSUME_NONNULL_BEGIN if (!strongSelf) { return; } - FingerprintViewController *fingerprintViewController = [[UIStoryboard main] - instantiateViewControllerWithIdentifier:@"FingerprintViewController"]; + FingerprintViewController *fingerprintViewController = [FingerprintViewController new]; [fingerprintViewController configureWithRecipientId:strongSelf.thread.contactIdentifier]; fingerprintViewController.dismissDelegate = strongSelf; - [strongSelf presentViewController:fingerprintViewController animated:YES completion:nil]; + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:fingerprintViewController]; + [strongSelf presentViewController:navigationController animated:YES completion:nil]; }]]; } diff --git a/Signal/src/ViewControllers/OWSQRCodeScanningViewController.m b/Signal/src/ViewControllers/OWSQRCodeScanningViewController.m index 56b4b0ec5..a6ab916a6 100644 --- a/Signal/src/ViewControllers/OWSQRCodeScanningViewController.m +++ b/Signal/src/ViewControllers/OWSQRCodeScanningViewController.m @@ -1,9 +1,9 @@ -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "OWSQRCodeScanningViewController.h" #import "UIColor+OWS.h" -//#import - @interface OWSQRCodeScanningViewController () @@ -12,9 +12,10 @@ @property UIView *maskingView; @property CALayer *maskingLayer; - @end +#pragma mark - + @implementation OWSQRCodeScanningViewController - (void)dealloc diff --git a/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift b/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift index ab3d8fe38..73f19184a 100644 --- a/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift +++ b/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift @@ -82,11 +82,10 @@ class SafetyNumberConfirmationAlert: NSObject { } public func presentSafetyNumberViewController(theirIdentityKey: Data, theirRecipientId: String, theirDisplayName: String, completion: (() -> Void)? = nil) { - let fingerprintViewController = UIStoryboard.instantiateFingerprintViewController() - + let fingerprintViewController = FingerprintViewController() fingerprintViewController.configure(recipientId: theirRecipientId) - - UIApplication.shared.frontmostViewController?.present(fingerprintViewController, animated: true, completion: completion) + let navigationController = UINavigationController(rootViewController:fingerprintViewController) + UIApplication.shared.frontmostViewController?.present(navigationController, animated: true, completion: completion) } private func untrustedIdentityForSending(recipientIds: [String]) -> OWSRecipientIdentity? { diff --git a/Signal/src/views/HighlightableLabel.swift b/Signal/src/views/HighlightableLabel.swift deleted file mode 100644 index f7ac2088e..000000000 --- a/Signal/src/views/HighlightableLabel.swift +++ /dev/null @@ -1,65 +0,0 @@ -// Created by Michael Kirk on 10/12/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. - -import UIKit - -@objc(OWSHighlightableLabelDelegate) -protocol HighlightableLabelDelegate { - func didHighlightLabel(_ label:HighlightableLabel, completion: (()->())?) -> () -} - -@objc(OWSHighlightableLabel) -class HighlightableLabel : UILabel { - - deinit { - NotificationCenter.default.removeObserver(self); - } - - var delegate: HighlightableLabelDelegate? - - override init(frame: CGRect) { - super.init(frame: frame) - setupGestureRecognizer() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupGestureRecognizer() - } - - @IBInspectable var borderColor: UIColor? { - didSet { - layer.borderColor = borderColor?.cgColor - } - } - - override func drawText(in rect: CGRect) { - let insets = UIEdgeInsets.init(top: 5, left: 5, bottom: 5, right: 5) - super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) - } - - func hideBorder() { - layer.borderWidth = 0 - } - - func showBorder() { - layer.borderWidth = 1.0 - } - - func setupGestureRecognizer() { - isUserInteractionEnabled = true - let longpressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(highlightGesture)) - addGestureRecognizer(longpressGestureRecognizer) - NotificationCenter.default.addObserver(self, selector: #selector(hideBorder), name:NSNotification.Name.UIMenuControllerWillHideMenu, object: nil) - } - - func highlightGesture(gestureRecognizer: UILongPressGestureRecognizer) { - guard gestureRecognizer.state == .began else { - return - } - - becomeFirstResponder(); - showBorder() - self.delegate?.didHighlightLabel(self, completion:{ self.hideBorder() }) - } -} diff --git a/Signal/src/views/OWSBezierPathView.h b/Signal/src/views/OWSBezierPathView.h index b9174d819..eff539241 100644 --- a/Signal/src/views/OWSBezierPathView.h +++ b/Signal/src/views/OWSBezierPathView.h @@ -4,6 +4,8 @@ typedef void (^ConfigureShapeLayerBlock)(CAShapeLayer *layer, CGRect bounds); +NS_ASSUME_NONNULL_BEGIN + @interface OWSBezierPathView : UIView // Configure the view with this method if it uses a single Bezier path. @@ -20,3 +22,5 @@ typedef void (^ConfigureShapeLayerBlock)(CAShapeLayer *layer, CGRect bounds); - (void)updateLayers; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSBezierPathView.m b/Signal/src/views/OWSBezierPathView.m index ca60c0ca9..505c048c3 100644 --- a/Signal/src/views/OWSBezierPathView.m +++ b/Signal/src/views/OWSBezierPathView.m @@ -4,12 +4,16 @@ #import "OWSBezierPathView.h" +NS_ASSUME_NONNULL_BEGIN + @interface OWSBezierPathView () @property (nonatomic) NSArray *configureShapeLayerBlocks; @end +#pragma mark - + @implementation OWSBezierPathView - (id)init @@ -109,3 +113,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSSystemMessageCell.h b/Signal/src/views/OWSSystemMessageCell.h index 57e564aa9..5ceb0f102 100644 --- a/Signal/src/views/OWSSystemMessageCell.h +++ b/Signal/src/views/OWSSystemMessageCell.h @@ -5,6 +5,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @class TSInteraction; @protocol OWSSystemMessageCellDelegate @@ -26,3 +28,5 @@ + (CGSize)cellSizeForInteraction:(TSInteraction *)interaction collectionViewWidth:(CGFloat)collectionViewWidth; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSSystemMessageCell.m b/Signal/src/views/OWSSystemMessageCell.m index 0304412f0..fef054035 100644 --- a/Signal/src/views/OWSSystemMessageCell.m +++ b/Signal/src/views/OWSSystemMessageCell.m @@ -16,6 +16,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @interface OWSSystemMessageCell () @property (nonatomic, nullable) TSInteraction *interaction; @@ -280,3 +282,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSUnreadIndicatorCell.h b/Signal/src/views/OWSUnreadIndicatorCell.h index ba085330f..7b57af0dc 100644 --- a/Signal/src/views/OWSUnreadIndicatorCell.h +++ b/Signal/src/views/OWSUnreadIndicatorCell.h @@ -5,6 +5,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @class TSUnreadIndicatorInteraction; @interface OWSUnreadIndicatorCell : JSQMessagesCollectionViewCell @@ -17,3 +19,5 @@ collectionViewWidth:(CGFloat)collectionViewWidth; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSUnreadIndicatorCell.m b/Signal/src/views/OWSUnreadIndicatorCell.m index 4662c0c2d..5d76dcb52 100644 --- a/Signal/src/views/OWSUnreadIndicatorCell.m +++ b/Signal/src/views/OWSUnreadIndicatorCell.m @@ -10,6 +10,8 @@ #import "UIView+OWS.h" #import +NS_ASSUME_NONNULL_BEGIN + @interface OWSUnreadIndicatorCell () @property (nonatomic, nullable) TSUnreadIndicatorInteraction *interaction; @@ -246,3 +248,5 @@ } @end + +NS_ASSUME_NONNULL_END