diff --git a/Signal/Images.xcassets/image_editor_brush.imageset/Contents.json b/Signal/Images.xcassets/image_editor_brush.imageset/Contents.json new file mode 100644 index 000000000..1a496d37d --- /dev/null +++ b/Signal/Images.xcassets/image_editor_brush.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marker-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "marker-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "marker-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@1x.png b/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@1x.png new file mode 100644 index 000000000..33e6dbc8e Binary files /dev/null and b/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@2x.png b/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@2x.png new file mode 100644 index 000000000..99f1ac6d7 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@3x.png b/Signal/Images.xcassets/image_editor_brush.imageset/marker-32@3x.png new file mode 100644 index 000000000..325edccec Binary files /dev/null and b/Signal/Images.xcassets/image_editor_brush.imageset/marker-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 new file mode 100644 index 000000000..22defce50 --- /dev/null +++ b/Signal/Images.xcassets/image_editor_caption.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "add-caption-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "add-caption-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "add-caption-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..2d1325e01 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@1x.png 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 new file mode 100644 index 000000000..ba403c8b3 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@2x.png 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 new file mode 100644 index 000000000..4cc8d3839 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_caption.imageset/add-caption-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/Contents.json b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/Contents.json new file mode 100644 index 000000000..7ad664647 --- /dev/null +++ b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "checkmark-circle-outline-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "checkmark-circle-outline-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "checkmark-circle-outline-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@1x.png b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@1x.png new file mode 100644 index 000000000..102b6d7e8 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@2x.png b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@2x.png new file mode 100644 index 000000000..19a221ccd Binary files /dev/null and b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@3x.png b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@3x.png new file mode 100644 index 000000000..ac7c7bc6f Binary files /dev/null and b/Signal/Images.xcassets/image_editor_checkmark_empty.imageset/checkmark-circle-outline-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_checkmark_full.imageset/Contents.json b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/Contents.json new file mode 100644 index 000000000..07c0f17ec --- /dev/null +++ b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "checkmark-circle-filled-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "checkmark-circle-filled-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "checkmark-circle-filled-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@1x.png b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@1x.png new file mode 100644 index 000000000..931d9791c Binary files /dev/null and b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@2x.png b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@2x.png new file mode 100644 index 000000000..af113b5e3 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@3x.png b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@3x.png new file mode 100644 index 000000000..76cbcd5d7 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_checkmark_full.imageset/checkmark-circle-filled-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_palette.imageset/Contents.json b/Signal/Images.xcassets/image_editor_crop.imageset/Contents.json similarity index 70% rename from Signal/Images.xcassets/image_editor_palette.imageset/Contents.json rename to Signal/Images.xcassets/image_editor_crop.imageset/Contents.json index bf932ac9b..e80abae62 100644 --- a/Signal/Images.xcassets/image_editor_palette.imageset/Contents.json +++ b/Signal/Images.xcassets/image_editor_crop.imageset/Contents.json @@ -2,15 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "Screen Shot 2019-02-26 at 1.57.23 PM.png", + "filename" : "crop-32@1x.png", "scale" : "1x" }, { "idiom" : "universal", + "filename" : "crop-32@2x.png", "scale" : "2x" }, { "idiom" : "universal", + "filename" : "crop-32@3x.png", "scale" : "3x" } ], diff --git a/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@1x.png b/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@1x.png new file mode 100644 index 000000000..c17df7370 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@2x.png b/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@2x.png new file mode 100644 index 000000000..804fafee5 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@3x.png b/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@3x.png new file mode 100644 index 000000000..c780199d2 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop.imageset/crop-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop_lock.imageset/Contents.json b/Signal/Images.xcassets/image_editor_crop_lock.imageset/Contents.json new file mode 100644 index 000000000..eb76b325a --- /dev/null +++ b/Signal/Images.xcassets/image_editor_crop_lock.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "crop-lock-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "crop-lock-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "crop-lock-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@1x.png b/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@1x.png new file mode 100644 index 000000000..d7bf30c25 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@2x.png b/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@2x.png new file mode 100644 index 000000000..3ddf37b64 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@3x.png b/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@3x.png new file mode 100644 index 000000000..22ff1debc Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop_lock.imageset/crop-lock-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop_unlock.imageset/Contents.json b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/Contents.json new file mode 100644 index 000000000..77ca2423a --- /dev/null +++ b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "crop-unlock-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "crop-unlock-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "crop-unlock-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@1x.png b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@1x.png new file mode 100644 index 000000000..0e61a460c Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@2x.png b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@2x.png new file mode 100644 index 000000000..51892c3dd Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@3x.png b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@3x.png new file mode 100644 index 000000000..0bdca3444 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_crop_unlock.imageset/crop-unlock-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_flip.imageset/Contents.json b/Signal/Images.xcassets/image_editor_flip.imageset/Contents.json new file mode 100644 index 000000000..92ead99d3 --- /dev/null +++ b/Signal/Images.xcassets/image_editor_flip.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "flip-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "flip-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "flip-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@1x.png b/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@1x.png new file mode 100644 index 000000000..7cc8a1a65 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@2x.png b/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@2x.png new file mode 100644 index 000000000..c2b4c0dc1 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@3x.png b/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@3x.png new file mode 100644 index 000000000..9d82e6a1e Binary files /dev/null and b/Signal/Images.xcassets/image_editor_flip.imageset/flip-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_palette.imageset/Screen Shot 2019-02-26 at 1.57.23 PM.png b/Signal/Images.xcassets/image_editor_palette.imageset/Screen Shot 2019-02-26 at 1.57.23 PM.png deleted file mode 100644 index 4b8e2e2bc..000000000 Binary files a/Signal/Images.xcassets/image_editor_palette.imageset/Screen Shot 2019-02-26 at 1.57.23 PM.png and /dev/null differ diff --git a/Signal/Images.xcassets/image_editor_rotate.imageset/Contents.json b/Signal/Images.xcassets/image_editor_rotate.imageset/Contents.json new file mode 100644 index 000000000..3e4720e54 --- /dev/null +++ b/Signal/Images.xcassets/image_editor_rotate.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "rotate-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "rotate-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "rotate-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@1x.png b/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@1x.png new file mode 100644 index 000000000..54587f022 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@2x.png b/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@2x.png new file mode 100644 index 000000000..6db61ae4a Binary files /dev/null and b/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@3x.png b/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@3x.png new file mode 100644 index 000000000..c927acbcf Binary files /dev/null and b/Signal/Images.xcassets/image_editor_rotate.imageset/rotate-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_text.imageset/Contents.json b/Signal/Images.xcassets/image_editor_text.imageset/Contents.json new file mode 100644 index 000000000..c0ff55700 --- /dev/null +++ b/Signal/Images.xcassets/image_editor_text.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "text-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "text-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "text-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_text.imageset/text-32@1x.png b/Signal/Images.xcassets/image_editor_text.imageset/text-32@1x.png new file mode 100644 index 000000000..a28f50dd7 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_text.imageset/text-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_text.imageset/text-32@2x.png b/Signal/Images.xcassets/image_editor_text.imageset/text-32@2x.png new file mode 100644 index 000000000..26ea5757a Binary files /dev/null and b/Signal/Images.xcassets/image_editor_text.imageset/text-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_text.imageset/text-32@3x.png b/Signal/Images.xcassets/image_editor_text.imageset/text-32@3x.png new file mode 100644 index 000000000..ebc8902f1 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_text.imageset/text-32@3x.png differ diff --git a/Signal/Images.xcassets/image_editor_undo.imageset/Contents.json b/Signal/Images.xcassets/image_editor_undo.imageset/Contents.json new file mode 100644 index 000000000..d32e57835 --- /dev/null +++ b/Signal/Images.xcassets/image_editor_undo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "undo-32@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "undo-32@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "undo-32@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@1x.png b/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@1x.png new file mode 100644 index 000000000..c3ec13abd Binary files /dev/null and b/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@1x.png differ diff --git a/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@2x.png b/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@2x.png new file mode 100644 index 000000000..13a841309 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@2x.png differ diff --git a/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@3x.png b/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@3x.png new file mode 100644 index 000000000..25bd1ccf1 Binary files /dev/null and b/Signal/Images.xcassets/image_editor_undo.imageset/undo-32@3x.png differ diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 398c3e2c3..53b149cc9 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -953,7 +953,8 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD autoPinView(toBottomOfViewControllerOrKeyboard: imageEditorView, avoidNotch: true) imageEditorView.autoPinWidthToSuperview() - imageEditorView.addControls(to: imageEditorView) + imageEditorView.addControls(to: imageEditorView, + viewController: self) } } #endif diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift index 51ee588c1..b40b231c8 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift @@ -78,32 +78,40 @@ class ImageEditorCropViewController: OWSViewController { // MARK: - View Lifecycle + private var isCropLocked = false + private var cropLockButton: OWSButton? + override func loadView() { self.view = UIView() self.view.backgroundColor = .black + self.view.layoutMargins = .zero // MARK: - Buttons // TODO: Apply icons. - let doneButton = OWSButton(title: "Done") { [weak self] in + let doneButton = OWSButton(imageName: "image_editor_checkmark_full", + tintColor: UIColor.white) { [weak self] in self?.didTapBackButton() } - let rotate90Button = OWSButton(title: "Rotate 90°") { [weak self] in + let rotate90Button = OWSButton(imageName: "image_editor_rotate", + tintColor: UIColor.white) { [weak self] in self?.rotate90ButtonPressed() } - let rotate45Button = OWSButton(title: "Rotate 45°") { [weak self] in - self?.rotate45ButtonPressed() - } - let resetButton = OWSButton(title: "Reset") { [weak self] in + // TODO: Myles may change this asset. + let resetButton = OWSButton(imageName: "image_editor_undo", + tintColor: UIColor.white) { [weak self] in self?.resetButtonPressed() } - let zoom2xButton = OWSButton(title: "Zoom 2x") { [weak self] in - self?.zoom2xButtonPressed() + let flipButton = OWSButton(imageName: "image_editor_flip", + tintColor: UIColor.white) { [weak self] in + self?.flipButtonPressed() } - let flipButton = OWSButton(title: "Flip") { [weak self] in - self?.flipButtonPressed() + let cropLockButton = OWSButton(imageName: "image_editor_crop_unlock", + tintColor: UIColor.white) { [weak self] in + self?.cropLockButtonPressed() } + self.cropLockButton = cropLockButton // MARK: - Header @@ -152,17 +160,17 @@ class ImageEditorCropViewController: OWSViewController { // MARK: - Footer let footer = UIStackView(arrangedSubviews: [ - flipButton, rotate90Button, - rotate45Button, + flipButton, UIView.hStretchingSpacer(), - zoom2xButton + cropLockButton ]) footer.axis = .horizontal footer.spacing = 16 footer.backgroundColor = .clear footer.isOpaque = false + let imageMargin: CGFloat = 20 let stackView = UIStackView(arrangedSubviews: [ header, wrapperView, @@ -170,8 +178,8 @@ class ImageEditorCropViewController: OWSViewController { ]) stackView.axis = .vertical stackView.alignment = .fill - stackView.spacing = 24 - stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) + stackView.spacing = imageMargin + stackView.layoutMargins = UIEdgeInsets(top: 8, left: imageMargin, bottom: 8, right: imageMargin) stackView.isLayoutMarginsRelativeArrangement = true self.view.addSubview(stackView) stackView.autoPinEdgesToSuperviewEdges() @@ -211,6 +219,16 @@ class ImageEditorCropViewController: OWSViewController { configureGestures() } + private func updateCropLockButton() { + guard let cropLockButton = cropLockButton else { + owsFailDebug("Missing cropLockButton") + return + } + cropLockButton.setImage(imageName: (isCropLocked + ? "image_editor_crop_lock" + : "image_editor_crop_unlock")) + } + private static let desiredCornerSize: CGFloat = 24 private static let minCropSize: CGFloat = desiredCornerSize * 2 private var cornerSize = CGSize.zero @@ -500,6 +518,14 @@ class ImageEditorCropViewController: OWSViewController { let cropRectangleStart = clipView.bounds var cropRectangleNow = cropRectangleStart + // Derive the new crop rectangle. + + // We limit the crop rectangle's minimum size for two reasons. + // + // * To ensure that the crop rectangles "corner handles" + // can always be safely drawn. + // * To avoid awkward interactions when the crop rectangle + // is very small. Users can always crop multiple times. let maxDeltaX = cropRectangleNow.size.width - cornerSize.width * 2 let maxDeltaY = cropRectangleNow.size.height - cornerSize.height * 2 @@ -527,6 +553,44 @@ class ImageEditorCropViewController: OWSViewController { break } + // If crop is locked, update the crop rectangle + // to retain the original aspect ratio. + if (isCropLocked) { + let scaleX = cropRectangleNow.width / cropRectangleStart.width + let scaleY = cropRectangleNow.height / cropRectangleStart.height + var cropRectangleLocked = cropRectangleStart + // Find a new crop rectangle size with the correct aspect + // ratio which is always larger than the "naive" crop rectangle. + // We always expand and never shrink the crop rectangle to + // fix its aspect ratio, to ensure the "max deltas" enforced + // above still are honored. + if scaleX > scaleY { + cropRectangleLocked.size.width = cropRectangleNow.width + cropRectangleLocked.size.height = cropRectangleNow.width * cropRectangleStart.height / cropRectangleStart.width + } else { + cropRectangleLocked.size.height = cropRectangleNow.height + cropRectangleLocked.size.width = cropRectangleNow.height * cropRectangleStart.width / cropRectangleStart.height + } + + // Pin the crop rectangle to the sides that aren't being manipulated. + switch panCropRegion { + case .left, .topLeft, .bottomLeft: + cropRectangleLocked.origin.x = cropRectangleStart.maxX - cropRectangleLocked.width + default: + // Bias towards aligning left. + cropRectangleLocked.origin.x = cropRectangleStart.minX + } + switch panCropRegion { + case .top, .topLeft, .topRight: + cropRectangleLocked.origin.y = cropRectangleStart.maxY - cropRectangleLocked.height + default: + // Bias towards aligning top. + cropRectangleLocked.origin.y = cropRectangleStart.minY + } + + cropRectangleNow = cropRectangleLocked + } + cropView.frame = view.convert(cropRectangleNow, from: clipView) switch gestureRecognizer.state { @@ -683,10 +747,6 @@ class ImageEditorCropViewController: OWSViewController { rotateButtonPressed(angleRadians: CGFloat.pi * 0.5, rotateCanvas: true) } - @objc public func rotate45ButtonPressed() { - rotateButtonPressed(angleRadians: CGFloat.pi * 0.25, rotateCanvas: false) - } - private func rotateButtonPressed(angleRadians: CGFloat, rotateCanvas: Bool) { let outputSizePixels = (rotateCanvas // Invert width and height. @@ -703,18 +763,6 @@ class ImageEditorCropViewController: OWSViewController { isFlipped: transform.isFlipped).normalize(srcImageSizePixels: model.srcImageSizePixels)) } - @objc public func zoom2xButtonPressed() { - let outputSizePixels = transform.outputSizePixels - let unitTranslation = transform.unitTranslation - let rotationRadians = transform.rotationRadians - let scaling = transform.scaling * 2.0 - updateTransform(ImageEditorTransform(outputSizePixels: outputSizePixels, - unitTranslation: unitTranslation, - rotationRadians: rotationRadians, - scaling: scaling, - isFlipped: transform.isFlipped).normalize(srcImageSizePixels: model.srcImageSizePixels)) - } - @objc public func flipButtonPressed() { updateTransform(ImageEditorTransform(outputSizePixels: transform.outputSizePixels, unitTranslation: transform.unitTranslation, @@ -726,6 +774,11 @@ class ImageEditorCropViewController: OWSViewController { @objc public func resetButtonPressed() { updateTransform(ImageEditorTransform.defaultTransform(srcImageSizePixels: model.srcImageSizePixels)) } + + @objc public func cropLockButtonPressed() { + isCropLocked = !isCropLocked + updateCropLockButton() + } } // MARK: - diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift index 73d3d442e..1f35fe06d 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorPaletteView.swift @@ -39,8 +39,10 @@ public class ImageEditorPaletteView: UIView { self.backgroundColor = .clear self.isOpaque = false - if let image = UIImage(named: "image_editor_palette") { + if let image = ImageEditorPaletteView.buildPaletteGradientImage() { imageView.image = image + imageView.layer.cornerRadius = image.size.width * 0.5 + imageView.clipsToBounds = true } else { owsFailDebug("Missing image.") } @@ -48,8 +50,6 @@ public class ImageEditorPaletteView: UIView { // We use an invisible margin to expand the hot area of // this control. let margin: CGFloat = 8 - // TODO: Review sizing when there's an asset. - imageView.autoSetDimensions(to: CGSize(width: 8, height: 200)) imageView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)) selectionWrapper.layoutCallback = { [weak self] (view) in @@ -58,8 +58,8 @@ public class ImageEditorPaletteView: UIView { } strongSelf.updateState() } - imageView.addSubview(selectionWrapper) - selectionWrapper.autoPinEdgesToSuperviewEdges() + self.addSubview(selectionWrapper) + selectionWrapper.autoPin(toEdgesOf: imageView) selectionView.addBorder(with: .white) selectionView.layer.cornerRadius = selectionSize / 2 @@ -95,9 +95,8 @@ public class ImageEditorPaletteView: UIView { private func updateState() { var selectedColor = UIColor.white - if let image = imageView.image, - let cgImage = image.cgImage { - if let imageColor = image.color(atLocation: CGPoint(x: CGFloat(cgImage.width) * 0.5, y: CGFloat(cgImage.height) * selectionAlpha)) { + if let image = imageView.image { + if let imageColor = image.color(atLocation: CGPoint(x: CGFloat(image.size.width) * 0.5, y: CGFloat(image.size.height) * selectionAlpha)) { selectedColor = imageColor } else { owsFailDebug("Couldn't determine image color.") @@ -132,6 +131,32 @@ public class ImageEditorPaletteView: UIView { let location = gesture.location(in: imageView) selectColor(atLocationY: location.y) } + + private static func buildPaletteGradientImage() -> UIImage? { + let gradientSize = CGSize(width: 8, height: 200) + let gradientBounds = CGRect(origin: .zero, size: gradientSize) + let gradientView = UIView() + gradientView.frame = gradientBounds + let gradientLayer = CAGradientLayer() + gradientView.layer.addSublayer(gradientLayer) + gradientLayer.frame = gradientBounds + // See: https://github.com/signalapp/Signal-Android/blob/master/res/values/arrays.xml#L267 + gradientLayer.colors = [ + UIColor(rgbHex: 0xffffff).cgColor, + UIColor(rgbHex: 0xff0000).cgColor, + UIColor(rgbHex: 0xff00ff).cgColor, + UIColor(rgbHex: 0x0000ff).cgColor, + UIColor(rgbHex: 0x00ffff).cgColor, + UIColor(rgbHex: 0x00ff00).cgColor, + UIColor(rgbHex: 0xffff00).cgColor, + UIColor(rgbHex: 0xff5500).cgColor, + UIColor(rgbHex: 0x000000).cgColor + ] + gradientLayer.startPoint = CGPoint.zero + gradientLayer.endPoint = CGPoint(x: 0, y: gradientSize.height) + gradientLayer.endPoint = CGPoint(x: 0, y: 1.0) + return gradientView.renderAsImage(opaque: true, scale: UIScreen.main.scale) + } } // MARK: - @@ -162,6 +187,9 @@ extension UIImage { owsFailDebug("Invalid image size.") return nil } + + Logger.verbose("scale: \(self.scale)") + // Convert the location from points to pixels and clamp to the image bounds. let xPixels: Int = Int(round(locationPoints.x * self.scale)).clamp(0, imageWidth - 1) let yPixels: Int = Int(round(locationPoints.y * self.scale)).clamp(0, imageHeight - 1) diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift index d75b5ddb2..4f6234503 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift @@ -29,12 +29,14 @@ public class ImageEditorView: UIView { // This is the default mode. It is used for interacting with text items. case none case brush + case text } private var editorMode = EditorMode.none { didSet { AssertIsOnMainThread() + updateButtons() updateGestureState() } } @@ -129,46 +131,50 @@ public class ImageEditorView: UIView { } } - private let undoButton = UIButton(type: .custom) - private let redoButton = UIButton(type: .custom) - private let brushButton = UIButton(type: .custom) - private let cropButton = UIButton(type: .custom) - private let newTextButton = UIButton(type: .custom) - private var allButtons = [UIButton]() + // The model supports redo if we ever want to add it. + private let undoButton = OWSButton() + private let brushButton = OWSButton() + private let cropButton = OWSButton() + private let newTextButton = OWSButton() + private let captionButton = OWSButton() + private let doneButton = OWSButton() + private let buttonStackView = UIStackView() // TODO: Should this method be private? @objc - public func addControls(to containerView: UIView) { + public func addControls(to containerView: UIView, + viewController: UIViewController) { configure(button: undoButton, - label: NSLocalizedString("BUTTON_UNDO", comment: "Label for undo button."), + imageName: "image_editor_undo", selector: #selector(didTapUndo(sender:))) - configure(button: redoButton, - label: NSLocalizedString("BUTTON_REDO", comment: "Label for redo button."), - selector: #selector(didTapRedo(sender:))) - configure(button: brushButton, - label: NSLocalizedString("IMAGE_EDITOR_BRUSH_BUTTON", comment: "Label for brush button in image editor."), + imageName: "image_editor_brush", selector: #selector(didTapBrush(sender:))) configure(button: cropButton, - label: NSLocalizedString("IMAGE_EDITOR_CROP_BUTTON", comment: "Label for crop button in image editor."), + imageName: "image_editor_crop", selector: #selector(didTapCrop(sender:))) configure(button: newTextButton, - label: "Text", + imageName: "image_editor_text", selector: #selector(didTapNewText(sender:))) - allButtons = [brushButton, cropButton, undoButton, redoButton, newTextButton] + configure(button: captionButton, + imageName: "image_editor_caption", + selector: #selector(didTapCaption(sender:))) + + configure(button: doneButton, + imageName: "image_editor_checkmark_full", + selector: #selector(didTapDone(sender:))) - let stackView = UIStackView(arrangedSubviews: allButtons) - stackView.axis = .vertical - stackView.alignment = .center - stackView.spacing = 10 + buttonStackView.axis = .horizontal + buttonStackView.alignment = .center + buttonStackView.spacing = 20 - containerView.addSubview(stackView) - stackView.autoAlignAxis(toSuperviewAxis: .horizontal) - stackView.autoPinTrailingToSuperviewMargin(withInset: 10) + containerView.addSubview(buttonStackView) + buttonStackView.autoPin(toTopLayoutGuideOf: viewController, withInset: 0) + buttonStackView.autoPinTrailingToSuperviewMargin(withInset: 18) containerView.addSubview(paletteView) paletteView.autoVCenterInSuperview() @@ -178,26 +184,54 @@ public class ImageEditorView: UIView { } private func configure(button: UIButton, - label: String, + imageName: String, selector: Selector) { - button.setTitle(label, for: .normal) - button.setTitleColor(.white, for: .normal) - button.setTitleColor(.gray, for: .disabled) - button.setTitleColor(UIColor.ows_materialBlue, for: .selected) - button.titleLabel?.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() + if let image = UIImage(named: imageName) { + button.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal) + } else { + owsFailDebug("Missing asset: \(imageName)") + } + button.tintColor = .white button.addTarget(self, action: selector, for: .touchUpInside) + button.layer.shadowColor = UIColor.black.cgColor + button.layer.shadowRadius = 4 + button.layer.shadowOpacity = 0.66 } private func updateButtons() { - undoButton.isEnabled = model.canUndo() - redoButton.isEnabled = model.canRedo() - brushButton.isSelected = editorMode == .brush - cropButton.isSelected = false - newTextButton.isSelected = false - - for button in allButtons { - button.isHidden = isEditingTextItem + var buttons = [OWSButton]() + + var hasPalette = false + switch editorMode { + case .text: + // TODO: + hasPalette = true + break + case .brush: + hasPalette = true + + if model.canUndo() { + buttons = [undoButton, doneButton] + } else { + buttons = [doneButton] + } + case .none: + if model.canUndo() { + buttons = [undoButton, newTextButton, brushButton, cropButton, captionButton] + } else { + buttons = [newTextButton, brushButton, cropButton, captionButton] + } + } + + for subview in buttonStackView.subviews { + subview.removeFromSuperview() + } + buttonStackView.addArrangedSubview(UIView.hStretchingSpacer()) + for button in buttons { + buttonStackView.addArrangedSubview(button) } + + paletteView.isHidden = !hasPalette } // MARK: - Actions @@ -211,19 +245,10 @@ public class ImageEditorView: UIView { model.undo() } - @objc func didTapRedo(sender: UIButton) { - Logger.verbose("") - guard model.canRedo() else { - owsFailDebug("Can't redo.") - return - } - model.redo() - } - @objc func didTapBrush(sender: UIButton) { Logger.verbose("") - toggle(editorMode: .brush) + self.editorMode = .brush } @objc func didTapCrop(sender: UIButton) { @@ -250,13 +275,16 @@ public class ImageEditorView: UIView { edit(textItem: textItem) } - func toggle(editorMode: EditorMode) { - if self.editorMode == editorMode { - self.editorMode = .none - } else { - self.editorMode = editorMode - } - updateButtons() + @objc func didTapCaption(sender: UIButton) { + Logger.verbose("") + + // TODO: + } + + @objc func didTapDone(sender: UIButton) { + Logger.verbose("") + + self.editorMode = .none } // MARK: - Gestures @@ -276,6 +304,11 @@ public class ImageEditorView: UIView { brushGestureRecognizer?.isEnabled = true tapGestureRecognizer?.isEnabled = false pinchGestureRecognizer?.isEnabled = false + case .text: + moveTextGestureRecognizer?.isEnabled = false + brushGestureRecognizer?.isEnabled = false + tapGestureRecognizer?.isEnabled = false + pinchGestureRecognizer?.isEnabled = false } } @@ -543,20 +576,10 @@ public class ImageEditorView: UIView { // MARK: - Edit Text Tool - private var isEditingTextItem = false { - didSet { - AssertIsOnMainThread() - - updateButtons() - } - } - private func edit(textItem: ImageEditorTextItem) { Logger.verbose("") - toggle(editorMode: .none) - - isEditingTextItem = true + self.editorMode = .text // TODO: let maxTextWidthPoints = model.srcImageSizePixels.width * ImageEditorTextItem.kDefaultUnitWidth @@ -572,7 +595,7 @@ public class ImageEditorView: UIView { private func presentCropTool() { Logger.verbose("") - toggle(editorMode: .none) + self.editorMode = .none guard let srcImage = canvasView.loadSrcImage() else { owsFailDebug("Couldn't load src image.") @@ -634,7 +657,7 @@ extension ImageEditorView: ImageEditorTextViewControllerDelegate { public func textEditDidComplete(textItem: ImageEditorTextItem, text: String?) { AssertIsOnMainThread() - isEditingTextItem = false + self.editorMode = .none guard let text = text?.ows_stripped(), text.count > 0 else { @@ -654,7 +677,7 @@ extension ImageEditorView: ImageEditorTextViewControllerDelegate { } public func textEditDidCancel() { - isEditingTextItem = false + self.editorMode = .none } } diff --git a/SignalMessaging/Views/OWSButton.swift b/SignalMessaging/Views/OWSButton.swift index 03ffdf0a0..7985be1dd 100644 --- a/SignalMessaging/Views/OWSButton.swift +++ b/SignalMessaging/Views/OWSButton.swift @@ -29,6 +29,28 @@ public class OWSButton: UIButton { setTitle(title, for: .normal) } + @objc + init(imageName: String, + tintColor: UIColor, + block: @escaping () -> Void = { }) { + super.init(frame: .zero) + + self.block = block + addTarget(self, action: #selector(didTap), for: .touchUpInside) + + setImage(imageName: imageName) + self.tintColor = tintColor + } + + @objc + public func setImage(imageName: String) { + if let image = UIImage(named: imageName) { + setImage(image.withRenderingMode(.alwaysTemplate), for: .normal) + } else { + owsFailDebug("Missing asset: \(imageName)") + } + } + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }