diff --git a/Signal/Images.xcassets/album_add_more.imageset/Contents.json b/Signal/Images.xcassets/album_add_more.imageset/Contents.json index 3b4a85bc9..ae32da7a5 100644 --- a/Signal/Images.xcassets/album_add_more.imageset/Contents.json +++ b/Signal/Images.xcassets/album_add_more.imageset/Contents.json @@ -2,17 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "add-photo-24@1x.png", + "filename" : "create-album-outline-32@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "add-photo-24@2x.png", + "filename" : "create-album-outline-32@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "add-photo-24@3x.png", + "filename" : "create-album-outline-32@3x.png", "scale" : "3x" } ], diff --git a/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@1x.png b/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@1x.png deleted file mode 100644 index 1f653a075..000000000 Binary files a/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@2x.png b/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@2x.png deleted file mode 100644 index 157ece7ef..000000000 Binary files a/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@3x.png b/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@3x.png deleted file mode 100644 index a6080e078..000000000 Binary files a/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@3x.png and /dev/null differ diff --git a/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@1x.png b/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@1x.png new file mode 100644 index 000000000..d23c007d1 Binary files /dev/null and b/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@1x.png differ diff --git a/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@2x.png b/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@2x.png new file mode 100644 index 000000000..9ccabb7c6 Binary files /dev/null and b/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@2x.png differ diff --git a/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@3x.png b/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@3x.png new file mode 100644 index 000000000..b13aa24b3 Binary files /dev/null and b/Signal/Images.xcassets/album_add_more.imageset/create-album-outline-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_caption.imageset/Contents.json b/Signal/Images.xcassets/image_editor_caption.imageset/Contents.json index 22defce50..80c4669a0 100644 --- a/Signal/Images.xcassets/image_editor_caption.imageset/Contents.json +++ b/Signal/Images.xcassets/image_editor_caption.imageset/Contents.json @@ -2,17 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "add-caption-32@1x.png", + "filename" : "caption-24@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "add-caption-32@2x.png", + "filename" : "caption-24@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "add-caption-32@3x.png", + "filename" : "caption-24@3x.png", "scale" : "3x" } ], diff --git a/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@1x.png b/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@1x.png deleted file mode 100644 index 2d1325e01..000000000 Binary files a/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@2x.png b/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@2x.png deleted file mode 100644 index ba403c8b3..000000000 Binary files a/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@3x.png b/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@3x.png deleted file mode 100644 index 4cc8d3839..000000000 Binary files a/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@3x.png and /dev/null differ diff --git a/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@1x.png b/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@1x.png new file mode 100644 index 000000000..5d6512367 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@2x.png b/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@2x.png new file mode 100644 index 000000000..c444e83a9 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@3x.png b/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@3x.png new file mode 100644 index 000000000..740abdfff Binary files /dev/null and b/Signal/Images.xcassets/image_editor_caption.imageset/caption-24@3x.png differ diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 9059a8888..ad2ecdf66 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2836,7 +2836,9 @@ typedef enum : NSUInteger { OWSImagePickerGridController *picker = [OWSImagePickerGridController new]; picker.delegate = self; - pickerModal = [[OWSNavigationController alloc] initWithRootViewController:picker]; + OWSNavigationController *modal = [[OWSNavigationController alloc] initWithRootViewController:picker]; + modal.ows_prefersStatusBarHidden = @(YES); + pickerModal = modal; } else { UIImagePickerController *picker = [OWSImagePickerController new]; picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; diff --git a/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift b/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift index c692952fc..54ac6aa07 100644 --- a/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift +++ b/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift @@ -380,19 +380,32 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat } func complete(withAssets assets: [PHAsset]) { - let attachmentPromises: [Promise] = assets.map({ - return photoCollectionContents.outgoingAttachment(for: $0) - }) - - firstly { - when(fulfilled: attachmentPromises) - }.map { attachments in - Logger.debug("built all attachments") - self.didComplete(withAttachments: attachments) - }.catch { error in - Logger.error("failed to prepare attachments. error: \(error)") - OWSAlerts.showAlert(title: NSLocalizedString("IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS", comment: "alert title")) - }.retainUntilComplete() + + ModalActivityIndicatorViewController.present(fromViewController: self, + canCancel: false) { (modal) in + let attachmentPromises: [Promise] = assets.map({ + return self.photoCollectionContents.outgoingAttachment(for: $0) + }) + + firstly { + when(fulfilled: attachmentPromises) + }.map { attachments in + Logger.debug("built all attachments") + + DispatchQueue.main.async { + modal.dismiss(completion: { + self.didComplete(withAttachments: attachments) + }) + } + }.catch { error in + Logger.error("failed to prepare attachments. error: \(error)") + DispatchQueue.main.async { + modal.dismiss(completion: { + OWSAlerts.showAlert(title: NSLocalizedString("IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS", comment: "alert title")) + }) + } + }.retainUntilComplete() + } } private func didComplete(withAttachments attachments: [SignalAttachment]) { diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 10fc300cb..40e681eda 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -169,6 +169,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC let vc = AttachmentApprovalViewController(mode: .modal, attachments: attachments) vc.approvalDelegate = approvalDelegate let navController = OWSNavigationController(rootViewController: vc) + navController.ows_prefersStatusBarHidden = true guard let navigationBar = navController.navigationBar as? OWSNavigationBar else { owsFailDebug("navigationBar was nil or unexpected class") @@ -198,6 +199,10 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC // MARK: - View Lifecycle + public override var prefersStatusBarHidden: Bool { + return true + } + override public func viewDidLoad() { super.viewDidLoad() @@ -229,8 +234,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC Logger.debug("") super.viewWillAppear(animated) - CurrentAppContext().setStatusBarHidden(true, animated: animated) - guard let navigationBar = navigationController?.navigationBar as? OWSNavigationBar else { owsFailDebug("navigationBar was nil or unexpected class") return @@ -238,6 +241,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC navigationBar.overrideTheme(type: .clear) updateNavigationBar() + updateControlVisibility() } override public func viewDidAppear(_ animated: Bool) { @@ -246,15 +250,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC super.viewDidAppear(animated) updateNavigationBar() + updateControlVisibility() } override public func viewWillDisappear(_ animated: Bool) { Logger.debug("") super.viewWillDisappear(animated) - - // Since this VC is being dismissed, the "show status bar" animation would feel like - // it's occuring on the presenting view controller - it's better not to animate at all. - CurrentAppContext().setStatusBarHidden(false, animated: false) } override public var inputAccessoryView: UIView? { @@ -263,12 +264,18 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } override public var canBecomeFirstResponder: Bool { - return true + return !shouldHideControls } // MARK: - Navigation Bar public func updateNavigationBar() { + guard !shouldHideControls else { + self.navigationItem.leftBarButtonItem = nil + self.navigationItem.rightBarButtonItem = nil + return + } + var navigationBarItems = [UIView]() var isShowingCaptionView = false @@ -295,7 +302,29 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC cancelButton.tintColor = .white self.navigationItem.leftBarButtonItem = cancelButton } else { - self.navigationItem.leftBarButtonItem = nil + // Note: using a custom leftBarButtonItem breaks the interactive pop gesture. + self.navigationItem.leftBarButtonItem = self.createOWSBackButton() + } + } + + // MARK: - Control Visibility + + public var shouldHideControls: Bool { + guard let pageViewController = pageViewControllers.first else { + return false + } + return pageViewController.shouldHideControls + } + + private func updateControlVisibility() { + if shouldHideControls { + if isFirstResponder { + resignFirstResponder() + } + } else { + if !isFirstResponder { + becomeFirstResponder() + } } } @@ -376,6 +405,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } updateNavigationBar() + updateControlVisibility() } // MARK: - UIPageViewControllerDataSource @@ -622,7 +652,11 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate } func prepViewControllerUpdateNavigationBar() { - self.updateNavigationBar() + updateNavigationBar() + } + + func prepViewControllerUpdateControls() { + updateControlVisibility() } func prepViewControllerAttachmentCount() -> Int { @@ -682,6 +716,8 @@ protocol AttachmentPrepViewControllerDelegate: class { func prepViewControllerUpdateNavigationBar() + func prepViewControllerUpdateControls() + func prepViewControllerAttachmentCount() -> Int } @@ -712,7 +748,15 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD fileprivate var isShowingCaptionView = false { didSet { prepDelegate?.prepViewControllerUpdateNavigationBar() + prepDelegate?.prepViewControllerUpdateControls() + } + } + + public var shouldHideControls: Bool { + guard let imageEditorView = imageEditorView else { + return false } + return imageEditorView.shouldHideControls } // MARK: - Initializers @@ -794,8 +838,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD view.addSubview(imageEditorView) imageEditorView.autoPinEdgesToSuperviewEdges() - imageEditorView.addControls(to: imageEditorView, - viewController: self) + imageEditorUpdateNavigationBar() } } #endif @@ -872,6 +915,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD super.viewWillAppear(animated) prepDelegate?.prepViewControllerUpdateNavigationBar() + prepDelegate?.prepViewControllerUpdateControls() } override public func viewDidAppear(_ animated: Bool) { @@ -880,6 +924,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD super.viewDidAppear(animated) prepDelegate?.prepViewControllerUpdateNavigationBar() + prepDelegate?.prepViewControllerUpdateControls() } override public func viewWillLayoutSubviews() { @@ -1208,6 +1253,10 @@ extension AttachmentPrepViewController: ImageEditorViewDelegate { public func imageEditorUpdateNavigationBar() { prepDelegate?.prepViewControllerUpdateNavigationBar() } + + public func imageEditorUpdateControls() { + prepDelegate?.prepViewControllerUpdateControls() + } } // MARK: - @@ -1411,7 +1460,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate { // Add shadow in case overlayed on white content lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor - lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) + lengthLimitLabel.layer.shadowOffset = .zero lengthLimitLabel.layer.shadowOpacity = 0.8 lengthLimitLabel.isHidden = true @@ -1645,6 +1694,7 @@ public class ApprovalRailCellView: GalleryRailCellView { imageView.layer.shadowColor = UIColor.black.cgColor imageView.layer.shadowRadius = 2 imageView.layer.shadowOpacity = 0.66 + imageView.layer.shadowOffset = .zero return imageView }() @@ -1676,8 +1726,8 @@ public class ApprovalRailCellView: GalleryRailCellView { if hasCaption { addSubview(captionIndicator) - captionIndicator.autoPinEdge(toSuperviewEdge: .top, withInset: 0) - captionIndicator.autoPinEdge(toSuperviewEdge: .leading, withInset: 4) + captionIndicator.autoPinEdge(toSuperviewEdge: .top, withInset: 2) + captionIndicator.autoPinEdge(toSuperviewEdge: .leading, withInset: 6) } else { captionIndicator.removeFromSuperview() } diff --git a/SignalMessaging/ViewControllers/AttachmentCaptionViewController.swift b/SignalMessaging/ViewControllers/AttachmentCaptionViewController.swift index c54a8e31c..e33fe9363 100644 --- a/SignalMessaging/ViewControllers/AttachmentCaptionViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentCaptionViewController.swift @@ -75,11 +75,6 @@ class AttachmentCaptionViewController: OWSViewController { configureTextView() - let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, - target: self, - action: #selector(didTapCancel)) - cancelButton.tintColor = .white - navigationItem.leftBarButtonItem = cancelButton let doneIcon = UIImage(named: "image_editor_checkmark_full")?.withRenderingMode(.alwaysTemplate) let doneButton = UIBarButtonItem(image: doneIcon, style: .plain, target: self, @@ -96,7 +91,6 @@ class AttachmentCaptionViewController: OWSViewController { stackView.axis = .vertical stackView.spacing = 20 stackView.alignment = .fill - stackView.addBackgroundView(withBackgroundColor: UIColor(white: 0, alpha: 0.5)) stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) stackView.isLayoutMarginsRelativeArrangement = true self.view.addSubview(stackView) @@ -104,6 +98,15 @@ class AttachmentCaptionViewController: OWSViewController { stackView.autoPinEdge(toSuperviewEdge: .trailing) self.autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true) + let backgroundView = UIView() + backgroundView.backgroundColor = UIColor(white: 0, alpha: 0.5) + view.addSubview(backgroundView) + view.sendSubview(toBack: backgroundView) + backgroundView.autoPinEdge(toSuperviewEdge: .leading) + backgroundView.autoPinEdge(toSuperviewEdge: .trailing) + backgroundView.autoPinEdge(toSuperviewEdge: .bottom) + backgroundView.autoPinEdge(.top, to: .top, of: stackView) + let minTextHeight: CGFloat = textView.font?.lineHeight ?? 0 textViewHeightConstraint = textView.autoSetDimension(.height, toSize: minTextHeight) @@ -174,7 +177,7 @@ class AttachmentCaptionViewController: OWSViewController { // Add shadow in case overlayed on white content lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor - lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) + lengthLimitLabel.layer.shadowOffset = .zero lengthLimitLabel.layer.shadowOpacity = 0.8 lengthLimitLabel.isHidden = true diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.h b/SignalMessaging/ViewControllers/OWSNavigationController.h index 533a5b4d0..3cc656c8a 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.h +++ b/SignalMessaging/ViewControllers/OWSNavigationController.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import @@ -23,6 +23,11 @@ NS_ASSUME_NONNULL_BEGIN // unsaved changes. @interface OWSNavigationController : UINavigationController +// If set, this property lets us override prefersStatusBarHidden behavior. +// This is useful for supressing the status bar while a modal is presented, +// regardless of which view is currently visible. +@property (nonatomic, nullable) NSNumber *ows_prefersStatusBarHidden; + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.m b/SignalMessaging/ViewControllers/OWSNavigationController.m index 64fa3604b..b2e40a3a5 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.m +++ b/SignalMessaging/ViewControllers/OWSNavigationController.m @@ -69,6 +69,14 @@ NS_ASSUME_NONNULL_BEGIN self.interactivePopGestureRecognizer.delegate = self; } +- (BOOL)prefersStatusBarHidden +{ + if (self.ows_prefersStatusBarHidden) { + return self.ows_prefersStatusBarHidden.boolValue; + } + return [super prefersStatusBarHidden]; +} + #pragma mark - UINavigationBarDelegate - (void)setupNavbar diff --git a/SignalMessaging/Views/GalleryRailView.swift b/SignalMessaging/Views/GalleryRailView.swift index 2e52f9e06..66690ab2a 100644 --- a/SignalMessaging/Views/GalleryRailView.swift +++ b/SignalMessaging/Views/GalleryRailView.swift @@ -64,14 +64,17 @@ public class GalleryRailCellView: UIView { private(set) var isSelected: Bool = false func setIsSelected(_ isSelected: Bool) { + let borderWidth: CGFloat = 2 self.isSelected = isSelected + + // Reserve space for the selection border whether or not the cell is selected. + layoutMargins = UIEdgeInsets(top: 0, left: borderWidth, bottom: 0, right: borderWidth) + if isSelected { - layoutMargins = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 2) imageView.layer.borderColor = Theme.galleryHighlightColor.cgColor - imageView.layer.borderWidth = 2 - imageView.layer.cornerRadius = 2 + imageView.layer.borderWidth = borderWidth + imageView.layer.cornerRadius = borderWidth } else { - layoutMargins = .zero imageView.layer.borderWidth = 0 imageView.layer.cornerRadius = 0 } diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorBrushViewController.swift b/SignalMessaging/Views/ImageEditor/ImageEditorBrushViewController.swift index 63a7590a7..af56c8db1 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorBrushViewController.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorBrushViewController.swift @@ -84,11 +84,18 @@ public class ImageEditorBrushViewController: OWSViewController { self.view.layoutSubviews() } - public func updateNavigationBar() { + private func updateNavigationBar() { + // Hide controls during stroke. + let hasStroke = currentStroke != nil + guard !hasStroke else { + updateNavigationBar(navigationBarItems: []) + return + } + let undoButton = navigationBarButton(imageName: "image_editor_undo", selector: #selector(didTapUndo(sender:))) let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full", - selector: #selector(didTapDone(sender:))) + selector: #selector(didTapDone(sender:))) // Prevent users from undo any changes made before entering the view. let canUndo = model.canUndo() && firstUndoOperationId != model.currentUndoOperationId() @@ -101,6 +108,12 @@ public class ImageEditorBrushViewController: OWSViewController { updateNavigationBar(navigationBarItems: navigationBarItems) } + private func updateControls() { + // Hide controls during stroke. + let hasStroke = currentStroke != nil + paletteView.isHidden = hasStroke + } + // MARK: - Actions @objc func didTapUndo(sender: UIButton) { @@ -129,7 +142,12 @@ public class ImageEditorBrushViewController: OWSViewController { // MARK: - Brush // These properties are non-empty while drawing a stroke. - private var currentStroke: ImageEditorStrokeItem? + private var currentStroke: ImageEditorStrokeItem? { + didSet { + updateControls() + updateNavigationBar() + } + } private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]() @objc diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift index e3e2b0b0d..2afb844b6 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift @@ -23,11 +23,13 @@ class ImageEditorCropViewController: OWSViewController { private var transform: ImageEditorTransform - public let contentView = OWSLayerView() - public let clipView = OWSLayerView() - private var imageLayer = CALayer() + public let croppedContentView = OWSLayerView() + public let uncroppedContentView = UIView() + + private var croppedImageLayer = CALayer() + private var uncroppedImageLayer = CALayer() private enum CropRegion { // The sides of the crop region. @@ -121,19 +123,32 @@ class ImageEditorCropViewController: OWSViewController { } wrapperView.addSubview(clipView) - imageLayer.contents = previewImage.cgImage - imageLayer.contentsScale = previewImage.scale - contentView.backgroundColor = .clear - contentView.isOpaque = false - contentView.layer.addSublayer(imageLayer) - contentView.layoutCallback = { [weak self] (_) in + croppedImageLayer.contents = previewImage.cgImage + croppedImageLayer.contentsScale = previewImage.scale + croppedContentView.backgroundColor = .clear + croppedContentView.isOpaque = false + croppedContentView.layer.addSublayer(croppedImageLayer) + croppedContentView.layoutCallback = { [weak self] (_) in guard let strongSelf = self else { return } strongSelf.updateContent() } - clipView.addSubview(contentView) - contentView.autoPinEdgesToSuperviewEdges() + clipView.addSubview(croppedContentView) + croppedContentView.autoPinEdgesToSuperviewEdges() + + uncroppedImageLayer.contents = previewImage.cgImage + uncroppedImageLayer.contentsScale = previewImage.scale + // The "uncropped" view/layer are used to display the + // content that has been cropped out. Its content + // should be semi-transparent to distinguish it from + // the content within the crop bounds. + uncroppedImageLayer.opacity = 0.5 + uncroppedContentView.backgroundColor = .clear + uncroppedContentView.isOpaque = false + uncroppedContentView.layer.addSublayer(uncroppedImageLayer) + wrapperView.addSubview(uncroppedContentView) + uncroppedContentView.autoPin(toEdgesOf: croppedContentView) // MARK: - Footer @@ -329,7 +344,7 @@ class ImageEditorCropViewController: OWSViewController { Logger.verbose("") - let viewSize = contentView.bounds.size + let viewSize = croppedContentView.bounds.size guard viewSize.width > 0, viewSize.height > 0 else { return @@ -354,13 +369,15 @@ class ImageEditorCropViewController: OWSViewController { } private func applyTransform() { - let viewSize = contentView.bounds.size - contentView.layer.setAffineTransform(transform.affineTransform(viewSize: viewSize)) + let viewSize = croppedContentView.bounds.size + croppedContentView.layer.setAffineTransform(transform.affineTransform(viewSize: viewSize)) + uncroppedContentView.layer.setAffineTransform(transform.affineTransform(viewSize: viewSize)) } private func updateImageLayer() { - let viewSize = contentView.bounds.size - ImageEditorCanvasView.updateImageLayer(imageLayer: imageLayer, viewSize: viewSize, imageSize: model.srcImageSizePixels, transform: transform) + let viewSize = croppedContentView.bounds.size + ImageEditorCanvasView.updateImageLayer(imageLayer: croppedImageLayer, viewSize: viewSize, imageSize: model.srcImageSizePixels, transform: transform) + ImageEditorCanvasView.updateImageLayer(imageLayer: uncroppedImageLayer, viewSize: viewSize, imageSize: model.srcImageSizePixels, transform: transform) } private func configureGestures() { @@ -368,14 +385,22 @@ class ImageEditorCropViewController: OWSViewController { let pinchGestureRecognizer = ImageEditorPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:))) pinchGestureRecognizer.referenceView = self.clipView + // Use this VC as a delegate to ensure that pinches only + // receive touches that start inside of the cropped image bounds. pinchGestureRecognizer.delegate = self view.addGestureRecognizer(pinchGestureRecognizer) let panGestureRecognizer = ImageEditorPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) panGestureRecognizer.maximumNumberOfTouches = 1 panGestureRecognizer.referenceView = self.clipView - panGestureRecognizer.delegate = self + // _DO NOT_ use this VC as a delegate to filter touches; + // pan gestures can start outside the cropped image bounds. + // Otherwise the edges of the crop rect are difficult to + // "grab". view.addGestureRecognizer(panGestureRecognizer) + + // De-conflict the gestures; the pan gesture has priority. + panGestureRecognizer.shouldBeRequiredToFail(by: pinchGestureRecognizer) } override public var canBecomeFirstResponder: Bool { diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift index 2e7689604..e1c78ff24 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift @@ -100,7 +100,12 @@ public class ImageEditorPaletteView: UIView { // We use an invisible margin to expand the hot area of this control. let margin: CGFloat = 20 imageView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)) - + imageView.layer.borderColor = UIColor.white.cgColor + imageView.layer.borderWidth = CGHairlineWidth() + imageView.layer.shadowColor = UIColor.black.cgColor + imageView.layer.shadowRadius = 2.0 + imageView.layer.shadowOpacity = 0.33 + imageView.layer.shadowOffset = .zero selectionWrapper.layoutCallback = { [weak self] (view) in guard let strongSelf = self else { return diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift index c0e490931..3acac8e1c 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift @@ -9,6 +9,7 @@ public protocol ImageEditorViewDelegate: class { func imageEditor(presentFullScreenView viewController: UIViewController, isTransparent: Bool) func imageEditorUpdateNavigationBar() + func imageEditorUpdateControls() } // MARK: - @@ -81,16 +82,17 @@ public class ImageEditorView: UIView { return true } - // TODO: Should this method be private? - @objc - public func addControls(to containerView: UIView, - viewController: UIViewController) { + // MARK: - Navigation Bar + + private func updateNavigationBar() { delegate?.imageEditorUpdateNavigationBar() } - // MARK: - Navigation Bar - public func navigationBarItems() -> [UIView] { + guard !shouldHideControls else { + return [] + } + let undoButton = navigationBarButton(imageName: "image_editor_undo", selector: #selector(didTapUndo(sender:))) let brushButton = navigationBarButton(imageName: "image_editor_brush", @@ -110,6 +112,15 @@ public class ImageEditorView: UIView { return buttons } + private func updateControls() { + delegate?.imageEditorUpdateControls() + } + + public var shouldHideControls: Bool { + // Hide controls during "text item move". + return movingTextItem != nil + } + // MARK: - Actions @objc func didTapUndo(sender: UIButton) { @@ -138,6 +149,12 @@ public class ImageEditorView: UIView { @objc func didTapNewText(sender: UIButton) { Logger.verbose("") + createNewTextItem() + } + + private func createNewTextItem() { + Logger.verbose("") + let viewSize = canvasView.gestureReferenceView.bounds.size let imageSize = model.srcImageSizePixels let imageFrame = ImageEditorCanvasView.imageFrame(forViewSize: viewSize, imageSize: imageSize, @@ -178,6 +195,8 @@ public class ImageEditorView: UIView { let location = gestureRecognizer.location(in: canvasView.gestureReferenceView) guard let textLayer = self.textLayer(forLocation: location) else { + // If there is no text item under the "tap", start a new one. + createNewTextItem() return } @@ -263,7 +282,12 @@ public class ImageEditorView: UIView { // MARK: - Editor Gesture // These properties are valid while moving a text item. - private var movingTextItem: ImageEditorTextItem? + private var movingTextItem: ImageEditorTextItem? { + didSet { + updateNavigationBar() + updateControls() + } + } private var movingTextStartUnitCenter: CGPoint? private var movingTextHasMoved = false @@ -479,11 +503,11 @@ extension ImageEditorView: ImageEditorModelObserver { public func imageEditorModelDidChange(before: ImageEditorContents, after: ImageEditorContents) { - delegate?.imageEditorUpdateNavigationBar() + updateNavigationBar() } public func imageEditorModelDidChange(changedItemIds: [String]) { - delegate?.imageEditorUpdateNavigationBar() + updateNavigationBar() } } diff --git a/SignalMessaging/Views/ImageEditor/OWSViewController+ImageEditor.swift b/SignalMessaging/Views/ImageEditor/OWSViewController+ImageEditor.swift index a7f50cf3e..9b233fd3f 100644 --- a/SignalMessaging/Views/ImageEditor/OWSViewController+ImageEditor.swift +++ b/SignalMessaging/Views/ImageEditor/OWSViewController+ImageEditor.swift @@ -15,6 +15,7 @@ public extension NSObject { button.layer.shadowColor = UIColor.black.cgColor button.layer.shadowRadius = 2 button.layer.shadowOpacity = 0.66 + button.layer.shadowOffset = .zero return button } } @@ -29,7 +30,7 @@ public extension UIViewController { return } - let spacing: CGFloat = 8 + let spacing: CGFloat = 16 let stackView = UIStackView(arrangedSubviews: navigationBarItems) stackView.axis = .horizontal stackView.spacing = spacing