diff --git a/Classes/Camera/CameraPermissionsViewController.swift b/Classes/Camera/CameraPermissionsViewController.swift index 04f90cc0b..7402da8c9 100644 --- a/Classes/Camera/CameraPermissionsViewController.swift +++ b/Classes/Camera/CameraPermissionsViewController.swift @@ -18,9 +18,11 @@ protocol CaptureDeviceAuthorizing: class { protocol CameraPermissionsViewDelegate: class { - func cameraAccessButtonPressed() - - func microphoneAccessButtonPressed() + func requestCameraAccess() + + func requestMicrophoneAccess() + + func openAppSettings() func mediaPickerButtonPressed() @@ -65,9 +67,17 @@ class CameraPermissionsView: UIView, CameraPermissionsViewable, MediaPickerButto private lazy var containerView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false - view.backgroundColor = UIColor.black.withAlphaComponent(0.75) + view.backgroundColor = UIColor.black return view }() + + private lazy var contentStack: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [titleLabel, descriptionLabel, settingsButton]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.distribution = .equalSpacing + return stackView + }() private lazy var titleLabel: UILabel = { let label = UILabel() @@ -76,20 +86,14 @@ class CameraPermissionsView: UIView, CameraPermissionsViewable, MediaPickerButto label.font = Constants.titleFont label.textColor = Constants.textColor label.textAlignment = .center + label.numberOfLines = 0 return label }() private lazy var descriptionLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false - - let description = KanvasStrings.shared.cameraPermissionsDescriptionLabel - let descriptionParagraphStyle = NSMutableParagraphStyle() - descriptionParagraphStyle.lineSpacing = Constants.descriptionFont.pointSize * 0.5 - let descriptionAttributedString = NSMutableAttributedString(string: description) - descriptionAttributedString.addAttribute(.paragraphStyle, value: descriptionParagraphStyle, range: NSMakeRange(0, descriptionAttributedString.length)) - - label.attributedText = descriptionAttributedString + label.text = KanvasStrings.shared.cameraPermissionsDescriptionLabel label.font = Constants.descriptionFont label.textColor = Constants.textColor label.alpha = Constants.descriptionOpacity @@ -98,30 +102,12 @@ class CameraPermissionsView: UIView, CameraPermissionsViewable, MediaPickerButto return label }() - private lazy var cameraAccessButton: UIButton = { - let title = NSLocalizedString("Allow access to camera", comment: "Button on camera permissions screen to initiate the sytem prompt for camera access") - let titleDisabled = NSLocalizedString("Camera access granted", comment: "Label on camera permissions screen to indicate camera access is granted") + private lazy var settingsButton: UIButton = { + let title = NSLocalizedString("PhotoAccessNoAccessAction", comment: "PhotoAccessNoAccessAction") + let titleDisabled = NSLocalizedString("PhotoAccessNoAccessAction", comment: "PhotoAccessNoAccessAction") let button = CameraPermissionsView.makeButton(title: title, titleDisabled: titleDisabled) button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(cameraAccessButtonPressed), for: .touchUpInside) - return button - }() - - private lazy var microphoneAccessButton: UIButton = { - let title = NSLocalizedString("Allow access to microphone", comment: "Button on camera permissions screen to initiate the sytem prompt for microphone access.") - let titleDisabled = NSLocalizedString("Microphone access granted", comment: "Label on camera permissions screen to indicate microphone access is granted") - let button = CameraPermissionsView.makeButton(title: title, titleDisabled: titleDisabled) - button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(microphoneAccessButtonPressed), for: .touchUpInside) - return button - }() - - private lazy var mediaPickerButton: MediaPickerButtonView = { - let settings = CameraSettings() - settings.features.mediaPicking = showMediaPicker - let button = MediaPickerButtonView(settings: settings) - button.translatesAutoresizingMaskIntoConstraints = false - button.delegate = self + button.addTarget(self, action: #selector(openAppSettings), for: .touchUpInside) return button }() @@ -151,30 +137,17 @@ class CameraPermissionsView: UIView, CameraPermissionsViewable, MediaPickerButto fatalError("init(coder:) has not been implemented") } - func updateCameraAccess(hasAccess: Bool) { - cameraAccessButton.isEnabled = !hasAccess - CameraPermissionsView.updateButton(button: cameraAccessButton) - } + func updateCameraAccess(hasAccess: Bool) {} - func updateMicrophoneAccess(hasAccess: Bool) { - microphoneAccessButton.isEnabled = !hasAccess - CameraPermissionsView.updateButton(button: microphoneAccessButton) - } + func updateMicrophoneAccess(hasAccess: Bool) {} private func setupView() { addSubview(containerView) - addSubview(titleLabel) - addSubview(descriptionLabel) - addSubview(cameraAccessButton) - addSubview(microphoneAccessButton) - addSubview(mediaPickerButton) + addSubview(contentStack) setupContainerView() - setupTitleView() - setupDescriptionView() - setupCameraAccessButton() - setupMicrophoneAccessButton() - setupMediaPickerButton() + setupContentStack() + setupSettingsButton() } private func setupContainerView() { @@ -185,60 +158,23 @@ class CameraPermissionsView: UIView, CameraPermissionsViewable, MediaPickerButto containerView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } - - private func setupTitleView() { - NSLayoutConstraint.activate([ - titleLabel.bottomAnchor.constraint(equalTo: descriptionLabel.topAnchor, constant: -15), - titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor), - titleLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.60) - ]) - } - - private func setupDescriptionView() { - NSLayoutConstraint.activate([ - descriptionLabel.bottomAnchor.constraint(equalTo: centerYAnchor, constant: -25), - descriptionLabel.centerXAnchor.constraint(equalTo: titleLabel.centerXAnchor), - descriptionLabel.widthAnchor.constraint(equalTo: titleLabel.widthAnchor) - ]) - } - - private func setupCameraAccessButton() { - NSLayoutConstraint.activate([ - cameraAccessButton.topAnchor.constraint(equalTo: centerYAnchor), - cameraAccessButton.centerXAnchor.constraint(equalTo: descriptionLabel.centerXAnchor), - ]) - cameraAccessButton.layoutIfNeeded() - CameraPermissionsView.updateButton(button: cameraAccessButton) - } - - private func setupMicrophoneAccessButton() { + + private func setupContentStack() { NSLayoutConstraint.activate([ - microphoneAccessButton.topAnchor.constraint(equalTo: cameraAccessButton.bottomAnchor, constant: 15), - microphoneAccessButton.centerXAnchor.constraint(equalTo: cameraAccessButton.centerXAnchor), + contentStack.heightAnchor.constraint(equalToConstant: 250), + contentStack.centerYAnchor.constraint(equalTo: safeLayoutGuide.centerYAnchor), + contentStack.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), + readableContentGuide.trailingAnchor.constraint(equalTo: contentStack.trailingAnchor) ]) - microphoneAccessButton.layoutIfNeeded() - CameraPermissionsView.updateButton(button: microphoneAccessButton) } - private func setupMediaPickerButton() { - let guide = UILayoutGuide() - addLayoutGuide(guide) - let bottomMargin: CGFloat = deviceDependentBottomMargin() - NSLayoutConstraint.activate([ - guide.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -bottomMargin), - guide.heightAnchor.constraint(equalToConstant: 100), - guide.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - guide.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor, constant: -50), - ]) - NSLayoutConstraint.activate([ - mediaPickerButton.centerXAnchor.constraint(equalTo: guide.centerXAnchor), - mediaPickerButton.centerYAnchor.constraint(equalTo: guide.centerYAnchor), - mediaPickerButton.widthAnchor.constraint(equalToConstant: 35), - mediaPickerButton.heightAnchor.constraint(equalTo: mediaPickerButton.widthAnchor), - ]) + private func setupSettingsButton() { + settingsButton.layer.cornerRadius = settingsButton.bounds.height / 2.0 + settingsButton.backgroundColor = .clear + settingsButton.layer.borderColor = Constants.buttonColor.cgColor + settingsButton.heightAnchor.constraint(equalToConstant: 50).isActive = true } - private func deviceDependentBottomMargin() -> CGFloat { guard Device.belongsToIPhoneXGroup == true else { return CGFloat(floatLiteral: 96.0) @@ -262,41 +198,17 @@ class CameraPermissionsView: UIView, CameraPermissionsViewable, MediaPickerButto return button } - private static func updateButton(button: UIButton) { - button.layer.cornerRadius = button.bounds.height / 2.0 - let verticalInset: CGFloat = 4.5 - button.imageEdgeInsets = UIEdgeInsets(top: verticalInset, left: button.bounds.height / -4.0, bottom: verticalInset, right: 0.0) - button.contentEdgeInsets = UIEdgeInsets( - top: button.bounds.height / 5.0, - left: button.bounds.height / 2.0, - bottom: button.bounds.height / 5.0, - right: button.bounds.height / 2.0) - if button.isEnabled { - button.backgroundColor = .clear - button.layer.borderColor = Constants.buttonColor.cgColor - } - else { - button.backgroundColor = Constants.buttonAcceptedBackgroundColor - button.layer.borderColor = Constants.buttonAcceptedBackgroundColor.cgColor - } - } - @objc private func cameraAccessButtonPressed() { - delegate?.cameraAccessButtonPressed() + delegate?.requestCameraAccess() } - - @objc private func microphoneAccessButtonPressed() { - delegate?.microphoneAccessButtonPressed() - } - - func mediaPickerButtonDidPress() { - delegate?.mediaPickerButtonPressed() + + @objc private func openAppSettings() { + delegate?.openAppSettings() } - func resetMediaPickerButton() { - mediaPickerButton.reset() - } + func mediaPickerButtonDidPress() {} + func resetMediaPickerButton() {} } class CaptureDeviceAuthorizer: CaptureDeviceAuthorizing { @@ -316,17 +228,20 @@ class CameraPermissionsViewController: UIViewController, CameraPermissionsViewDe let captureDeviceAuthorizer: CaptureDeviceAuthorizing let shouldShowMediaPicker: Bool - + + var isViewBlockingCameraAccess: Bool { !isIgnoringTouches } + weak var delegate: CameraPermissionsViewControllerDelegate? private var permissionsView: CameraPermissionsViewable? { return view as? CameraPermissionsViewable } - - private var ignoreTouchesView: IgnoreTouchesView? { - return view as? IgnoreTouchesView + + private var isIgnoringTouches: Bool { + return view is IgnoreTouchesView } + init(shouldShowMediaPicker: Bool, captureDeviceAuthorizer: CaptureDeviceAuthorizing) { self.captureDeviceAuthorizer = captureDeviceAuthorizer self.shouldShowMediaPicker = shouldShowMediaPicker @@ -353,9 +268,13 @@ class CameraPermissionsViewController: UIViewController, CameraPermissionsViewDe super.viewWillAppear(animated) setupViewFromAccess() + if hasFullAccess() { return } + + requestCameraAccess() + requestMicrophoneAccess() } - - func cameraAccessButtonPressed() { + + func requestCameraAccess() { switch captureDeviceAuthorizer.authorizationStatus(for: .video) { case .notDetermined: captureDeviceAuthorizer.requestAccess(for: .video) { videoGranted in @@ -363,17 +282,12 @@ class CameraPermissionsViewController: UIViewController, CameraPermissionsViewDe self.setupViewFromAccessAndNotifyPermissionsChanged() } } - case .restricted, .denied: - openAppSettings() - case .authorized: - assertionFailure("How was this button pressed if we're already authorized!?") - self.setupViewFromAccessAndNotifyPermissionsChanged() - @unknown default: - assertionFailure() + case .restricted, .denied, .authorized: + return } } - func microphoneAccessButtonPressed() { + func requestMicrophoneAccess() { switch captureDeviceAuthorizer.authorizationStatus(for: .audio) { case .notDetermined: captureDeviceAuthorizer.requestAccess(for: .audio) { audioGranted in @@ -381,15 +295,14 @@ class CameraPermissionsViewController: UIViewController, CameraPermissionsViewDe self.setupViewFromAccessAndNotifyPermissionsChanged() } } - case .restricted, .denied: - openAppSettings() - case .authorized: - assertionFailure("How was this button pressed if we're already authorized!?") - self.setupViewFromAccessAndNotifyPermissionsChanged() - @unknown default: - assertionFailure() + case .restricted, .denied, .authorized: + return } } + + func openAppSettings() { + delegate?.openAppSettings(completion: nil) + } func mediaPickerButtonPressed() { delegate?.didTapMediaPickerButton { @@ -423,10 +336,6 @@ class CameraPermissionsViewController: UIViewController, CameraPermissionsViewDe } } - private func openAppSettings() { - delegate?.openAppSettings(completion: nil) - } - private func setupViewFromAccessAndNotifyPermissionsChanged() { setupViewFromAccess() delegate?.cameraPermissionsChanged(hasFullAccess: self.hasFullAccess()) @@ -434,18 +343,24 @@ class CameraPermissionsViewController: UIViewController, CameraPermissionsViewDe private func setupViewFromAccess() { if hasFullAccess() { - if ignoreTouchesView == nil { - view = IgnoreTouchesView() - } + showIgnoreTouchesView() } else { - if permissionsView == nil { - let view = CameraPermissionsView() - view.delegate = self - self.view = view - } - permissionsView?.updateCameraAccess(hasAccess: hasCameraAccess()) - permissionsView?.updateMicrophoneAccess(hasAccess: hasMicrophoneAccess()) + showPermissionsView() + } + } + + private func showPermissionsView() { + if permissionsView == nil { + let view = CameraPermissionsView() + view.delegate = self + self.view = view + } + } + + private func showIgnoreTouchesView() { + if !isIgnoringTouches { + view = IgnoreTouchesView() } } diff --git a/Classes/Constants/KanvasStrings.swift b/Classes/Constants/KanvasStrings.swift index ff30ba634..c52b3e2aa 100644 --- a/Classes/Constants/KanvasStrings.swift +++ b/Classes/Constants/KanvasStrings.swift @@ -46,10 +46,18 @@ public struct KanvasStrings { public var cameraPermissionsTitleLabel: String public var cameraPermissionsDescriptionLabel: String - public static var shared = KanvasStrings( - cameraPermissionsTitleLabel: NSLocalizedString("Post to Tumblr", comment: "Title of camera permissions screen"), - cameraPermissionsDescriptionLabel: NSLocalizedString("Allow access so you can start taking photos and videos", comment: "Message on camera permissions screen to explain why the Tumblr app needs camera and microphone permissions") - ) + public static var shared = KanvasStrings(cameraPermissionsTitleLabel: cameraPermissionTitleString, + cameraPermissionsDescriptionLabel: cameraPermissionDescriptionString) + + private static let cameraPermissionTitleString = { + NSLocalizedString("Please allow Tumblr access to your Camera and Microphone", + comment: "Title text for scenerio when access to Photos has been disallowed") + }() + + private static let cameraPermissionDescriptionString = { + NSLocalizedString("CameraAccessNoAccessDesc", + comment: "Description text for scenerio when access to Photos has been disallowed") + }() public init(cameraPermissionsTitleLabel: String, cameraPermissionsDescriptionLabel: String) { diff --git a/KanvasExample/KanvasExample.xcodeproj/xcshareddata/xcschemes/KanvasExample.xcscheme b/KanvasExample/KanvasExample.xcodeproj/xcshareddata/xcschemes/KanvasExample.xcscheme index b8ab9420b..54c47c5f1 100644 --- a/KanvasExample/KanvasExample.xcodeproj/xcshareddata/xcschemes/KanvasExample.xcscheme +++ b/KanvasExample/KanvasExample.xcodeproj/xcshareddata/xcschemes/KanvasExample.xcscheme @@ -50,6 +50,9 @@ + + diff --git a/KanvasExample/KanvasExample/en.lproj/Localizable.strings b/KanvasExample/KanvasExample/en.lproj/Localizable.strings index 5dd72c417..dcfc95bb1 100644 --- a/KanvasExample/KanvasExample/en.lproj/Localizable.strings +++ b/KanvasExample/KanvasExample/en.lproj/Localizable.strings @@ -1,12 +1,12 @@ -/* Message on camera permissions screen to explain why the Tumblr app needs camera and microphone permissions */ -"Allow access so you can start taking photos and videos" = "Allow access so you can start taking photos and videos"; - /* Button on camera permissions screen to initiate the sytem prompt for camera access */ "Allow access to camera" = "Allow access to camera"; /* Button on camera permissions screen to initiate the sytem prompt for microphone access */ "Allow access to microphone" = "Allow access to microphone"; +/* Action button text for scenerio when access to Photos has been disallowed */ +"PhotoAccessNoAccessAction" = "Take me to Settings"; + /* Popup message when user discards all their clips */ "Are you sure? If you close this, you'll lose everything you just created." = "Are you sure? If you close this, you'll lose everything you just created."; @@ -55,8 +55,11 @@ /* Message for the post button in the editor screen */ "Post" = "Post"; -/* Title of camera permissions screen */ -"Post to Tumblr" = "Post to Tumblr"; +/* Title text for scenerio when access to Photos has been disallowed */ +"Please allow Tumblr access to your Camera and Microphone" = "Please allow Tumblr access to your Camera and Microphone"; + +/* Description text for scenerio when access to Photos has been disallowed */ +"CameraAccessNoAccessDesc" = "You might want to save your post as a draft first so you don’t lose any unsaved progress."; /* Alert controller message */ "SomethingGoofedTitle" = "SomethingGoofedTitle"; diff --git a/KanvasExample/KanvasExampleTests/Camera/CameraPermissionsTests.swift b/KanvasExample/KanvasExampleTests/Camera/CameraPermissionsTests.swift index 0ac4523ab..df588ca0b 100644 --- a/KanvasExample/KanvasExampleTests/Camera/CameraPermissionsTests.swift +++ b/KanvasExample/KanvasExampleTests/Camera/CameraPermissionsTests.swift @@ -90,9 +90,9 @@ final class CameraPermissionsViewControllerTests: XCTestCase { let delegate = MockCameraPermissionsViewControllerDelegate() let controller = CameraPermissionsViewController(shouldShowMediaPicker: true, captureDeviceAuthorizer: authorizer) controller.delegate = delegate - controller.cameraAccessButtonPressed() + controller.requestCameraAccess() XCTAssertEqual(delegate.cameraPermissionsChangedHasFullAccess, false) - controller.microphoneAccessButtonPressed() + controller.requestMicrophoneAccess() XCTAssertEqual(delegate.cameraPermissionsChangedHasFullAccess, true) } @@ -116,8 +116,8 @@ final class CameraPermissionsViewControllerTests: XCTestCase { let delegate = MockCameraPermissionsViewControllerDelegate() let controller = CameraPermissionsViewController(shouldShowMediaPicker: true, captureDeviceAuthorizer: authorizer) controller.delegate = delegate - controller.cameraAccessButtonPressed() - XCTAssertEqual(delegate.appSettingsOpened, true) + controller.loadViewIfNeeded() + XCTAssertTrue(controller.isViewBlockingCameraAccess) } }