Merge branch 'charlesmchen/fingerprintView2'

pull/1/head
Matthew Chen 7 years ago
commit 2dbf305a7d

@ -80,6 +80,7 @@
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; };
34DFCB851E8E04B500053165 /* AddToBlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */; };
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
34E8BF381EE9E2FD00F5F4CA /* FingerprintViewScanController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E8BF371EE9E2FD00F5F4CA /* FingerprintViewScanController.m */; };
34F3089C1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */; };
34F3089F1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */; };
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; };
@ -491,6 +492,8 @@
34DFCB831E8E04B400053165 /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = "<group>"; };
34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = "<group>"; };
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioProgressView.swift; sourceTree = "<group>"; };
34E8BF361EE9E2FD00F5F4CA /* FingerprintViewScanController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FingerprintViewScanController.h; sourceTree = "<group>"; };
34E8BF371EE9E2FD00F5F4CA /* FingerprintViewScanController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FingerprintViewScanController.m; sourceTree = "<group>"; };
34F3089A1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUnreadIndicatorInteraction.h; sourceTree = "<group>"; };
34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUnreadIndicatorInteraction.m; sourceTree = "<group>"; };
34F3089D1ECA580B00BB7697 /* OWSUnreadIndicatorCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUnreadIndicatorCell.h; sourceTree = "<group>"; };
@ -927,6 +930,8 @@
34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */,
34B3F8451E8DF1700035BE1A /* FingerprintViewController.h */,
34B3F8461E8DF1700035BE1A /* FingerprintViewController.m */,
34E8BF361EE9E2FD00F5F4CA /* FingerprintViewScanController.h */,
34E8BF371EE9E2FD00F5F4CA /* FingerprintViewScanController.m */,
34B3F8471E8DF1700035BE1A /* FullImageViewController.h */,
34B3F8481E8DF1700035BE1A /* FullImageViewController.m */,
34D5CCA71EAE3D30005515DB /* GroupViewHelper.h */,
@ -2209,6 +2214,7 @@
45666F7E1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m in Sources */,
4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */,
34B3F8941E8DF1710035BE1A /* SignalsViewController.m in Sources */,
34E8BF381EE9E2FD00F5F4CA /* FingerprintViewScanController.m in Sources */,
76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */,
34330A611E788EA900DF2FB9 /* AttachmentUploadView.m in Sources */,
34B3F87D1E8DF1700035BE1A /* FullImageViewController.m in Sources */,

@ -2535,7 +2535,7 @@ typedef enum : NSUInteger {
- (void)createScrollDownButton
{
const CGFloat kScrollDownButtonSize = round(ScaleFromIPhone5To7Plus(35.f, 40.f));
const CGFloat kScrollDownButtonSize = ScaleFromIPhone5To7Plus(35.f, 40.f);
UIButton *scrollDownButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.scrollDownButton = scrollDownButton;
scrollDownButton.backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.f];
@ -2729,8 +2729,7 @@ typedef enum : NSUInteger {
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:picker animated:YES completion:[UIUtil modalCompletionBlock]];
});
}
alertActionHandler:nil];
}];
}
- (void)chooseFromLibrary
{

@ -2,21 +2,12 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSQRCodeScanningViewController.h"
NS_ASSUME_NONNULL_BEGIN
@class OWSFingerprint;
@class OWSConversationSettingsTableViewController;
@interface FingerprintViewController : UIViewController <OWSQRScannerDelegate>
@property (nullable) OWSConversationSettingsTableViewController *dismissDelegate;
@interface FingerprintViewController : UIViewController
- (void)configureWithRecipientId:(NSString *)recipientId NS_SWIFT_NAME(configure(recipientId:));
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithData:(NSData *)data;
@end
NS_ASSUME_NONNULL_END

@ -4,12 +4,14 @@
#import "FingerprintViewController.h"
#import "Environment.h"
#import "FingerprintViewScanController.h"
#import "OWSBezierPathView.h"
#import "OWSConversationSettingsTableViewController.h"
#import "OWSQRCodeScanningViewController.h"
#import "OWSContactsManager.h"
#import "Signal-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIUtil.h"
#import "UIViewController+CameraPermissions.h"
#import "UIView+OWS.h"
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSError.h>
#import <SignalServiceKit/OWSFingerprint.h>
@ -33,8 +35,34 @@ typedef void (^CustomLayoutBlock)();
@implementation CustomLayoutView
- (instancetype)init
{
if (self = [super init]) {
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.layoutBlock();
}
@ -44,18 +72,12 @@ typedef void (^CustomLayoutBlock)();
@interface FingerprintViewController () <OWSCompareSafetyNumbersActivityDelegate>
@property (nonatomic) NSString *recipientId;
@property (nonatomic) TSStorageManager *storageManager;
@property (nonatomic) OWSFingerprint *fingerprint;
@property (nonatomic) NSString *contactName;
@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController;
@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
@ -65,6 +87,8 @@ typedef void (^CustomLayoutBlock)();
{
OWSAssert(recipientId.length > 0);
self.recipientId = recipientId;
self.storageManager = [TSStorageManager sharedManager];
OWSContactsManager *contactsManager = [Environment getCurrent].contactsManager;
@ -104,59 +128,12 @@ typedef void (^CustomLayoutBlock)();
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];
[mainView autoPinToTopLayoutGuideOfViewController:self withInset:0];
[mainView autoPinToBottomLayoutGuideOfViewController:self withInset:0];
// Scan Button
UIView *scanButton = [UIView new];
@ -240,6 +217,7 @@ typedef void (^CustomLayoutBlock)();
UIImageView *fingerprintImageView = [UIImageView new];
fingerprintImageView.image = self.fingerprint.image;
// Don't antialias QR Codes.
fingerprintImageView.layer.magnificationFilter = kCAFilterNearest;
fingerprintImageView.layer.minificationFilter = kCAFilterNearest;
[fingerprintView addSubview:fingerprintImageView];
@ -249,65 +227,6 @@ typedef void (^CustomLayoutBlock)();
fingerprintImageView.frame = CGRectMake(
round((fingerprintView.width - size) * 0.5f), round((fingerprintView.height - size) * 0.5f), size, size);
};
[self updateLayoutForIsScanning:NO animated:NO];
}
- (void)showScanningViews
{
[self updateLayoutForIsScanning:YES animated:YES];
}
- (void)hideScanningViews
{
[self updateLayoutForIsScanning:NO animated:YES];
}
- (void)updateLayoutForIsScanning:(BOOL)isScanning animated:(BOOL)animated
{
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
@ -316,15 +235,7 @@ typedef void (^CustomLayoutBlock)();
[UIUtil applySignalAppearence];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
if (self.dismissDelegate) {
[self.dismissDelegate presentedModalWasDismissed];
}
}
#pragma mark - HilightableLableDelegate
#pragma mark -
- (void)showSharingActivityWithCompletion:(nullable void (^)(void))completionHandler
{
@ -372,52 +283,6 @@ typedef void (^CustomLayoutBlock)();
[self showVerificationFailedWithError:error];
}
#pragma mark - Action
- (void)closeButton
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didTapShareButton
{
[self showSharingActivityWithCompletion:nil];
}
- (void)showScanner
{
[self ows_askForCameraPermissions:^{
// 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.
DDLogInfo(@"%@ Showing Scanner", self.tag);
[self showScanningViews];
[self.qrScanningController startCapture];
}
alertActionHandler:nil];
}
#pragma mark - OWSQRScannerDelegate
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithData:(NSData *)data
{
[self verifyCombinedFingerprintData:data];
}
- (void)verifyCombinedFingerprintData:(NSData *)combinedFingerprintData
{
NSError *error;
if ([self.fingerprint matchesLogicalFingerprintsData:combinedFingerprintData error:&error]) {
[self showVerificationSucceeded];
} else {
[self showVerificationFailedWithError:error];
}
}
- (void)showVerificationSucceeded
{
DDLogInfo(@"%@ Successfully verified privacy.", self.tag);
@ -430,10 +295,11 @@ typedef void (^CustomLayoutBlock)();
[UIAlertController alertControllerWithTitle:successTitle
message:successDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *dismissAction =
[UIAlertAction actionWithTitle:dismissText style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
[self dismissViewControllerAnimated:true completion:nil];
}];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:dismissText
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self dismissViewControllerAnimated:true completion:nil];
}];
[successAlertController addAction:dismissAction];
[self presentViewController:successAlertController animated:YES completion:nil];
@ -453,28 +319,31 @@ typedef void (^CustomLayoutBlock)();
NSString *dismissText = NSLocalizedString(@"DISMISS_BUTTON_TEXT", nil);
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:dismissText style:UIAlertActionStyleCancel handler: ^(UIAlertAction *action){
// Restore previous layout
[self hideScanningViews];
}];
[failureAlertController addAction:dismissAction];
// TODO
// NSString retryText = NSLocalizedString(@"RETRY_BUTTON_TEXT", nil);
// UIAlertAction *retryAction = [UIAlertAction actionWithTitle:retryText style:UIAlertActionStyleDefault
// handler:^(UIAlertAction * _Nonnull action) {
//
// }];
// [failureAlertController addAction:retryAction];
[self presentViewController:failureAlertController animated:YES completion:nil];
DDLogWarn(@"%@ Identity verification failed with error: %@", self.tag, error);
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(nullable void (^)(void))completion
#pragma mark - Action
- (void)closeButton
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didTapShareButton
{
[self showSharingActivityWithCompletion:nil];
}
- (void)showScanner
{
[self updateLayoutForIsScanning:NO animated:NO];
[super dismissViewControllerAnimated:flag completion:completion];
FingerprintViewScanController *scanView = [FingerprintViewScanController new];
[scanView configureWithRecipientId:self.recipientId];
[self.navigationController pushViewController:scanView animated:YES];
}
- (void)scanButtonTapped:(UIGestureRecognizer *)gestureRecognizer
@ -487,10 +356,6 @@ typedef void (^CustomLayoutBlock)();
- (void)fingerprintLabelTapped:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) {
if (self.isScanning) {
[self hideScanningViews];
return;
}
[self showSharingActivityWithCompletion:nil];
}
}
@ -498,23 +363,10 @@ typedef void (^CustomLayoutBlock)();
- (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

@ -0,0 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface FingerprintViewScanController : UIViewController
- (void)configureWithRecipientId:(NSString *)recipientId NS_SWIFT_NAME(configure(recipientId:));
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,219 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "FingerprintViewScanController.h"
#import "Environment.h"
#import "OWSContactsManager.h"
#import "OWSQRCodeScanningViewController.h"
#import "Signal-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIUtil.h"
#import "UIView+OWS.h"
#import "UIViewController+CameraPermissions.h"
#import <SignalServiceKit/OWSError.h>
#import <SignalServiceKit/OWSFingerprint.h>
#import <SignalServiceKit/OWSFingerprintBuilder.h>
NS_ASSUME_NONNULL_BEGIN
@interface FingerprintViewScanController () <OWSQRScannerDelegate>
@property (nonatomic) TSStorageManager *storageManager;
@property (nonatomic) OWSFingerprint *fingerprint;
@property (nonatomic) NSString *contactName;
@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController;
@end
@implementation FingerprintViewScanController
- (void)configureWithRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
self.storageManager = [TSStorageManager sharedManager];
OWSContactsManager *contactsManager = [Environment getCurrent].contactsManager;
self.contactName = [contactsManager displayNameForPhoneIdentifier:recipientId];
OWSRecipientIdentity *_Nullable recipientIdentity =
[[OWSIdentityManager sharedManager] recipientIdentityForRecipientId:recipientId];
OWSAssert(recipientIdentity);
OWSFingerprintBuilder *builder =
[[OWSFingerprintBuilder alloc] initWithStorageManager:self.storageManager contactsManager:contactsManager];
self.fingerprint =
[builder fingerprintWithTheirSignalId:recipientId theirIdentityKey:recipientIdentity.identityKey];
}
- (void)loadView
{
[super loadView];
self.title = NSLocalizedString(@"SCAN_QR_CODE_VIEW_TITLE", @"Title for the 'scan QR code' view.");
[self createViews];
}
- (void)createViews
{
UIColor *darkGrey = [UIColor colorWithRGBHex:0x404040];
self.view.backgroundColor = [UIColor blackColor];
self.qrScanningController = [OWSQRCodeScanningViewController new];
self.qrScanningController.scanDelegate = self;
[self.view addSubview:self.qrScanningController.view];
[self.qrScanningController.view autoPinWidthToSuperview];
[self.qrScanningController.view autoPinToTopLayoutGuideOfViewController:self withInset:0];
UIView *footer = [UIView new];
footer.backgroundColor = darkGrey;
[self.view addSubview:footer];
[footer autoPinWidthToSuperview];
[footer autoPinToBottomLayoutGuideOfViewController:self withInset:0];
[footer autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.qrScanningController.view];
UILabel *cameraInstructionLabel = [UILabel new];
cameraInstructionLabel.text
= NSLocalizedString(@"SCAN_CODE_INSTRUCTIONS", @"label presented once scanning (camera) view is visible.");
cameraInstructionLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 18.f)];
cameraInstructionLabel.textColor = [UIColor whiteColor];
cameraInstructionLabel.textAlignment = NSTextAlignmentCenter;
cameraInstructionLabel.numberOfLines = 0;
cameraInstructionLabel.lineBreakMode = NSLineBreakByWordWrapping;
[footer addSubview:cameraInstructionLabel];
[cameraInstructionLabel autoPinWidthToSuperviewWithMargin:ScaleFromIPhone5To7Plus(16.f, 30.f)];
CGFloat instructionsVMargin = ScaleFromIPhone5To7Plus(10.f, 20.f);
[cameraInstructionLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:instructionsVMargin];
[cameraInstructionLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:instructionsVMargin];
}
- (void)viewWillAppear:(BOOL)animated
{
// In case we're returning from activity view that needed default system styles.
[UIUtil applySignalAppearence];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
}
#pragma mark - Action
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self ows_askForCameraPermissions:^{
// 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.
DDLogInfo(@"%@ Showing Scanner", self.tag);
[self.qrScanningController startCapture];
}
failureCallback:^{
[self.navigationController popViewControllerAnimated:YES];
}];
}
#pragma mark - OWSQRScannerDelegate
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithData:(NSData *)data
{
[self verifyCombinedFingerprintData:data];
}
- (void)verifyCombinedFingerprintData:(NSData *)combinedFingerprintData
{
NSError *error;
if ([self.fingerprint matchesLogicalFingerprintsData:combinedFingerprintData error:&error]) {
[self showVerificationSucceeded];
} else {
[self showVerificationFailedWithError:error];
}
}
- (void)showVerificationSucceeded
{
DDLogInfo(@"%@ Successfully verified privacy.", self.tag);
NSString *successTitle = NSLocalizedString(@"SUCCESSFUL_VERIFICATION_TITLE", nil);
NSString *dismissText = NSLocalizedString(@"DISMISS_BUTTON_TEXT", nil);
NSString *descriptionFormat = NSLocalizedString(
@"SUCCESSFUL_VERIFICATION_DESCRIPTION", @"Alert body after verifying privacy with {{other user's name}}");
NSString *successDescription = [NSString stringWithFormat:descriptionFormat, self.contactName];
UIAlertController *successAlertController =
[UIAlertController alertControllerWithTitle:successTitle
message:successDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:dismissText
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self dismissViewControllerAnimated:true completion:nil];
}];
[successAlertController addAction:dismissAction];
[self presentViewController:successAlertController animated:YES completion:nil];
}
- (void)showVerificationFailedWithError:(NSError *)error
{
NSString *_Nullable failureTitle;
if (error.code != OWSErrorCodeUserError) {
failureTitle = NSLocalizedString(@"FAILED_VERIFICATION_TITLE", @"alert title");
} // else no title. We don't want to show a big scary "VERIFICATION FAILED" when it's just user error.
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:failureTitle
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
[alertController
addAction:[UIAlertAction
actionWithTitle:NSLocalizedString(@"RETRY_BUTTON_TEXT",
@"Generic text for button that retries whatever the last action was.")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self.qrScanningController startCapture];
}]];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
[self.navigationController popViewControllerAnimated:YES];
}];
[alertController addAction:dismissAction];
[self presentViewController:alertController animated:YES completion:nil];
DDLogWarn(@"%@ Identity verification failed with error: %@", self.tag, error);
}
- (void)dismissViewControllerAnimated:(BOOL)animated completion:(nullable void (^)(void))completion
{
self.qrScanningController.view.hidden = YES;
[super dismissViewControllerAnimated:animated completion:completion];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -15,7 +15,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak) id<OWSConversationSettingsViewDelegate> conversationSettingsViewDelegate;
- (void)configureWithThread:(TSThread *)thread;
- (void)presentedModalWasDismissed;
@end

@ -224,7 +224,6 @@ NS_ASSUME_NONNULL_BEGIN
}
FingerprintViewController *fingerprintViewController = [FingerprintViewController new];
[fingerprintViewController configureWithRecipientId:strongSelf.thread.contactIdentifier];
fingerprintViewController.dismissDelegate = strongSelf;
UINavigationController *navigationController =
[[UINavigationController alloc] initWithRootViewController:fingerprintViewController];
[strongSelf presentViewController:navigationController animated:YES completion:nil];
@ -718,12 +717,6 @@ NS_ASSUME_NONNULL_BEGIN
[self.navigationController popViewControllerAnimated:YES];
}
- (void)presentedModalWasDismissed
{
// Else row stays selected after dismissing modal.
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}
- (void)disappearingMessagesSwitchValueDidChange:(UISwitch *)sender
{
UISwitch *disappearingMessagesSwitch = (UISwitch *)sender;

@ -254,16 +254,13 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionAddDevice)
{
[self ows_askForCameraPermissions:^{
[self performSegueWithIdentifier:@"LinkDeviceSegue" sender:self];
}
alertActionHandler:^{
// HACK to unselect rows when swiping back
// http://stackoverflow.com/questions/19379510/uitableviewcell-doesnt-get-deselected-when-swiping-back-quickly
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}];
}];
}
}

@ -1,4 +1,6 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
@ -15,10 +17,13 @@
@end
#pragma mark -
@interface OWSQRCodeScanningViewController
: UIViewController <AVCaptureMetadataOutputObjectsDelegate, ZXCaptureDelegate>
@property (nonatomic, weak) UIViewController<OWSQRScannerDelegate> *scanDelegate;
- (void)startCapture;
@end

@ -3,14 +3,15 @@
//
#import "OWSQRCodeScanningViewController.h"
#import "OWSBezierPathView.h"
#import "UIColor+OWS.h"
#import "UIView+OWS.h"
@interface OWSQRCodeScanningViewController ()
@property (atomic) ZXCapture *capture;
@property (nonatomic) BOOL captureEnabled;
@property (atomic, strong) ZXCapture *capture;
@property UIView *maskingView;
@property CALayer *maskingLayer;
@property (nonatomic) UIView *maskingView;
@end
@ -47,11 +48,33 @@
return self;
}
- (void)viewDidLoad
- (void)loadView
{
[super viewDidLoad];
self.maskingView = [[UIView alloc] initWithFrame:self.view.frame];
[self.view addSubview:self.maskingView];
[super loadView];
OWSBezierPathView *maskingView = [OWSBezierPathView new];
self.maskingView = maskingView;
[maskingView setConfigureShapeLayerBlock:^(CAShapeLayer *layer, CGRect bounds) {
// Add a circular mask
UIBezierPath *path = [UIBezierPath bezierPathWithRect:bounds];
CGFloat margin = ScaleFromIPhone5To7Plus(8.f, 16.f);
CGFloat radius = MIN(bounds.size.width, bounds.size.height) * 0.5f - margin;
// Center the circle's bounding rectangle
CGRect circleRect = CGRectMake(
bounds.size.width * 0.5f - radius, bounds.size.height * 0.5f - radius, radius * 2.f, radius * 2.f);
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:radius];
[path appendPath:circlePath];
[path setUsesEvenOddFillRule:YES];
layer.path = path.CGPath;
layer.fillRule = kCAFillRuleEvenOdd;
layer.fillColor = [UIColor grayColor].CGColor;
layer.opacity = 0.5f;
}];
[self.view addSubview:maskingView];
[maskingView autoPinWidthToSuperview];
[maskingView autoPinHeightToSuperview];
}
- (void)viewWillAppear:(BOOL)animated
@ -63,28 +86,12 @@
}
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self layoutMaskingView];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self stopCapture];
}
- (void)layoutMaskingView
{
self.maskingView.frame = self.view.frame;
if (self.maskingLayer) {
[self.maskingLayer removeFromSuperlayer];
}
self.maskingLayer = [self buildCircularMaskingLayer];
[self.maskingView.layer addSublayer:self.maskingLayer];
}
- (void)startCapture
{
self.captureEnabled = YES;
@ -93,8 +100,9 @@
self.capture = [[ZXCapture alloc] init];
self.capture.camera = self.capture.back;
self.capture.focusMode = AVCaptureFocusModeContinuousAutoFocus;
self.capture.layer.frame = self.view.frame;
self.capture.layer.frame = self.view.bounds;
self.capture.delegate = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self.view.layer addSublayer:self.capture.layer];
[self.view bringSubviewToFront:self.maskingView];
@ -114,39 +122,13 @@
});
}
- (CAShapeLayer *)buildCircularMaskingLayer
{
// Add a circular mask
UIBezierPath *path = [UIBezierPath
bezierPathWithRoundedRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)
cornerRadius:0];
CGFloat verticalMargin = 8.0;
CGFloat radius = self.view.frame.size.height / 2.0f - verticalMargin;
// Center the circle's bounding rectangle
CGFloat horizontalMargin = (self.view.frame.size.width - 2.0f * radius) / 2.0f;
UIBezierPath *circlePath = [UIBezierPath
bezierPathWithRoundedRect:CGRectMake(horizontalMargin, verticalMargin, 2.0f * radius, 2.0f * radius)
cornerRadius:radius];
[path appendPath:circlePath];
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor grayColor].CGColor;
fillLayer.opacity = 0.5;
return fillLayer;
}
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result
{
if (!self.captureEnabled)
if (!self.captureEnabled) {
return;
}
[self stopCapture];
// TODO bounding rectangle
// Vibrate
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

@ -1,17 +1,15 @@
//
// UIViewController+CameraPermissions.h
// Signal
//
// Created by Jarosław Pawlak on 18.10.2016.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (CameraPermissions)
- (void)ows_askForCameraPermissions:(void (^)())permissionsGrantedCallback;
- (void)ows_askForCameraPermissions:(void (^)())permissionsGrantedCallback
alertActionHandler:(nullable void (^)())alertActionHandler;
failureCallback:(nullable void (^)())failureCallback;
@end
NS_ASSUME_NONNULL_END

@ -12,12 +12,25 @@ NS_ASSUME_NONNULL_BEGIN
@implementation UIViewController (CameraPermissions)
- (void)ows_askForCameraPermissions:(void (^)())permissionsGrantedCallback
alertActionHandler:(nullable void (^)())alertActionHandler
{
[self ows_askForCameraPermissions:permissionsGrantedCallback failureCallback:nil];
}
- (void)ows_askForCameraPermissions:(void (^)())permissionsGrantedCallback
failureCallback:(nullable void (^)())failureCallback
{
// Avoid nil tests below.
if (!failureCallback) {
failureCallback = ^{
};
}
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
DDLogError(@"Camera ImagePicker source not available");
failureCallback();
return;
}
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status == AVAuthorizationStatusDenied) {
UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"MISSING_CAMERA_PERMISSION_TITLE", @"Alert title")
@ -27,30 +40,34 @@ NS_ASSUME_NONNULL_BEGIN
NSString *settingsTitle = NSLocalizedString(@"OPEN_SETTINGS_BUTTON", @"Button text which opens the settings app");
UIAlertAction *openSettingsAction = [UIAlertAction actionWithTitle:settingsTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] openSystemSettings];
if (alertActionHandler) {
alertActionHandler();
}
failureCallback();
}];
[alert addAction:openSettingsAction];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"DISMISS_BUTTON_TEXT", nil)
style:UIAlertActionStyleCancel
handler:alertActionHandler];
handler:^(UIAlertAction *action) {
failureCallback();
}];
[alert addAction:dismissAction];
[self presentViewController:alert animated:YES completion:nil];
} else if (status == AVAuthorizationStatusAuthorized) {
permissionsGrantedCallback();
} else if (status == AVAuthorizationStatusNotDetermined) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
permissionsGrantedCallback();
});
}
}];
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
if (granted) {
permissionsGrantedCallback();
} else {
failureCallback();
}
});
}];
} else {
DDLogError(@"Unknown AVAuthorizationStatus: %ld", (long)status);
failureCallback();
}
}

@ -1090,6 +1090,9 @@
/* label presented once scanning (camera) view is visible. */
"SCAN_CODE_INSTRUCTIONS" = "Scan the QR Code on your contact's device.";
/* Title for the 'scan QR code' view. */
"SCAN_QR_CODE_VIEW_TITLE" = "Scan QR Code";
/* No comment provided by engineer. */
"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT" = "Search by name or number";

Loading…
Cancel
Save