diff --git a/Signal/Images.xcassets/check-circle-filled-16.imageset/Contents.json b/Signal/Images.xcassets/check-circle-filled-16.imageset/Contents.json index 04289d733c..f4a10a48dc 100644 --- a/Signal/Images.xcassets/check-circle-filled-16.imageset/Contents.json +++ b/Signal/Images.xcassets/check-circle-filled-16.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "check-circle-filled-16@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "check-circle-filled-16@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "check-circle-filled-16@3x.png", - "scale" : "3x" + "filename" : "check-circle-solid-16 1.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@1x.png b/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@1x.png deleted file mode 100644 index b56aa6ed88..0000000000 Binary files a/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@2x.png b/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@2x.png deleted file mode 100644 index ae87ee20ae..0000000000 Binary files a/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@3x.png b/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@3x.png deleted file mode 100644 index 3eb6072513..0000000000 Binary files a/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-filled-16@3x.png and /dev/null differ diff --git a/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-solid-16 1.pdf b/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-solid-16 1.pdf new file mode 100644 index 0000000000..b4dbd8f774 --- /dev/null +++ b/Signal/Images.xcassets/check-circle-filled-16.imageset/check-circle-solid-16 1.pdf @@ -0,0 +1,155 @@ +%PDF-1.7 + +1 0 obj + << /BBox [ 0.000000 0.000000 16.000000 16.000000 ] + /Resources << >> + /Subtype /Form + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Type /XObject + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 -0.154968 cm +1.000000 1.000000 1.000000 scn +8.000004 16.154968 m +6.417753 16.154968 4.871035 15.685777 3.555442 14.806726 c +2.239848 13.927673 1.214468 12.678245 0.608967 11.216436 c +0.003466 9.754627 -0.154960 8.146094 0.153721 6.594246 c +0.462403 5.042397 1.224329 3.616934 2.343149 2.498114 c +3.461969 1.379293 4.887434 0.617368 6.439281 0.308686 c +7.991129 0.000004 9.599662 0.158431 11.061471 0.763932 c +12.523280 1.369433 13.772710 2.394814 14.651761 3.710406 c +15.530812 5.025999 16.000004 6.572718 16.000004 8.154968 c +16.000004 10.276700 15.157149 12.311531 13.656858 13.811823 c +12.156568 15.312114 10.121736 16.154968 8.000004 16.154968 c +8.000004 16.154968 l +h +6.760004 4.594968 m +3.760004 7.594968 l +4.820004 8.664968 l +6.760004 6.714969 l +11.180004 11.154968 l +12.240004 10.094969 l +6.760004 4.594968 l +h +f +n +Q + +endstream +endobj + +2 0 obj + 901 +endobj + +3 0 obj + << /BBox [ 0.000000 0.000000 16.000000 16.000000 ] + /Resources << >> + /Subtype /Form + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Type /XObject + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 16.000000 m +16.000000 16.000000 l +16.000000 0.000000 l +0.000000 0.000000 l +0.000000 16.000000 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 232 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 16.000000 16.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Type /Catalog + /Pages 9 0 R + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000001159 00000 n +0000001181 00000 n +0000001661 00000 n +0000001683 00000 n +0000001981 00000 n +0000002083 00000 n +0000002104 00000 n +0000002277 00000 n +0000002351 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +2411 +%%EOF \ No newline at end of file diff --git a/Signal/Images.xcassets/circle-outline-16.imageset/Contents.json b/Signal/Images.xcassets/circle-outline-16.imageset/Contents.json new file mode 100644 index 0000000000..dbda734a61 --- /dev/null +++ b/Signal/Images.xcassets/circle-outline-16.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "circle-outline-16.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Signal/Images.xcassets/circle-outline-16.imageset/circle-outline-16.pdf b/Signal/Images.xcassets/circle-outline-16.imageset/circle-outline-16.pdf new file mode 100644 index 0000000000..7cd0164436 --- /dev/null +++ b/Signal/Images.xcassets/circle-outline-16.imageset/circle-outline-16.pdf @@ -0,0 +1,160 @@ +%PDF-1.7 + +1 0 obj + << /BBox [ 0.000000 0.000000 16.000000 16.000000 ] + /Resources << >> + /Subtype /Form + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Type /XObject + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 -0.154968 cm +1.000000 1.000000 1.000000 scn +8.000004 14.654968 m +9.285583 14.654968 10.542291 14.273750 11.611210 13.559521 c +12.680129 12.845291 13.513250 11.830131 14.005220 10.642410 c +14.497190 9.454691 14.625912 8.147758 14.375108 6.886881 c +14.124304 5.626004 13.505239 4.467815 12.596198 3.558774 c +11.687157 2.649733 10.528968 2.030668 9.268091 1.779864 c +8.007215 1.529060 6.700281 1.657782 5.512561 2.149752 c +4.324841 2.641722 3.309680 3.474843 2.595451 4.543762 c +1.881222 5.612681 1.500004 6.869390 1.500004 8.154968 c +1.502648 9.878063 2.188317 11.529827 3.406731 12.748241 c +4.625144 13.966655 6.276909 14.652324 8.000004 14.654968 c +8.000004 14.654968 l +h +8.000004 16.154968 m +6.417753 16.154968 4.871035 15.685777 3.555442 14.806726 c +2.239848 13.927673 1.214468 12.678245 0.608967 11.216436 c +0.003466 9.754627 -0.154960 8.146094 0.153721 6.594246 c +0.462403 5.042397 1.224329 3.616934 2.343149 2.498114 c +3.461969 1.379293 4.887434 0.617368 6.439281 0.308686 c +7.991129 0.000004 9.599662 0.158431 11.061471 0.763932 c +12.523280 1.369433 13.772710 2.394814 14.651761 3.710406 c +15.530812 5.025999 16.000004 6.572718 16.000004 8.154968 c +16.000004 10.276700 15.157149 12.311531 13.656858 13.811823 c +12.156568 15.312114 10.121736 16.154968 8.000004 16.154968 c +8.000004 16.154968 l +h +f +n +Q + +endstream +endobj + +2 0 obj + 1383 +endobj + +3 0 obj + << /BBox [ 0.000000 0.000000 16.000000 16.000000 ] + /Resources << >> + /Subtype /Form + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Type /XObject + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 16.000000 m +16.000000 16.000000 l +16.000000 0.000000 l +0.000000 0.000000 l +0.000000 16.000000 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 232 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 16.000000 16.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Type /Catalog + /Pages 9 0 R + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000001641 00000 n +0000001664 00000 n +0000002144 00000 n +0000002166 00000 n +0000002464 00000 n +0000002566 00000 n +0000002587 00000 n +0000002760 00000 n +0000002834 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +2894 +%%EOF \ No newline at end of file diff --git a/Signal/src/ViewControllers/Wallpapers/PreviewWallpaperViewController.swift b/Signal/src/ViewControllers/Wallpapers/PreviewWallpaperViewController.swift index 0be44add2d..9ffb4f7b53 100644 --- a/Signal/src/ViewControllers/Wallpapers/PreviewWallpaperViewController.swift +++ b/Signal/src/ViewControllers/Wallpapers/PreviewWallpaperViewController.swift @@ -11,6 +11,7 @@ class PreviewWallpaperViewController: UIViewController { } private(set) var mode: Mode { didSet { modeDidChange() }} let thread: TSThread? + lazy var blurButton = BlurButton { [weak self] shouldBlur in self?.standalonePage?.shouldBlur = shouldBlur } let pageViewController = UIPageViewController( transitionStyle: .scroll, @@ -90,6 +91,10 @@ class PreviewWallpaperViewController: UIViewController { safeAreaCover.autoPinEdge(toSuperviewEdge: .bottom) safeAreaCover.autoPinWidthToSuperview() safeAreaCover.autoPinEdge(.top, to: .bottom, of: buttonStack) + + view.addSubview(blurButton) + blurButton.autoPinEdge(.bottom, to: .top, of: buttonStack, withOffset: -24) + blurButton.autoHCenterInSuperview() } override func viewDidLoad() { @@ -129,7 +134,7 @@ class PreviewWallpaperViewController: UIViewController { } } - private var standalonePage: UIViewController? + private var standalonePage: WallpaperPage? func modeDidChange() { switch mode { case .photo(let selectedPhoto): @@ -139,6 +144,7 @@ class PreviewWallpaperViewController: UIViewController { view.insertSubview(standalonePage.view, at: 0) addChild(standalonePage) standalonePage.view.autoPinEdgesToSuperviewEdges() + blurButton.isHidden = false case .preset(let selectedWallpaper): if pageViewController.view.superview == nil { view.insertSubview(pageViewController.view, at: 0) @@ -149,6 +155,7 @@ class PreviewWallpaperViewController: UIViewController { } currentPage = WallpaperPage(wallpaper: selectedWallpaper) + blurButton.isHidden = true } mockConversationView.mode = buildMockConversationMode() @@ -260,11 +267,15 @@ extension PreviewWallpaperViewController: UIPageViewControllerDataSource, UIPage private class WallpaperPage: UIViewController { let wallpaper: Wallpaper - weak var photo: UIImage? + let photo: UIImage? + var shouldBlur = false { didSet { updatePhoto() } } + init(wallpaper: Wallpaper, photo: UIImage? = nil) { self.wallpaper = wallpaper self.photo = photo super.init(nibName: nil, bundle: nil) + + if photo != nil { prepareBlurredPhoto() } } required init?(coder: NSCoder) { @@ -350,13 +361,68 @@ private class WallpaperPage: UIViewController { } } + private func updatePhoto() { + guard let wallpaperImageView = wallpaperView as? UIImageView else { return } + UIView.transition(with: wallpaperImageView, duration: 0.2, options: .transitionCrossDissolve) { + wallpaperImageView.image = self.shouldBlur ? self.blurredPhoto : self.photo + } completion: { _ in } + } + + private var blurredPhoto: UIImage? + private func prepareBlurredPhoto() { + DispatchQueue.global(qos: .userInteractive).async { [weak self] in + guard let self = self else { return } + + guard let clampFilter = CIFilter(name: "CIAffineClamp") else { + return owsFailDebug("Failed to create blur filter") + } + + guard let blurFilter = CIFilter(name: "CIGaussianBlur", parameters: [kCIInputRadiusKey: 3]) else { + return owsFailDebug("Failed to create blur filter") + } + + guard let photo = self.photo else { + return owsFailDebug("Could not load src image.") + } + + guard let resizedPhoto = photo.resized(withMaxDimensionPixels: 300), let resizedCGPhoto = resizedPhoto.cgImage else { + return owsFailDebug("Failed to downsize image for blur") + } + + // In order to get a nice edge-to-edge blur, we must apply a clamp filter and *then* the blur filter. + let inputPhoto = CIImage(cgImage: resizedCGPhoto) + clampFilter.setDefaults() + clampFilter.setValue(inputPhoto, forKey: kCIInputImageKey) + + guard let clampOutput = clampFilter.outputImage else { + return owsFailDebug("Failed to clamp src image") + } + + blurFilter.setValue(clampOutput, forKey: kCIInputImageKey) + + guard let blurredOutput = blurFilter.value(forKey: kCIOutputImageKey) as? CIImage else { + return owsFailDebug("Failed to blur clamped image") + } + + let context = CIContext(options: nil) + guard let blurredPhoto = context.createCGImage(blurredOutput, from: inputPhoto.extent) else { + return owsFailDebug("Failed to create CGImage from blurred output") + } + + self.blurredPhoto = UIImage(cgImage: blurredPhoto) + + // Once the blur is ready, update any content in case the user already blurred + DispatchQueue.main.async { [weak self] in + self?.updatePhoto() + } + } + } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate { _ in self.updateWallpaperConstraints(reference: size) - } completion: { _ in - - } + } completion: { _ in } } private var previousReferenceSize: CGSize = .zero @@ -401,3 +467,76 @@ extension WallpaperPage: UIScrollViewDelegate { return wallpaperView } } + +class BlurButton: UIButton { + let checkImageView = UIImageView() + let label = UILabel() + let action: (Bool) -> Void + let backgroundView: UIView = { + if UIAccessibility.isReduceTransparencyEnabled { + let backgroundView = UIView() + backgroundView.backgroundColor = .ows_blackAlpha80 + return backgroundView + } else { + let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + return blurView + } + }() + + init(action: @escaping (Bool) -> Void) { + self.action = action + super.init(frame: .zero) + + addTarget(self, action: #selector(didTap), for: .touchUpInside) + + layoutMargins = UIEdgeInsets(top: 3, leading: 10, bottom: 3, trailing: 12) + autoSetDimension(.height, toSize: 28, relation: .greaterThanOrEqual) + + backgroundView.clipsToBounds = true + backgroundView.isUserInteractionEnabled = false + addSubview(backgroundView) + backgroundView.autoPinEdgesToSuperviewEdges() + + addSubview(checkImageView) + checkImageView.autoPinEdge(toSuperviewMargin: .leading) + checkImageView.autoPinHeightToSuperviewMargins() + checkImageView.autoSetDimension(.width, toSize: 16) + checkImageView.contentMode = .scaleAspectFit + checkImageView.isUserInteractionEnabled = false + + label.font = .ows_semiboldFont(withSize: 14) + label.textColor = .white + label.text = NSLocalizedString("WALLPAPER_PREVIEW_BLUR_BUTTON", + comment: "Blur button on wallpaper preview.") + addSubview(label) + label.autoPinHeightToSuperviewMargins() + label.autoPinEdge(toSuperviewMargin: .trailing) + label.autoPinEdge(.leading, to: .trailing, of: checkImageView, withOffset: 10) + label.isUserInteractionEnabled = false + + isSelected = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + backgroundView.layer.cornerRadius = height / 2 + } + + override var isSelected: Bool { + didSet { + UIView.transition(with: checkImageView, duration: 0.15, options: .transitionCrossDissolve) { + self.checkImageView.image = self.isSelected ? #imageLiteral(resourceName: "check-circle-filled-16") : #imageLiteral(resourceName: "circle-outline-16") + } completion: { _ in } + } + } + + @objc + func didTap() { + isSelected = !isSelected + action(isSelected) + } +} diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index e7e36e0418..d28de52539 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -5182,6 +5182,9 @@ /* Placeholder text for header of the wallpaper mini preview */ "WALLPAPER_MINI_PREVIEW_CONTACT_NAME" = "Contact Name"; +/* Blur button on wallpaper preview. */ +"WALLPAPER_PREVIEW_BLUR_BUTTON" = "Blur Photo"; + /* The incoming bubble text when setting a photo */ "WALLPAPER_PREVIEW_INCOMING_MESSAGE_PHOTO" = "Pinch to zoom, drag to adjust.";