diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 61b979666c..73fe8057a7 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -13,6 +13,7 @@ #import "NotificationsManager.h" #import "OWSAnyTouchGestureRecognizer.h" #import "OWSAudioAttachmentPlayer.h" +#import "OWSBezierPathView.h" #import "OWSCallNotificationsAdaptee.h" #import "OWSContactAvatarBuilder.h" #import "OWSContactsManager.h" diff --git a/Signal/src/UserInterface/Strings.swift b/Signal/src/UserInterface/Strings.swift index 6c77c44111..2d683d53e9 100644 --- a/Signal/src/UserInterface/Strings.swift +++ b/Signal/src/UserInterface/Strings.swift @@ -10,6 +10,8 @@ import Foundation @objc class CommonStrings: NSObject { static let dismissButton = NSLocalizedString("DISMISS_BUTTON_TEXT", comment: "Short text to dismiss current modal / actionsheet / screen") + static let cancelButton = NSLocalizedString("TXT_CANCEL_TITLE", comment:"Label for the cancel button in an alert or action sheet.") + } @objc class CallStrings: NSObject { diff --git a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift index 45441c887a..0ba61d6eea 100644 --- a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift +++ b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift @@ -355,8 +355,7 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla buttonSpacer.autoSetDimension(.width, toSize:buttonHSpacing) buttonSpacer.autoHCenterInSuperview() - let cancelButton = createButton(title: NSLocalizedString("TXT_CANCEL_TITLE", - comment: ""), + let cancelButton = createButton(title: CommonStrings.cancelButton, color : UIColor.ows_destructiveRed(), action: #selector(cancelPressed)) buttonRow.addSubview(cancelButton) diff --git a/Signal/src/ViewControllers/AvatarViewHelper.m b/Signal/src/ViewControllers/AvatarViewHelper.m index f5821427bc..6ec5879d02 100644 --- a/Signal/src/ViewControllers/AvatarViewHelper.m +++ b/Signal/src/ViewControllers/AvatarViewHelper.m @@ -132,10 +132,8 @@ NS_ASSUME_NONNULL_BEGIN successCompletion:^(UIImage *_Nonnull dstImage) { [self.delegate avatarDidChange:dstImage]; }]; - OWSNavigationController *navigationController = - [[OWSNavigationController alloc] initWithRootViewController:vc]; [self.delegate.fromViewController - presentViewController:navigationController + presentViewController:vc animated:YES completion:[UIUtil modalCompletionBlock]]; } diff --git a/Signal/src/ViewControllers/BlockListUIUtils.m b/Signal/src/ViewControllers/BlockListUIUtils.m index a4a70a37b4..88e9c15741 100644 --- a/Signal/src/ViewControllers/BlockListUIUtils.m +++ b/Signal/src/ViewControllers/BlockListUIUtils.m @@ -5,6 +5,7 @@ #import "BlockListUIUtils.h" #import "OWSContactsManager.h" #import "PhoneNumber.h" +#import "Signal-Swift.h" #import #import #import @@ -104,7 +105,7 @@ typedef void (^BlockAlertCompletionBlock)(); }]; [actionSheetController addAction:unblockAction]; - UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action) { if (completionBlock) { @@ -208,7 +209,7 @@ typedef void (^BlockAlertCompletionBlock)(); }]; [actionSheetController addAction:unblockAction]; - UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action) { if (completionBlock) { diff --git a/Signal/src/ViewControllers/ContactsPicker.swift b/Signal/src/ViewControllers/ContactsPicker.swift index 28b0e485bc..29278653b2 100644 --- a/Signal/src/ViewControllers/ContactsPicker.swift +++ b/Signal/src/ViewControllers/ContactsPicker.swift @@ -150,7 +150,7 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa let alert = UIAlertController(title: title, message: body, preferredStyle: UIAlertControllerStyle.alert) - let dismissText = NSLocalizedString("TXT_CANCEL_TITLE", comment:"") + let dismissText = CommonStrings.cancelButton let cancelAction = UIAlertAction(title: dismissText, style: .cancel, handler: { _ in let error = NSError(domain: "contactsPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"]) diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index a2fe54eb4d..cfdc92ba5e 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -421,7 +421,7 @@ NS_ASSUME_NONNULL_BEGIN contactViewController.allowsActions = NO; contactViewController.allowsEditing = YES; contactViewController.navigationItem.leftBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil) + [[UIBarButtonItem alloc] initWithTitle:CommonStrings.cancelButton style:UIBarButtonItemStylePlain target:fromViewController action:@selector(didFinishEditingContact)]; diff --git a/Signal/src/ViewControllers/CropScaleImageViewController.swift b/Signal/src/ViewControllers/CropScaleImageViewController.swift index 93ae34bb13..09c0d01668 100644 --- a/Signal/src/ViewControllers/CropScaleImageViewController.swift +++ b/Signal/src/ViewControllers/CropScaleImageViewController.swift @@ -66,8 +66,6 @@ class CropScaleImageViewController: OWSViewController { // We use a CALayer to render the image for performance reasons. var imageLayer: CALayer! - var dashedBorderLayer: CAShapeLayer! - // In width/height. // // TODO: We could make this a parameter. @@ -98,6 +96,9 @@ class CropScaleImageViewController: OWSViewController { // corner of the crop region in src image point coordinates. var srcTranslation: CGPoint = CGPoint.zero + // space between the cropping circle and the outside edge of the view + let maskMargin = CGFloat(20) + // MARK: Initializers @available(*, unavailable, message:"use srcImage:successCompletion: constructor instead.") @@ -174,29 +175,18 @@ class CropScaleImageViewController: OWSViewController { view.backgroundColor = UIColor.white - self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.stop, - target:self, - action:#selector(cancelPressed)) - self.navigationItem.title = NSLocalizedString("CROP_SCALE_IMAGE_VIEW_TITLE", - comment: "Title for the 'crop/scale image' dialog.") - createViews() } // MARK: - Create Views private func createViews() { - let previewTopMargin: CGFloat = 30 - let previewHMargin: CGFloat = 20 let contentView = UIView() + contentView.backgroundColor = UIColor.black self.view.addSubview(contentView) - contentView.autoPinWidthToSuperview(withMargin:previewHMargin) - contentView.autoPin(toTopLayoutGuideOf: self, withInset:previewTopMargin) + contentView.autoPinEdgesToSuperviewEdges() - createButtonRow(contentView:contentView) - - let imageHMargin: CGFloat = 0 let imageView = OWSLayerView(frame:CGRect.zero, layoutCallback: {[weak self] _ in guard let strongSelf = self else { return } strongSelf.updateImageLayout() @@ -204,28 +194,63 @@ class CropScaleImageViewController: OWSViewController { imageView.clipsToBounds = true self.imageView = imageView contentView.addSubview(imageView) - imageView.autoPinWidthToSuperview(withMargin:imageHMargin) - imageView.autoVCenterInSuperview() - imageView.autoPinToSquareAspectRatio() + imageView.autoPinEdgesToSuperviewEdges() let imageLayer = CALayer() self.imageLayer = imageLayer imageLayer.contents = srcImage.cgImage imageView.layer.addSublayer(imageLayer) - let dashedBorderLayer = CAShapeLayer() - self.dashedBorderLayer = dashedBorderLayer - dashedBorderLayer.strokeColor = UIColor.ows_materialBlue().cgColor - dashedBorderLayer.lineDashPattern = [10, 10] - dashedBorderLayer.lineWidth = 4 - dashedBorderLayer.fillColor = nil - imageView.layer.addSublayer(dashedBorderLayer) + let maskingView = OWSBezierPathView() + contentView.addSubview(maskingView) + + maskingView.configureShapeLayerBlock = { [weak self] layer, bounds in + guard let strongSelf = self else { + return + } + let path = UIBezierPath(rect: bounds) + + let circleRect = strongSelf.cropFrame(forBounds:bounds) + let radius = circleRect.size.width * 0.5 + let circlePath = UIBezierPath(roundedRect: circleRect, cornerRadius: radius) + + path.append(circlePath) + path.usesEvenOddFillRule = true + + layer.path = path.cgPath + layer.fillRule = kCAFillRuleEvenOdd + layer.fillColor = UIColor.black.cgColor + layer.opacity = 0.7 + } + maskingView.autoPinEdgesToSuperviewEdges() + + let titleLabel = UILabel() + titleLabel.textColor = UIColor.white + titleLabel.textAlignment = .center + titleLabel.font = UIFont.ows_mediumFont(withSize:ScaleFromIPhone5(16)) + titleLabel.text = NSLocalizedString("CROP_SCALE_IMAGE_VIEW_TITLE", + comment: "Title for the 'crop/scale image' dialog.") + contentView.addSubview(titleLabel) + titleLabel.autoPinWidthToSuperview() + let titleLabelMargin = ScaleFromIPhone5(16) + titleLabel.autoPin(toTopLayoutGuideOf:self, withInset:titleLabelMargin) + + createButtonRow(contentView: contentView) contentView.isUserInteractionEnabled = true contentView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:)))) contentView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(sender:)))) } + // Given the current bounds for the image view, return the frame of the + // crop region within that view. + private func cropFrame(forBounds bounds: CGRect) -> CGRect { + let radius = min(bounds.size.width, bounds.size.height) * 0.5 - self.maskMargin + // Center the circle's bounding rectangle + let circleRect = CGRect(x: bounds.size.width * 0.5 - radius, y: bounds.size.height * 0.5 - radius, width: radius * 2, height: radius * 2) + return circleRect + } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -281,11 +306,14 @@ class CropScaleImageViewController: OWSViewController { return } - let viewSizePoints = imageView.frame.size + // The size of the image view (should be full screen). + let imageViewSizePoints = imageView.frame.size guard - (viewSizePoints.width > 0 && viewSizePoints.height > 0) else { + (imageViewSizePoints.width > 0 && imageViewSizePoints.height > 0) else { return } + // The frame of the crop circle within the image view. + let cropFrame = self.cropFrame(forBounds:CGRect(origin:CGPoint.zero, size: imageViewSizePoints)) // Normalize the scaling property. imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale)) @@ -294,38 +322,39 @@ class CropScaleImageViewController: OWSViewController { height:srcDefaultCropSizePoints.height / imageScale) let minSrcTranslationPoints = CGPoint.zero + + // Prevent panning outside of image area. let maxSrcTranslationPoints = CGPoint(x:srcImageSizePoints.width - srcCropSizePoints.width, y:srcImageSizePoints.height - srcCropSizePoints.height ) - // Normalize the translation property. + // Normalize the translation property srcTranslation = CGPoint(x: max(minSrcTranslationPoints.x, min(maxSrcTranslationPoints.x, srcTranslation.x)), y: max(minSrcTranslationPoints.y, min(maxSrcTranslationPoints.y, srcTranslation.y))) - let imageViewFrame = imageRenderRect(forDstSize:viewSizePoints) + // The frame of the image layer in crop frame coordinates. + let rawImageLayerFrame = imageRenderRect(forDstSize: cropFrame.size) + // The frame of the image layer in image view coordinates. + let imageLayerFrame = CGRect(x: rawImageLayerFrame.origin.x + cropFrame.origin.x, + y: rawImageLayerFrame.origin.y + cropFrame.origin.y, + width: rawImageLayerFrame.size.width, + height: rawImageLayerFrame.size.height) - // Disable implicit animations. + // Disable implicit animations for snappier panning/zooming. CATransaction.begin() CATransaction.setDisableActions(true) - imageLayer.frame = imageViewFrame - // Mask to circle. - let maskLayer = CAShapeLayer() - maskLayer.frame = imageViewFrame - maskLayer.fillRule = kCAFillRuleEvenOdd - let maskFrame = CGRect(origin:CGPoint(x:-imageViewFrame.origin.x * 2, - y: -imageViewFrame.origin.y * 2), - size:imageView.bounds.size) - maskLayer.path = - CGPath(ellipseIn: maskFrame, transform: nil) - imageLayer.mask = maskLayer - - dashedBorderLayer.frame = imageView.bounds - dashedBorderLayer.path = UIBezierPath(rect: imageView.bounds).cgPath + imageLayer.frame = imageLayerFrame CATransaction.commit() } + // Give the size of a given view or image context into which we + // will render the source image, return the frame (in that + // view/context's coordinate system) to render the source image. + // + // Gathering this logic in a single function ensures that the + // output will be WYSIWYG with the view state. private func imageRenderRect(forDstSize dstSize: CGSize) -> CGRect { let srcCropSizePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale, @@ -442,19 +471,24 @@ class CropScaleImageViewController: OWSViewController { buttonRow.autoPinEdge(toSuperviewEdge:.bottom, withInset:buttonBottomMargin) buttonRow.autoPinEdge(.top, to:.bottom, of:contentView, withOffset:buttonTopMargin) + let cancelButton = createButton(title: CommonStrings.cancelButton, + action: #selector(cancelPressed)) + buttonRow.addSubview(cancelButton) + cancelButton.autoPinEdge(toSuperviewEdge:.top) + cancelButton.autoPinEdge(toSuperviewEdge:.bottom) + cancelButton.autoPinEdge(toSuperviewEdge: .left) + let doneButton = createButton(title: NSLocalizedString("BUTTON_DONE", comment: "Label for generic done button."), - color : UIColor.ows_materialBlue(), action: #selector(donePressed)) buttonRow.addSubview(doneButton) doneButton.autoPinEdge(toSuperviewEdge:.top) doneButton.autoPinEdge(toSuperviewEdge:.bottom) - doneButton.autoHCenterInSuperview() + doneButton.autoPinEdge(toSuperviewEdge: .right) } - private func createButton(title: String, color: UIColor, action: Selector) -> UIButton { + private func createButton(title: String, action: Selector) -> UIButton { let buttonFont = UIFont.ows_mediumFont(withSize:ScaleFromIPhone5To7Plus(18, 22)) - let buttonCornerRadius = ScaleFromIPhone5To7Plus(4, 5) let buttonWidth = ScaleFromIPhone5To7Plus(110, 140) let buttonHeight = ScaleFromIPhone5To7Plus(35, 45) @@ -462,9 +496,6 @@ class CropScaleImageViewController: OWSViewController { button.setTitle(title, for:.normal) button.setTitleColor(UIColor.white, for:.normal) button.titleLabel!.font = buttonFont - button.backgroundColor = color - button.layer.cornerRadius = buttonCornerRadius - button.clipsToBounds = true button.addTarget(self, action:action, for:.touchUpInside) button.autoSetDimension(.width, toSize:buttonWidth) button.autoSetDimension(.height, toSize:buttonHeight) @@ -497,8 +528,8 @@ class CropScaleImageViewController: OWSViewController { let context = UIGraphicsGetCurrentContext() context!.interpolationQuality = .high - let imageViewFrame = imageRenderRect(forDstSize:dstSizePixels) - srcImage.draw(in:imageViewFrame) + let imageViewFrame = imageRenderRect(forDstSize: dstSizePixels) + srcImage.draw(in: imageViewFrame) let scaledImage = UIGraphicsGetImageFromCurrentImageContext() if scaledImage == nil { diff --git a/Signal/src/ViewControllers/OWSLinkDeviceViewController.m b/Signal/src/ViewControllers/OWSLinkDeviceViewController.m index bf56ec8a13..04eb52320e 100644 --- a/Signal/src/ViewControllers/OWSLinkDeviceViewController.m +++ b/Signal/src/ViewControllers/OWSLinkDeviceViewController.m @@ -7,6 +7,7 @@ #import "OWSDeviceProvisioningURLParser.h" #import "OWSLinkedDevicesTableViewController.h" #import "OWSProfileManager.h" +#import "Signal-Swift.h" #import #import #import @@ -80,15 +81,15 @@ NS_ASSUME_NONNULL_BEGIN UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert]; - + UIAlertAction *cancelAction = - [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil) - style:UIAlertActionStyleCancel - handler:^(UIAlertAction *action) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.navigationController popViewControllerAnimated:YES]; - }); - }]; + [UIAlertAction actionWithTitle:CommonStrings.cancelButton + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.navigationController popViewControllerAnimated:YES]; + }); + }]; [alertController addAction:cancelAction]; UIAlertAction *proceedAction = @@ -111,7 +112,7 @@ NS_ASSUME_NONNULL_BEGIN preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAction = - [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil) + [UIAlertAction actionWithTitle:CommonStrings.cancelButton style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -183,7 +184,7 @@ NS_ASSUME_NONNULL_BEGIN [alertController addAction:retryAction]; UIAlertAction *cancelAction = - [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil) + [UIAlertAction actionWithTitle:CommonStrings.cancelButton style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Signal/src/ViewControllers/SelectRecipientViewController.m b/Signal/src/ViewControllers/SelectRecipientViewController.m index 0f43a59375..70ce7d7a8d 100644 --- a/Signal/src/ViewControllers/SelectRecipientViewController.m +++ b/Signal/src/ViewControllers/SelectRecipientViewController.m @@ -319,7 +319,7 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien message:NSLocalizedString(@"ALERT_VALIDATE_RECIPIENT_MESSAGE", @"A message for the alert shown while validating a signal account") preferredStyle:UIAlertControllerStyleAlert]; - [activityAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") + [activityAlert addAction:[UIAlertAction actionWithTitle:CommonStrings.cancelButton style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action) { wasCancelled = YES; diff --git a/Signal/src/util/AppUpdateNag.m b/Signal/src/util/AppUpdateNag.m index cf33e81e63..d6150a6410 100644 --- a/Signal/src/util/AppUpdateNag.m +++ b/Signal/src/util/AppUpdateNag.m @@ -85,7 +85,7 @@ NSString *const TSStorageManagerAppUpgradeNagDate = @"TSStorageManagerAppUpgrade @"version number.}}.")]; [updater setAlertUpdateButtonTitle:NSLocalizedString(@"APP_UPDATE_NAG_ALERT_UPDATE_BUTTON", @"Label for the 'update' button in the 'new app version available' alert.")]; - [updater setAlertCancelButtonTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")]; + [updater setAlertCancelButtonTitle:CommonStrings.cancelButton]; [updater setDelegate:self]; [updater showUpdateWithConfirmation]; } diff --git a/Signal/src/views/OWSAlerts.swift b/Signal/src/views/OWSAlerts.swift index 1b1c3fb0df..df4d1fcaa3 100644 --- a/Signal/src/views/OWSAlerts.swift +++ b/Signal/src/views/OWSAlerts.swift @@ -41,7 +41,7 @@ import Foundation } public class func cancelAction() -> UIAlertAction { - let action = UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment:"Label for the cancel button in an alert or action sheet."), style: .cancel) { _ in + let action = UIAlertAction(title: CommonStrings.cancelButton, style: .cancel) { _ in Logger.debug("Cancel item") // Do nothing. } diff --git a/Signal/src/views/OWSBezierPathView.h b/Signal/src/views/OWSBezierPathView.h index f7417968fe..6cb0201a8c 100644 --- a/Signal/src/views/OWSBezierPathView.h +++ b/Signal/src/views/OWSBezierPathView.h @@ -9,12 +9,12 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSBezierPathView : UIView // Configure the view with this method if it uses a single Bezier path. -- (void)setConfigureShapeLayerBlock:(ConfigureShapeLayerBlock)configureShapeLayerBlock; +@property (nonatomic) ConfigureShapeLayerBlock configureShapeLayerBlock; // Configure the view with this method if it uses multiple Bezier paths. // // Paths will be rendered in back-to-front order. -- (void)setConfigureShapeLayerBlocks:(NSArray *)configureShapeLayerBlocks; +@property (nonatomic) NSArray *configureShapeLayerBlocks; // This method forces the view to reconstruct its layer content. It shouldn't // be necessary to call this unless the ConfigureShapeLayerBlocks depend on external diff --git a/Signal/src/views/OWSBezierPathView.m b/Signal/src/views/OWSBezierPathView.m index 7e4fe75dd7..0155737069 100644 --- a/Signal/src/views/OWSBezierPathView.m +++ b/Signal/src/views/OWSBezierPathView.m @@ -6,12 +6,6 @@ NS_ASSUME_NONNULL_BEGIN -@interface OWSBezierPathView () - -@property (nonatomic) NSArray *configureShapeLayerBlocks; - -@end - #pragma mark - @implementation OWSBezierPathView diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index e6b7bf8316..fee9e11663 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -389,7 +389,7 @@ "CREATE_NEW_GROUP" = "Create new group"; /* Title for the 'crop/scale image' dialog. */ -"CROP_SCALE_IMAGE_VIEW_TITLE" = "Crop Image"; +"CROP_SCALE_IMAGE_VIEW_TITLE" = "Move and Scale"; /* Subtitle shown while the app is updating its database. */ "DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes.";