diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj
index 20f328642..8a0807a9d 100644
--- a/Signal.xcodeproj/project.pbxproj
+++ b/Signal.xcodeproj/project.pbxproj
@@ -33,6 +33,8 @@
 		3453D8EA1EC0D4ED003F9E6F /* OWSAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3453D8E91EC0D4ED003F9E6F /* OWSAlerts.swift */; };
 		345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671001E89A5F1006EE662 /* ThreadUtil.m */; };
 		3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */; };
+		346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
+		346B66331F4F08FD00E5122F /* IMG_4187.PNG in Resources */ = {isa = PBXBuildFile; fileRef = 346B66321F4F08FD00E5122F /* IMG_4187.PNG */; };
 		3471B1DA1EB7C63600F6AEC8 /* NewNonContactConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */; };
 		3472229F1EB22FFE00E53955 /* AddToGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */; };
 		348F2EAE1F0D21BC00D4ECE0 /* DeviceSleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */; };
@@ -439,6 +441,8 @@
 		345671001E89A5F1006EE662 /* ThreadUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadUtil.m; sourceTree = "<group>"; };
 		345671081E8A9F5D006EE662 /* TSGenericAttachmentAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGenericAttachmentAdapter.h; sourceTree = "<group>"; };
 		345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGenericAttachmentAdapter.m; sourceTree = "<group>"; };
+		346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = "<group>"; };
+		346B66321F4F08FD00E5122F /* IMG_4187.PNG */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = IMG_4187.PNG; sourceTree = "<group>"; };
 		3471B1D81EB7C63600F6AEC8 /* NewNonContactConversationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewNonContactConversationViewController.h; sourceTree = "<group>"; };
 		3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewNonContactConversationViewController.m; sourceTree = "<group>"; };
 		3472229D1EB22FFE00E53955 /* AddToGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToGroupViewController.h; sourceTree = "<group>"; };
@@ -1000,6 +1004,7 @@
 				3448BFC01EDF0EA7005B2D69 /* ConversationView */,
 				34B3F8401E8DF1700035BE1A /* CountryCodeViewController.h */,
 				34B3F8411E8DF1700035BE1A /* CountryCodeViewController.m */,
+				346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */,
 				34D8C0221ED3673300188D7C /* DebugUI */,
 				3497DBED1ECE2E4700DB2605 /* DomainFrontingCountryViewController.h */,
 				3497DBEE1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m */,
@@ -1555,6 +1560,7 @@
 				AD83FF381A73426500B5C81A /* audio_pause_button_blue.png */,
 				AD83FF391A73426500B5C81A /* audio_pause_button_blue@2x.png */,
 				AD83FF3A1A73426500B5C81A /* audio_play_button_blue@2x.png */,
+				346B66321F4F08FD00E5122F /* IMG_4187.PNG */,
 				AD83FF3B1A73426500B5C81A /* audio_play_button.png */,
 				AD83FF3C1A73426500B5C81A /* audio_play_button@2x.png */,
 				AD83FF3D1A73426500B5C81A /* audio_pause_button.png */,
@@ -1974,6 +1980,7 @@
 				34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */,
 				B633C5C41A1D190B0059AC12 /* mute_on@2x.png in Resources */,
 				B633C5CE1A1D190B0059AC12 /* quit@2x.png in Resources */,
+				346B66331F4F08FD00E5122F /* IMG_4187.PNG in Resources */,
 				AD83FF441A73426500B5C81A /* audio_pause_button.png in Resources */,
 				B6F509971AA53F760068F56A /* Localizable.strings in Resources */,
 				AD41D7B51A6F6F0600241130 /* play_button.png in Resources */,
@@ -2372,6 +2379,7 @@
 				34C42D661F4734ED0072EC04 /* OWSContactOffersInteraction.m in Sources */,
 				34B3F8941E8DF1710035BE1A /* SignalsViewController.m in Sources */,
 				34E8BF381EE9E2FD00F5F4CA /* FingerprintViewScanController.m in Sources */,
+				346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */,
 				76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */,
 				34330A611E788EA900DF2FB9 /* AttachmentUploadView.m in Sources */,
 				34B3F87D1E8DF1700035BE1A /* FullImageViewController.m in Sources */,
@@ -2564,7 +2572,11 @@
 					"DEBUG=1",
 					"$(inherited)",
 				);
-				"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1";
+				"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
+					"DEBUG=1",
+					"$(inherited)",
+					"SSK_BUILDING_FOR_TESTS=1",
+				);
 				GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
 				GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
diff --git a/Signal/Images/IMG_4187.PNG b/Signal/Images/IMG_4187.PNG
new file mode 100644
index 000000000..1faf58606
Binary files /dev/null and b/Signal/Images/IMG_4187.PNG differ
diff --git a/Signal/src/ViewControllers/AvatarViewHelper.m b/Signal/src/ViewControllers/AvatarViewHelper.m
index 9e97d54e9..534ad97ea 100644
--- a/Signal/src/ViewControllers/AvatarViewHelper.m
+++ b/Signal/src/ViewControllers/AvatarViewHelper.m
@@ -98,6 +98,28 @@ NS_ASSUME_NONNULL_BEGIN
     }
 }
 
+- (void)showCropScaleUI
+{
+    OWSAssert([NSThread isMainThread]);
+    OWSAssert(self.delegate);
+
+    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
+    picker.delegate = self;
+    picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
+
+    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) {
+        picker.mediaTypes = [[NSArray alloc] initWithObjects:(NSString *)kUTTypeImage, nil];
+        [self.delegate.fromViewController presentViewController:picker
+                                                       animated:YES
+                                                     completion:[UIUtil modalCompletionBlock]];
+    }
+}
+//// We resize the avatar to fill a 210x210 square.
+////
+//// See: GroupCreateActivity.java in Signal-Android.java.
+// UIImage *resizedAvatar = [rawAvatar resizedImageToFillPixelSize:CGSizeMake(210, 210)];
+//[self.delegate avatarDidChange:resizedAvatar];
+
 /*
  *  Dismissing UIImagePickerController
  */
@@ -121,11 +143,7 @@ NS_ASSUME_NONNULL_BEGIN
     UIImage *rawAvatar = [info objectForKey:UIImagePickerControllerOriginalImage];
 
     if (rawAvatar) {
-        // We resize the avatar to fill a 210x210 square.
-        //
-        // See: GroupCreateActivity.java in Signal-Android.java.
-        UIImage *resizedAvatar = [rawAvatar resizedImageToFillPixelSize:CGSizeMake(210, 210)];
-        [self.delegate avatarDidChange:resizedAvatar];
+        //        [self showCropScaleUI:rawAvatar];
     }
 
     [self.delegate.fromViewController dismissViewControllerAnimated:YES completion:nil];
diff --git a/Signal/src/ViewControllers/CropScaleImageViewController.swift b/Signal/src/ViewControllers/CropScaleImageViewController.swift
new file mode 100644
index 000000000..599e7e7f9
--- /dev/null
+++ b/Signal/src/ViewControllers/CropScaleImageViewController.swift
@@ -0,0 +1,798 @@
+//
+//  Copyright (c) 2017 Open Whisper Systems. All rights reserved.
+//
+
+import Foundation
+import MediaPlayer
+
+class OWSLayerView: UIView {
+    var layoutCallback : (() -> Void)?
+
+    required init(frame: CGRect, layoutCallback : @escaping () -> Void) {
+        self.layoutCallback = layoutCallback
+        super.init(frame: frame)
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        self.layoutCallback = {
+        }
+        super.init(coder: aDecoder)
+    }
+
+    override var bounds: CGRect {
+        didSet {
+            guard let layoutCallback = self.layoutCallback else {
+                return
+            }
+            layoutCallback()
+        }
+    }
+
+    override var frame: CGRect {
+        didSet {
+            guard let layoutCallback = self.layoutCallback else {
+                return
+            }
+            layoutCallback()
+        }
+    }
+}
+
+class CropScaleImageViewController: OWSViewController {
+
+    let TAG = "[CropScaleImageViewController]"
+
+    // MARK: Properties
+
+    let srcImage: UIImage
+
+    var successCompletion: ((UIImage) -> Void)?
+
+    var imageView: UIView?
+
+    var imageLayer: CALayer?
+
+    var dashedBorderLayer: CAShapeLayer?
+
+//    var defaultCropFramePoints: CGRect?
+//    var currentCropFramePoints: CGRect?
+
+    // In width/height.
+    let targetAspectRatio: CGFloat = 1.0
+
+    var srcImageSizePoints: CGSize = CGSize.zero
+    var unitDefaultCropSizePoints: CGSize = CGSize.zero
+//    var unitDefaultCropFramePoints : CGRect = CGRect.zero
+//    coordinate
+//    var maxUnitTranslation : CGPoint = CGPoint.zero
+
+    // N = Scaled, zoomed in.
+    let kMaxImageScale: CGFloat = 4.0
+    // 1.0 = Unscaled, cropped to fill crop rect.
+    let kMinImageScale: CGFloat = 1.0
+    var imageScale: CGFloat = 1.0
+
+    // 0
+//    var imageTranslation : CGPoint = CGPoint.zero
+    var srcTranslation: CGPoint = CGPoint.zero
+//    var maxImageTranslation : CGPoint = CGPoint.zero
+
+//
+//    var imageScale : CGFloat = kMinImageScale
+//    var imageTranslation : CGPoint = CGPoint.zero
+
+//    var videoPlayer: MPMoviePlayerController?
+//
+//    var audioPlayer: OWSAudioAttachmentPlayer?
+//    var audioStatusLabel: UILabel?
+//    var audioPlayButton: UIButton?
+//    var isAudioPlayingFlag = false
+//    var isAudioPaused = false
+//    var audioProgressSeconds: CGFloat = 0
+//    var audioDurationSeconds: CGFloat = 0
+
+    // MARK: Initializers
+
+    @available(*, unavailable, message:"use attachment: constructor instead.")
+    required init?(coder aDecoder: NSCoder) {
+        self.srcImage = UIImage(named:"fail")!
+        super.init(coder: aDecoder)
+        owsFail("\(self.TAG) invalid constructor")
+
+        configureCropAndScale()
+    }
+
+    required init(srcImage: UIImage, successCompletion : @escaping (UIImage) -> Void) {
+        self.srcImage = srcImage
+        self.successCompletion = successCompletion
+        super.init(nibName: nil, bundle: nil)
+
+        configureCropAndScale()
+    }
+
+    // MARK: Cropping and Scaling
+
+    private func configureCropAndScale() {
+        // Size of bounding box that reflects the target aspect ratio, whose longer side = 1.
+        let unitSquareHeight: CGFloat = (targetAspectRatio >= 1.0 ? 1.0 : 1.0 / targetAspectRatio)
+        let unitSquareWidth: CGFloat = (targetAspectRatio >= 1.0 ? targetAspectRatio * unitSquareHeight : 1.0)
+        let unitSquareSize = CGSize(width: unitSquareWidth, height: unitSquareHeight)
+
+        let imageSizePoints = srcImage.size
+        guard
+            (imageSizePoints.width > 0 && imageSizePoints.height > 0) else {
+                return
+        }
+        self.srcImageSizePoints = imageSizePoints
+
+        Logger.error("----")
+        Logger.error("imageSizePoints: \(imageSizePoints)")
+        Logger.error("unitSquareWidth: \(unitSquareWidth)")
+        Logger.error("unitSquareHeight: \(unitSquareHeight)")
+
+        // Default
+
+        // The "default" (no scaling, no translation) crop frame, expressed in 
+        // srcImage's coordinate system.
+        unitDefaultCropSizePoints = defaultCropSizePoints(dstSizePoints:unitSquareSize)
+//        unitDefaultCropFramePoints = defaultCropFramePoints(dstSizePoints:unitSquareSize)
+        assert(imageSizePoints.width >= unitDefaultCropSizePoints.width)
+        assert(imageSizePoints.height >= unitDefaultCropSizePoints.height)
+
+//        maxUnitTranslation = CGPoint(x:
+        Logger.error("unitDefaultCropSizePoints: \(unitDefaultCropSizePoints)")
+        srcTranslation = CGPoint(x:(imageSizePoints.width - unitDefaultCropSizePoints.width) * 0.5,
+                                        y:(imageSizePoints.height - unitDefaultCropSizePoints.height) * 0.5)
+        Logger.error("srcTranslation: \(srcTranslation)")
+//        let maxSrcTranslation = CGPoint(x:(imageSizePoints.width - unitDefaultCropSizePoints.width) * 0.5,
+//                                        y:(imageSizePoints.height - unitDefaultCropSizePoints.height) * 0.5)
+//        srcTranslation =
+
+//        self.defaultCropFramePoints = defaultCropFramePoints
+//        let maxCropSizePoints = CGSize(width:defaultCropFramePoints.width,
+//                                       height:defaultCropFramePoints.height)
+//        let minCropSizePoints = CGSize(width:defaultCropFramePoints.width / CropScaleImageViewController.kMaxImageScale,
+//                                       height:defaultCropFramePoints.height / CropScaleImageViewController.kMaxImageScale)
+//        Logger.error("defaultCropFramePoints: \(defaultCropFramePoints)")
+//        Logger.error("maxCropSizePoints: \(maxCropSizePoints)")
+//        Logger.error("minCropSizePoints: \(minCropSizePoints)")
+//        
+//        if currentCropFramePoints == nil {
+//            currentCropFramePoints = defaultCropFramePoints
+//        }
+//        var cropFramePoints = currentCropFramePoints!
+    }
+
+    private func defaultCropSizePoints(dstSizePoints: CGSize) -> (CGSize) {
+        assert(srcImageSizePoints.width > 0)
+        assert(srcImageSizePoints.height > 0)
+
+        let imageAspectRatio = srcImageSizePoints.width / srcImageSizePoints.height
+        let dstAspectRatio = dstSizePoints.width / dstSizePoints.height
+
+        var dstCropSizePoints = CGSize.zero
+        if imageAspectRatio > dstAspectRatio {
+            dstCropSizePoints = CGSize(width: dstSizePoints.width / dstSizePoints.height * srcImageSizePoints.height, height: srcImageSizePoints.height)
+        } else {
+            dstCropSizePoints = CGSize(width: srcImageSizePoints.width, height: dstSizePoints.height / dstSizePoints.width * srcImageSizePoints.width)
+        }
+        return dstCropSizePoints
+    }
+
+//    private func defaultCropFramePoints(dstSizePoints: CGSize) -> (CGRect) {
+//        assert(imageSizePoints.width > 0)
+//        assert(imageSizePoints.height > 0)
+//        
+//        let imageAspectRatio = imageSizePoints.width / imageSizePoints.height
+//        let dstAspectRatio = dstSizePoints.width / dstSizePoints.height
+//        
+//        var dstCropSizePoints = CGSize.zero
+//        if imageAspectRatio > dstAspectRatio {
+//            dstCropSizePoints = CGSize(width: dstSizePoints.width / dstSizePoints.height * imageSizePoints.height, height: imageSizePoints.height)
+//        } else {
+//            dstCropSizePoints = CGSize(width: imageSizePoints.width, height: dstSizePoints.height / dstSizePoints.width * imageSizePoints.width)
+//        }
+//        
+//        let dstCropOriginPoints = CGPoint.zero
+//        assert(dstCropOriginPoints.x >= 0)
+//        assert(dstCropOriginPoints.y >= 0)
+//        assert(dstCropOriginPoints.x <= dstSizePoints.width - dstCropSizePoints.width)
+//        assert(dstCropOriginPoints.y <= dstSizePoints.height - dstCropSizePoints.height)
+//        return CGRect(origin:dstCropOriginPoints, size:dstCropSizePoints)
+//    }
+//    
+    // MARK: View Lifecycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        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()
+    }
+
+//    override func viewWillAppear(_ animated: Bool) {
+//        super.viewWillAppear(animated)
+//
+//        ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(true)
+//    }
+//
+//    override func viewWillDisappear(_ animated: Bool) {
+//        super.viewWillDisappear(animated)
+//
+//        ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(false)
+//    }
+
+    // MARK: - Create Views
+
+    private func createViews() {
+        let previewTopMargin: CGFloat = 30
+        let previewHMargin: CGFloat = 20
+
+        let contentView = UIView()
+        self.view.addSubview(contentView)
+        contentView.autoPinWidthToSuperview(withMargin:previewHMargin)
+        contentView.autoPin(toTopLayoutGuideOf: self, withInset:previewTopMargin)
+
+        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()
+        })
+        imageView.clipsToBounds = true
+        self.imageView = imageView
+        contentView.addSubview(imageView)
+        imageView.autoPinWidthToSuperview(withMargin:imageHMargin)
+        imageView.autoVCenterInSuperview()
+        imageView.autoPinToSquareAspectRatio()
+
+        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 = [6, 6]
+        dashedBorderLayer.lineWidth = 2
+        dashedBorderLayer.fillColor = nil
+        imageView.layer.addSublayer(dashedBorderLayer)
+
+        contentView.isUserInteractionEnabled = true
+        contentView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:))))
+        contentView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(sender:))))
+    }
+
+    override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+
+        updateImageLayout()
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        self.view.layoutSubviews()
+        updateImageLayout()
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+
+        updateImageLayout()
+    }
+
+//    private func imageSizeAndViewSizePoints(imageView: UIView) -> (CGSize?, CGSize?) {
+//        let imageSizePoints = srcImage.size
+//        guard
+//            (imageSizePoints.width > 0 && imageSizePoints.height > 0) else {
+//                return (nil, nil)
+//        }
+//
+//        let viewSizePoints = imageView.frame.size
+//        guard
+//            (viewSizePoints.width > 0 && viewSizePoints.height > 0) else {
+//                return (nil, nil)
+//        }
+//
+//        return (imageSizePoints, viewSizePoints)
+//    }
+
+    private func defaultCropFramePoints(imageSizePoints: CGSize, viewSizePoints: CGSize) -> (CGRect) {
+        let imageAspectRatio = imageSizePoints.width / imageSizePoints.height
+        let viewAspectRatio = viewSizePoints.width / viewSizePoints.height
+
+        var defaultCropSizePoints = CGSize.zero
+        if imageAspectRatio > viewAspectRatio {
+            defaultCropSizePoints = CGSize(width: viewSizePoints.width / viewSizePoints.height * imageSizePoints.height, height: imageSizePoints.height)
+        } else {
+            defaultCropSizePoints = CGSize(width: imageSizePoints.width, height: viewSizePoints.height / viewSizePoints.width * imageSizePoints.width)
+        }
+
+        let defaultCropOriginPoints = CGPoint(x: (imageSizePoints.width - defaultCropSizePoints.width) * 0.5,
+                                         y: (imageSizePoints.height - defaultCropSizePoints.height) * 0.5)
+        assert(defaultCropOriginPoints.x >= 0)
+        assert(defaultCropOriginPoints.y >= 0)
+        assert(defaultCropOriginPoints.x <= imageSizePoints.width - defaultCropSizePoints.width)
+        assert(defaultCropOriginPoints.y <= imageSizePoints.height - defaultCropSizePoints.height)
+        return CGRect(origin:defaultCropOriginPoints, size:defaultCropSizePoints)
+    }
+
+//    private func imageBaseSizeAndOffset(imageSize:CGSize,viewSize:CGSize) -> (CGSize, CGPoint)
+//    {
+//        let imageAspectRatio = imageSize.width / imageSize.height
+//        let viewAspectRatio = viewSize.width / viewSize.height
+//        
+//        var imageBaseSize = CGSize.zero
+//        if imageAspectRatio > viewAspectRatio {
+//            imageBaseSize = CGSize(width: imageSize.width / imageSize.height * viewSize.height, height: viewSize.height)
+//        } else {
+//            imageBaseSize = CGSize(width: viewSize.width, height: imageSize.height / imageSize.width * viewSize.width)
+//        }
+//        
+//        let imageBaseOffset = CGPoint(x: (imageBaseSize.width - viewSize.width) * -0.5,
+//                                      y: (imageBaseSize.height - viewSize.height) * -0.5)
+//        
+//        return (imageBaseSize, imageBaseOffset)
+//    }
+
+    private func updateImageLayout() {
+        guard let imageView = self.imageView else {
+            return
+        }
+        guard let imageLayer = self.imageLayer else {
+            return
+        }
+        guard let dashedBorderLayer = self.dashedBorderLayer else {
+            return
+        }
+        guard srcImageSizePoints.width > 0 && srcImageSizePoints.height > 0 else {
+            return
+        }
+        guard unitDefaultCropSizePoints.width > 0 && unitDefaultCropSizePoints.height > 0 else {
+            return
+        }
+
+//        var imageSizePoints : CGSize = CGSize.zero
+//        var unitDefaultCropSizePoints : CGSize = CGSize.zero
+
+//        let imageSizePoints = srcImage.size
+//        guard
+//            (imageSizePoints.width > 0 && imageSizePoints.height > 0) else {
+//                return
+//        }
+
+        let viewSizePoints = imageView.frame.size
+        guard
+            (viewSizePoints.width > 0 && viewSizePoints.height > 0) else {
+                return
+        }
+
+//        let (srcSizePointsOptional, viewSizePointsOptional) = imageSizeAndViewSizePoints(imageView:imageView)
+//        guard let srcSizePoints = srcSizePointsOptional else {
+//            return
+//        }
+//        guard let viewSizePoints = viewSizePointsOptional else {
+//            return
+//        }
+        Logger.error("----")
+        Logger.error("srcImageSizePoints: \(srcImageSizePoints)")
+        Logger.error("viewSizePoints: \(viewSizePoints)")
+
+//        let viewDefaultCropSizePoints = defaultCropSizePoints(dstSizePoints:viewSizePoints)
+//        assert(viewDefaultCropSizePoints.width >= unitDefaultCropSizePoints.width)
+//        assert(viewDefaultCropSizePoints.height >= unitDefaultCropSizePoints.height)
+//
+//        Logger.error("viewDefaultCropSizePoints: \(viewDefaultCropSizePoints)")
+
+        Logger.error("imageScale: \(imageScale)")
+        imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale))
+        Logger.error("imageScale (normalized): \(imageScale)")
+
+        let srcCropSizePoints = CGSize(width:unitDefaultCropSizePoints.width / imageScale,
+                                       height:unitDefaultCropSizePoints.height / imageScale)
+
+        Logger.error("srcCropSizePoints: \(srcCropSizePoints)")
+
+        let minSrcTranslationPoints = CGPoint.zero
+        let maxSrcTranslationPoints = CGPoint(x:srcImageSizePoints.width - srcCropSizePoints.width,
+                                              y:srcImageSizePoints.height - srcCropSizePoints.height
+                                              )
+
+        Logger.error("minSrcTranslationPoints: \(minSrcTranslationPoints)")
+        Logger.error("maxSrcTranslationPoints: \(maxSrcTranslationPoints)")
+
+        Logger.error("srcTranslation: \(srcTranslation)")
+        srcTranslation = CGPoint(x: max(minSrcTranslationPoints.x, min(maxSrcTranslationPoints.x, srcTranslation.x)),
+                                               y: max(minSrcTranslationPoints.y, min(maxSrcTranslationPoints.y, srcTranslation.y)))
+        Logger.error("srcTranslation (normalized): \(srcTranslation)")
+
+        let srcToViewRatio = viewSizePoints.width / srcCropSizePoints.width
+        Logger.error("srcToViewRatio: \(srcToViewRatio)")
+
+        let imageViewFrame = CGRect(origin: CGPoint(x:srcTranslation.x * -srcToViewRatio,
+                                                    y:srcTranslation.y * -srcToViewRatio),
+                                    size: CGSize(width:srcImageSizePoints.width * +srcToViewRatio,
+                                                 height:srcImageSizePoints.height * +srcToViewRatio
+                                                 ))
+        Logger.error("imageViewFrame: \(imageViewFrame)")
+        imageLayer.removeAllAnimations()
+        imageLayer.frame = imageViewFrame
+
+//        //        maxUnitTranslation = CGPoint(x:
+//        Logger.error("unitDefaultCropSizePoints: \(unitDefaultCropSizePoints)")
+//        srcTranslation = CGPoint(x:(imageSizePoints.width - unitDefaultCropSizePoints.width) * 0.5,
+//                                 y:(imageSizePoints.height - unitDefaultCropSizePoints.height) * 0.5)
+//        Logger.error("srcTranslation: \(srcTranslation)")
+//
+//        
+//        // Default
+//
+//        let defaultCropFramePoints = self.defaultCropFramePoints(imageSizePoints:imageSizePoints, viewSizePoints:viewSizePoints)
+//        self.defaultCropFramePoints = defaultCropFramePoints
+//        let maxCropSizePoints = CGSize(width:defaultCropFramePoints.width,
+//                                 height:defaultCropFramePoints.height)
+//        let minCropSizePoints = CGSize(width:defaultCropFramePoints.width / CropScaleImageViewController.kMaxImageScale,
+//                                 height:defaultCropFramePoints.height / CropScaleImageViewController.kMaxImageScale)
+//        Logger.error("defaultCropFramePoints: \(defaultCropFramePoints)")
+//        Logger.error("maxCropSizePoints: \(maxCropSizePoints)")
+//        Logger.error("minCropSizePoints: \(minCropSizePoints)")
+//
+//        if currentCropFramePoints == nil {
+//            currentCropFramePoints = defaultCropFramePoints
+//        }
+//        var cropFramePoints = currentCropFramePoints!
+//
+//        // Ensure the crop frame has valid origin and size.0
+//        cropFramePoints.size.width = max(minCropSizePoints.width, min(maxCropSizePoints.width, cropFramePoints.size.width))
+//        cropFramePoints.size.height = max(minCropSizePoints.height, min(maxCropSizePoints.height, cropFramePoints.size.height))
+//        let minCropOriginPoints = CGPoint.zero
+//        let maxCropOriginPoints = CGPoint(x:imageSizePoints.width - cropFramePoints.size.width,
+//                                          y:imageSizePoints.height - cropFramePoints.size.height
+//                                          )
+//        cropFramePoints.origin.x = max(minCropOriginPoints.x, min(maxCropOriginPoints.x, cropFramePoints.origin.x))
+//        cropFramePoints.origin.y = max(minCropOriginPoints.y, min(maxCropOriginPoints.y, cropFramePoints.origin.y))
+//
+//        // Update the property.
+//        currentCropFramePoints = cropFramePoints
+//        Logger.error("cropFramePoints: \(cropFramePoints)")
+//
+//        let displayScaleWidth = viewSizePoints.width / cropFramePoints.width
+//        let displayScaleHeight = viewSizePoints.height / cropFramePoints.height
+//        let displayScale = (displayScaleWidth + displayScaleHeight) * 0.5
+//        let displayFramePoints = CGRect(origin: CGPoint(x:cropFramePoints.origin.x * -displayScale, y:cropFramePoints.origin.y * -displayScale),
+//                                        size: CGSize(width:imageSizePoints.width * displayScale,
+//                                                     height:imageSizePoints.height * displayScale))
+//        Logger.error("displayScaleWidth: \(displayScaleWidth)")
+//        Logger.error("displayScaleHeight: \(displayScaleHeight)")
+//        Logger.error("displayScale: \(displayScale)")
+//        Logger.error("displayFramePoints: \(displayFramePoints)")
+//        imageLayer.frame = displayFramePoints
+//        Logger.error("imageView: \(imageView.frame)")
+//        Logger.error("imageLayer: \(displayFramePoints)")
+
+////        let minCropFramePoints =
+////        static let kMaxImageScale : CGFloat = 4.0
+////        static let kMinImageScale : CGFloat = 1.0
+//
+//        let (imageBaseSize, imageBaseOffset) = imageBaseSizeAndOffset(imageSize:imageSize, viewSize:viewSize)
+//        
+//        if cropFramePoints == nil || defaultCropFramePoints == nil {
+//            defaultCropFramePoints =
+//                CGRect(origin: imageBaseOffset, size: imageBaseSize)
+//            cropFramePoints = defaultCropFramePoints
+//        }
+//
+////        guard let imageView = self.imageView else {
+////            return
+////        }
+////        guard let imageLayer = self.imageLayer else {
+////            return
+////        }
+////        guard let dashedBorderLayer = self.dashedBorderLayer else {
+////            return
+////        }
+//        
+////        let imageSize = srcImage.size
+////        guard
+////            (imageSize.width > 0 && imageSize.height > 0) else {
+////                return
+////        }
+////        
+////        let viewSize = imageView.frame.size
+////        guard
+////            (viewSize.width > 0 && viewSize.height > 0) else {
+////                return
+////        }
+//        
+//        // Base
+//        
+////        let imageAspectRatio = imageSize.width / imageSize.height
+////        let viewAspectRatio = viewSize.width / viewSize.height
+////        
+////        var imageBaseSize = CGSize.zero
+////        if imageAspectRatio > viewAspectRatio {
+////            imageBaseSize = CGSize(width: imageSize.width / imageSize.height * viewSize.height, height: viewSize.height)
+////        } else {
+////            imageBaseSize = CGSize(width: viewSize.width, height: imageSize.height / imageSize.width * viewSize.width)
+////        }
+////
+////        let imageBaseOffset = CGPoint(x: (imageBaseSize.width - viewSize.width) * -0.5,
+////                                      y: (imageBaseSize.height - viewSize.height) * -0.5)
+//        
+//        // Display
+//        
+////        assert(imageScale >= CropScaleImageViewController.kMinImageScale)
+////        assert(imageScale <= CropScaleImageViewController.kMaxImageScale)
+//        
+////        static let kMaxImageScale : CGFloat = 4.0
+////        static let kMinImageScale : CGFloat = 1.0
+////        
+////        var imageScale : CGFloat = kMinImageScale
+//
+//        let imageDisplaySize = CGSize(width: imageBaseSize.width * imageScale,
+//                                      height: imageBaseSize.height * imageScale)
+//        let imageDisplayOffset = CGPoint(x: imageBaseOffset.x + imageTranslation.x * imageScale,
+//                                         y: imageBaseOffset.y + imageTranslation.y * imageScale)
+//        // TODO: Assert that imageDisplayOffset is valid.
+////        var imageScale : CGFloat = 1.0
+////        var imageTranslation : CGPoint = CGPoint.zero
+//
+//        imageLayer.frame = CGRect(origin: imageDisplayOffset, size: imageDisplaySize)
+//        Logger.error("imageView: \(NSStringFromCGRect(imageView.frame))")
+//        Logger.error("imageLayer: \(imageLayer.frame)")
+
+        dashedBorderLayer.frame = imageView.bounds
+        dashedBorderLayer.path = UIBezierPath(rect: imageView.bounds).cgPath
+    }
+
+    var srcTranslationAtPinchStart: CGPoint = CGPoint.zero
+    var imageScaleAtPinchStart: CGFloat = 0
+
+//    var currentCropFramePointsAtPinchStart: CGRect = CGRect.zero
+    var lastPinchLocation: CGPoint = CGPoint.zero
+    var lastPinchScale: CGFloat = 1.0
+//    var isPinching = false
+
+    func handlePinch(sender: UIPinchGestureRecognizer) {
+        Logger.error("pinch scale: \(sender.scale)")
+        switch (sender.state) {
+        case .possible:
+            break
+        case .began:
+            srcTranslationAtPinchStart = srcTranslation
+            imageScaleAtPinchStart = imageScale
+
+//            guard let currentCropFramePoints = currentCropFramePoints else {
+//                isPinching = false
+//                return
+//            }
+//            currentCropFramePointsAtPinchStart = currentCropFramePoints
+//            isPinching = true
+            lastPinchLocation =
+                sender.location(in: sender.view)
+            lastPinchScale = sender.scale
+            break
+        case .changed, .ended:
+            guard let imageView = self.imageView else {
+                return
+            }
+//            guard isPinching else {
+//                return
+//            }
+//            guard let imageView = self.imageView else {
+//                return
+//            }
+//            
+//            let (_, viewSizePointsOptional) = imageSizeAndViewSizePoints(imageView:imageView)
+//            guard let viewSizePoints = viewSizePointsOptional else {
+//                return
+//            }
+
+//            guard let imageView = self.imageView else {
+//                return
+//            }
+//            let viewSizePoints = imageView.frame.size
+//            Logger.error("viewSizePoints: \(viewSizePoints)")
+//            let srcCropSizePoints = CGSize(width:unitDefaultCropSizePoints.width / imageScale,
+//                                           height:unitDefaultCropSizePoints.height / imageScale)
+//            Logger.error("srcCropSizePoints: \(srcCropSizePoints)")
+//            
+//            let srcToViewRatio = viewSizePoints.width / srcCropSizePoints.width
+
+            let location =
+                sender.location(in: sender.view)
+            let scaleDiff = sender.scale / lastPinchScale
+            Logger.error("scaling \(lastPinchScale) \(sender.scale) -> \(scaleDiff)")
+
+//            unitDefaultCropSizePoints = defaultCropSizePoints(dstSizePoints:unitSquareSize)
+//            //        unitDefaultCropFramePoints = defaultCropFramePoints(dstSizePoints:unitSquareSize)
+//            assert(imageSizePoints.width >= unitDefaultCropSizePoints.width)
+//            assert(imageSizePoints.height >= unitDefaultCropSizePoints.height)
+
+            //        maxUnitTranslation = CGPoint(x:
+//            Logger.error("unitDefaultCropSizePoints: \(unitDefaultCropSizePoints)")
+//            srcTranslation = CGPoint(x:(imageSizePoints.width - unitDefaultCropSizePoints.width) * 0.5,
+//                                     y:(imageSizePoints.height - unitDefaultCropSizePoints.height) * 0.5)
+
+            // Update the scaling
+            let srcCropSizeBeforeScalePoints = CGSize(width:unitDefaultCropSizePoints.width / imageScale,
+                                                      height:unitDefaultCropSizePoints.height / imageScale)
+            imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale * scaleDiff))
+            let srcCropSizeAfterScalePoints = CGSize(width:unitDefaultCropSizePoints.width / imageScale,
+                                                      height:unitDefaultCropSizePoints.height / imageScale)
+            // Since the translation state reflects the "upper left" corner of the crop region, we need to
+            // adjust the translation when scaling.
+            srcTranslation.x += (srcCropSizeBeforeScalePoints.width - srcCropSizeAfterScalePoints.width) * 0.5
+            srcTranslation.y += (srcCropSizeBeforeScalePoints.height - srcCropSizeAfterScalePoints.height) * 0.5
+
+            // Update translation
+
+            let viewSizePoints = imageView.frame.size
+            Logger.error("viewSizePoints: \(viewSizePoints)")
+            let srcCropSizePoints = CGSize(width:unitDefaultCropSizePoints.width / imageScale,
+                                           height:unitDefaultCropSizePoints.height / imageScale)
+            Logger.error("srcCropSizePoints: \(srcCropSizePoints)")
+
+            let srcToViewRatio = viewSizePoints.width / srcCropSizePoints.width
+            Logger.error("srcToViewRatio: \(srcToViewRatio)")
+            let viewToSrcRatio = 1 / srcToViewRatio
+            Logger.error("viewToSrcRatio: \(viewToSrcRatio)")
+
+            let gestureTranslation = CGPoint(x:location.x - lastPinchLocation.x,
+                                            y:location.y - lastPinchLocation.y)
+
+            Logger.error("gestureTranslation: \(gestureTranslation)")
+
+            //            var cropFramePoints = currentCropFramePointsAtPanStart
+            //            cropFramePoints.origin.x += +gestureTranslation.x / viewSizePoints.width * currentCropFramePointsAtPanStart.width
+            //            cropFramePoints.origin.y += -gestureTranslation.y / viewSizePoints.height * currentCropFramePointsAtPanStart.height
+            //            self.currentCropFramePoints = cropFramePoints
+
+            srcTranslation = CGPoint(x:srcTranslation.x + gestureTranslation.x * -viewToSrcRatio,
+                                     y:srcTranslation.y + gestureTranslation.y * -viewToSrcRatio)
+
+//            let translationOffset = CGPoint(x:location.x - lastPinchLocation.x,
+//                                             y:location.y - lastPinchLocation.y)
+//            let oldCropFramePoints = self.currentCropFramePoints!
+//            var newCropFramePoints = oldCropFramePoints
+//            newCropFramePoints.size.width /= scaleDiff
+//            newCropFramePoints.size.height /= scaleDiff
+//            newCropFramePoints.origin.x += (oldCropFramePoints.size.width - newCropFramePoints.size.width) * 0.5
+//            newCropFramePoints.origin.y += (oldCropFramePoints.size.height - newCropFramePoints.size.height) * 0.5
+////            cropFramePoints.origin.y += -gestureTranslation.y / viewSizePoints.height * currentCropFramePointsAtPinchStart.height
+////            cropFramePoints.origin.x += +gestureTranslation.x / viewSizePoints.width * currentCropFramePointsAtPinchStart.width
+////            cropFramePoints.origin.y += -gestureTranslation.y / viewSizePoints.height * currentCropFramePointsAtPinchStart.height
+//            self.currentCropFramePoints = newCropFramePoints
+
+            lastPinchLocation = location
+            lastPinchScale = sender.scale
+
+//            if sender.state == .ended {
+//                isPinching = false
+//            }
+            break
+        case .cancelled, .failed:
+            srcTranslation = srcTranslationAtPinchStart
+            imageScale = imageScaleAtPinchStart
+//            guard isPinching else {
+//                return
+//            }
+//            currentCropFramePoints
+//                = currentCropFramePointsAtPinchStart
+//            isPinching = false
+            break
+        }
+
+        updateImageLayout()
+    }
+
+    var srcTranslationAtPanStart: CGPoint = CGPoint.zero
+
+    func handlePan(sender: UIPanGestureRecognizer) {
+        switch (sender.state) {
+        case .possible:
+            break
+        case .began:
+            srcTranslationAtPanStart = srcTranslation
+            break
+        case .changed, .ended:
+            guard let imageView = self.imageView else {
+                return
+            }
+            let viewSizePoints = imageView.frame.size
+            Logger.error("viewSizePoints: \(viewSizePoints)")
+            let srcCropSizePoints = CGSize(width:unitDefaultCropSizePoints.width / imageScale,
+                                           height:unitDefaultCropSizePoints.height / imageScale)
+            Logger.error("srcCropSizePoints: \(srcCropSizePoints)")
+
+            let srcToViewRatio = viewSizePoints.width / srcCropSizePoints.width
+            Logger.error("srcToViewRatio: \(srcToViewRatio)")
+            let viewToSrcRatio = 1 / srcToViewRatio
+            Logger.error("viewToSrcRatio: \(viewToSrcRatio)")
+
+            let gestureTranslation =
+                sender.translation(in: sender.view)
+
+            Logger.error("gestureTranslation: \(gestureTranslation)")
+
+//            var cropFramePoints = currentCropFramePointsAtPanStart
+//            cropFramePoints.origin.x += +gestureTranslation.x / viewSizePoints.width * currentCropFramePointsAtPanStart.width
+//            cropFramePoints.origin.y += -gestureTranslation.y / viewSizePoints.height * currentCropFramePointsAtPanStart.height
+//            self.currentCropFramePoints = cropFramePoints
+
+            srcTranslation = CGPoint(x:srcTranslationAtPanStart.x + gestureTranslation.x * -viewToSrcRatio,
+                                     y:srcTranslationAtPanStart.y + gestureTranslation.y * -viewToSrcRatio)
+            break
+        case .cancelled, .failed:
+            srcTranslation
+                = srcTranslationAtPanStart
+            break
+        }
+
+        updateImageLayout()
+    }
+
+    private func createButtonRow(contentView: UIView) {
+        let buttonTopMargin = ScaleFromIPhone5To7Plus(30, 40)
+        let buttonBottomMargin = ScaleFromIPhone5To7Plus(25, 40)
+
+        let buttonRow = UIView()
+        self.view.addSubview(buttonRow)
+        buttonRow.autoPinWidthToSuperview()
+        buttonRow.autoPinEdge(toSuperviewEdge:.bottom, withInset:buttonBottomMargin)
+        buttonRow.autoPinEdge(.top, to:.bottom, of:contentView, withOffset:buttonTopMargin)
+
+        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()
+    }
+
+    private func createButton(title: String, color: UIColor, 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)
+
+        let button = UIButton()
+        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)
+        return button
+    }
+
+    // MARK: - Event Handlers
+
+    func cancelPressed(sender: UIButton) {
+        dismiss(animated: true, completion:nil)
+    }
+
+    func donePressed(sender: UIButton) {
+        let successCompletion = self.successCompletion
+        dismiss(animated: true, completion: {
+            // TODO
+            let dstImage = self.srcImage
+            successCompletion?(dstImage)
+        })
+    }
+}
diff --git a/Signal/src/ViewControllers/SignalsViewController.m b/Signal/src/ViewControllers/SignalsViewController.m
index 86d63a7be..e5ccbbcdc 100644
--- a/Signal/src/ViewControllers/SignalsViewController.m
+++ b/Signal/src/ViewControllers/SignalsViewController.m
@@ -271,6 +271,17 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
     }
 
     [self updateBarButtonItems];
+
+    dispatch_async(dispatch_get_main_queue(), ^{
+        UIImage *srcDmage = [UIImage imageNamed:@"IMG_4187.PNG"];
+        OWSAssert(srcDmage);
+        CropScaleImageViewController *vc =
+            [[CropScaleImageViewController alloc] initWithSrcImage:srcDmage
+                                                 successCompletion:^(UIImage *_Nonnull dstImage){
+                                                 }];
+        OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:vc];
+        [self presentTopLevelModalViewController:navigationController animateDismissal:NO animatePresentation:YES];
+    });
 }
 
 - (void)updateBarButtonItems {
diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings
index dc0785757..8cdef9d5b 100644
--- a/Signal/translations/en.lproj/Localizable.strings
+++ b/Signal/translations/en.lproj/Localizable.strings
@@ -205,6 +205,9 @@
 /* Title format for action sheet that offers to block an unknown user.Embeds {{the unknown user's name or phone number}}. */
 "BLOCK_OFFER_ACTIONSHEET_TITLE_FORMAT" = "Block %@?";
 
+/* Label for generic done button. */
+"BUTTON_DONE" = "Done";
+
 /* Alert message when calling and permissions for microphone are missing */
 "CALL_AUDIO_PERMISSION_MESSAGE" = "Signal requires access to your microphone to make calls and record voice messages. You can grant this permission in the Settings app.";
 
@@ -385,6 +388,9 @@
 /* Accessibility label for the create group new group button */
 "CREATE_NEW_GROUP" = "Create new group";
 
+/* Title for the 'crop/scale image' dialog. */
+"CROP_SCALE_IMAGE_VIEW_TITLE" = "Crop Image";
+
 /* Subtitle shown while the app is updating its database. */
 "DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes.";