diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 61b979666..73fe8057a 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 6c77c4411..2d683d53e 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 45441c887..0ba61d6ee 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 f5821427b..6ec5879d0 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 a4a70a37b..88e9c1574 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 28b0e485b..29278653b 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 a2fe54eb4..cfdc92ba5 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 93ae34bb1..ad5bbca1f 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,22 +194,45 @@ 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 = { layer, bounds in + let path = UIBezierPath(rect: bounds) + + 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) + 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:)))) @@ -294,35 +307,36 @@ 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) + var imageViewFrame = imageRenderRect(forDstSize: viewSizePoints) - // Disable implicit animations. + // offset to vertically center image in view (aspect ratio?) + let srcToViewRatio = viewSizePoints.width / srcCropSizePoints.width / imageScale + let heightOfImageLayer = srcDefaultCropSizePoints.height * srcToViewRatio + let yOffset = imageView.frame.height * 0.5 - heightOfImageLayer * 0.5 + imageViewFrame = imageViewFrame.offsetBy(dx: 0, dy: yOffset) + + // inset frame by the with of the masking margin so the user can pan to the very edge of the image + imageViewFrame = CGRect(x: imageViewFrame.origin.x + self.maskMargin, + y: imageViewFrame.origin.y + self.maskMargin, + width: imageViewFrame.width - maskMargin * 2, + height: imageViewFrame.height - maskMargin * 2) + + // 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 - CATransaction.commit() } @@ -333,10 +347,10 @@ class CropScaleImageViewController: OWSViewController { let srcToViewRatio = dstSize.width / srcCropSizePoints.width - return CGRect(origin: CGPoint(x:srcTranslation.x * -srcToViewRatio, - y:srcTranslation.y * -srcToViewRatio), - size: CGSize(width:srcImageSizePoints.width * +srcToViewRatio, - height:srcImageSizePoints.height * +srcToViewRatio + return CGRect(origin: CGPoint(x: srcTranslation.x * -srcToViewRatio, + y: srcTranslation.y * -srcToViewRatio), + size: CGSize(width:srcImageSizePoints.width * +srcToViewRatio, + height:srcImageSizePoints.height * +srcToViewRatio )) } @@ -442,19 +456,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 +481,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 +513,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 bf56ec8a1..04eb52320 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 0f43a5937..70ce7d7a8 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 cf33e81e6..d6150a641 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 1b1c3fb0d..df4d1fcaa 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 f7417968f..6cb0201a8 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 7e4fe75dd..015573706 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 e6b7bf831..2630d6e6b 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" = "Crop and Scale"; /* Subtitle shown while the app is updating its database. */ "DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes.";