Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplified permission flow #133

Merged
merged 21 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 80 additions & 165 deletions Classes/Camera/CameraPermissionsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ protocol CaptureDeviceAuthorizing: class {

protocol CameraPermissionsViewDelegate: class {

func cameraAccessButtonPressed()

func microphoneAccessButtonPressed()
func requestCameraAccess()

func requestMicrophoneAccess()

func openAppSettings()

func mediaPickerButtonPressed()

Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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
}()

Expand Down Expand Up @@ -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() {
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -353,43 +268,41 @@ 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
performUIUpdate {
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
performUIUpdate {
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 {
Expand Down Expand Up @@ -423,29 +336,31 @@ class CameraPermissionsViewController: UIViewController, CameraPermissionsViewDe
}
}

private func openAppSettings() {
delegate?.openAppSettings(completion: nil)
}

private func setupViewFromAccessAndNotifyPermissionsChanged() {
setupViewFromAccess()
delegate?.cameraPermissionsChanged(hasFullAccess: self.hasFullAccess())
}

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()
}
}

Expand Down
Loading