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

Replace camera implementation with something more standard and less limited #2620

Merged
merged 14 commits into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from 11 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
30 changes: 29 additions & 1 deletion Riot.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,11 @@
B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */; };
B1B9DEF422EB426D0065E677 /* ReactionHistoryViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */; };
B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */; };
B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */; };
B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */; };
B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */; };
B1C3360322F1ED600021BA8D /* MediaPickerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C3360022F1ED600021BA8D /* MediaPickerCoordinator.swift */; };
B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C3361B22F32B4A0021BA8D /* SingleImagePickerPresenter.swift */; };
B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */; };
B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562CB228AB3510037F12A /* UIStackView.swift */; };
B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */; };
Expand Down Expand Up @@ -1274,6 +1279,11 @@
B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewData.swift; sourceTree = "<group>"; };
B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewCell.swift; sourceTree = "<group>"; };
B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReactionHistoryViewCell.xib; sourceTree = "<group>"; };
B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPresenter.swift; sourceTree = "<group>"; };
B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorType.swift; sourceTree = "<group>"; };
B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
B1C3360022F1ED600021BA8D /* MediaPickerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinator.swift; sourceTree = "<group>"; };
B1C3361B22F32B4A0021BA8D /* SingleImagePickerPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleImagePickerPresenter.swift; sourceTree = "<group>"; };
B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = "<group>"; };
B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = "<group>"; };
B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2187,6 +2197,7 @@
B1B556DC20EE6C4C00210D55 /* Call */,
B1B5568720EE6C4C00210D55 /* Contacts */,
B1B556ED20EE6C4C00210D55 /* MediaPicker */,
B1C3361A22F328AE0021BA8D /* Camera */,
B1B556E420EE6C4C00210D55 /* UserDevices */,
B1B5596B20EFA85C00210D55 /* EncryptionInfo */,
B1B556FD20EE6C4C00210D55 /* RoomKeyRequest */,
Expand Down Expand Up @@ -2570,11 +2581,15 @@
B1B556ED20EE6C4C00210D55 /* MediaPicker */ = {
isa = PBXGroup;
children = (
B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */,
B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */,
B1C3360022F1ED600021BA8D /* MediaPickerCoordinator.swift */,
B1B556F320EE6C4C00210D55 /* MediaPickerViewController.h */,
B1B556EF20EE6C4C00210D55 /* MediaPickerViewController.m */,
B1B556F220EE6C4C00210D55 /* MediaPickerViewController.xib */,
B1B557CD20EF5E3500210D55 /* Views */,
B1B5577720EE724200210D55 /* Library */,
B1B557CD20EF5E3500210D55 /* Views */,
B1C3361B22F32B4A0021BA8D /* SingleImagePickerPresenter.swift */,
);
path = MediaPicker;
sourceTree = "<group>";
Expand Down Expand Up @@ -3306,6 +3321,14 @@
path = ReactionHistory;
sourceTree = "<group>";
};
B1C3361A22F328AE0021BA8D /* Camera */ = {
isa = PBXGroup;
children = (
B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */,
);
path = Camera;
sourceTree = "<group>";
};
B1C562D7228C0B4C0037F12A /* ContextualMenu */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4138,6 +4161,7 @@
B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */,
B1057789221304EC00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift in Sources */,
B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */,
B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */,
3232ABB72257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift in Sources */,
32F6B96D2270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift in Sources */,
B16932B120F3AC9200746532 /* RoomSearchDataSource.m in Sources */,
Expand Down Expand Up @@ -4176,6 +4200,7 @@
B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */,
B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */,
B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */,
B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */,
B1B558CB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
B169330B20F3CA3A00746532 /* Contact.m in Sources */,
Expand Down Expand Up @@ -4216,6 +4241,7 @@
B1B9DEEB22EB34EF0065E677 /* ReactionHistoryViewModel.swift in Sources */,
B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */,
B1B558E920EF768F00210D55 /* RoomSelectedStickerBubbleCell.m in Sources */,
B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */,
B1B558DF20EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
F083BE041E7009ED00A9B29C /* Tools.m in Sources */,
3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */,
Expand Down Expand Up @@ -4387,6 +4413,7 @@
B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */,
B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */,
3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */,
B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */,
B1B558C820EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.m in Sources */,
B1B557C620EF5CD400210D55 /* DirectoryServerDetailTableViewCell.m in Sources */,
B1B5590920EF768F00210D55 /* RoomEmptyBubbleCell.m in Sources */,
Expand All @@ -4413,6 +4440,7 @@
B14F143222144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinator.swift in Sources */,
B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */,
B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */,
B1C3360322F1ED600021BA8D /* MediaPickerCoordinator.swift in Sources */,
B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */,
B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */,
B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */,
Expand Down
8 changes: 8 additions & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@
"room_event_action_reaction_history" = "Reaction history";
"room_warning_about_encryption" = "End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption.";
"room_event_failed_to_send" = "Failed to send";
"room_action_camera" = "Take photo or video";
"room_action_send_photo_or_video" = "Send photo or video";
"room_action_send_sticker" = "Send sticker";
"room_action_send_file" = "Send file";
Expand Down Expand Up @@ -574,9 +575,14 @@
"receipt_status_read" = "Read: ";

// Media picker
"media_picker_title" = "Media library";
"media_picker_library" = "Library";
"media_picker_select" = "Select";

// Image picker
"image_picker_action_camera" = "Take photo";
"image_picker_action_library" = "Choose from library";

// Directory
"directory_title" = "Directory";
"directory_server_picker_title" = "Select a directory";
Expand Down Expand Up @@ -607,6 +613,8 @@
"rage_shake_prompt" = "You seem to be shaking the phone in frustration. Would you like to submit a bug report?";
"do_not_ask_again" = "Do not ask again";
"camera_access_not_granted" = "%@ doesn't have permission to use Camera, please change privacy settings";
"camera_unavailable" = "The camera is unavailable on your device";
"photo_library_access_not_granted" = "%@ doesn't have permission to access photo library, please change privacy settings";
"large_badge_value_k_format" = "%.1fK";
"room_does_not_exist" = "%@ does not exist";

Expand Down
24 changes: 24 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ internal enum VectorL10n {
internal static func cameraAccessNotGranted(_ p1: String) -> String {
return VectorL10n.tr("Vector", "camera_access_not_granted", p1)
}
/// The camera is unavailable on your device
internal static var cameraUnavailable: String {
return VectorL10n.tr("Vector", "camera_unavailable")
}
/// Cancel
internal static var cancel: String {
return VectorL10n.tr("Vector", "cancel")
Expand Down Expand Up @@ -1110,6 +1114,14 @@ internal enum VectorL10n {
internal static var homeserverConnectionLost: String {
return VectorL10n.tr("Vector", "homeserver_connection_lost")
}
/// Take photo
internal static var imagePickerActionCamera: String {
return VectorL10n.tr("Vector", "image_picker_action_camera")
}
/// Choose from library
internal static var imagePickerActionLibrary: String {
return VectorL10n.tr("Vector", "image_picker_action_library")
}
/// Invite
internal static var invite: String {
return VectorL10n.tr("Vector", "invite")
Expand Down Expand Up @@ -1354,6 +1366,10 @@ internal enum VectorL10n {
internal static var mediaPickerSelect: String {
return VectorL10n.tr("Vector", "media_picker_select")
}
/// Media library
internal static var mediaPickerTitle: String {
return VectorL10n.tr("Vector", "media_picker_title")
}
/// The Internet connection appears to be offline.
internal static var networkOfflinePrompt: String {
return VectorL10n.tr("Vector", "network_offline_prompt")
Expand Down Expand Up @@ -1394,6 +1410,10 @@ internal enum VectorL10n {
internal static var peopleNoConversation: String {
return VectorL10n.tr("Vector", "people_no_conversation")
}
/// %@ doesn't have permission to access photo library, please change privacy settings
internal static func photoLibraryAccessNotGranted(_ p1: String) -> String {
return VectorL10n.tr("Vector", "photo_library_access_not_granted", p1)
}
/// Preview
internal static var preview: String {
return VectorL10n.tr("Vector", "preview")
Expand Down Expand Up @@ -1438,6 +1458,10 @@ internal enum VectorL10n {
internal static var retry: String {
return VectorL10n.tr("Vector", "retry")
}
/// Take photo or video
internal static var roomActionCamera: String {
return VectorL10n.tr("Vector", "room_action_camera")
}
/// Reply
internal static var roomActionReply: String {
return VectorL10n.tr("Vector", "room_action_reply")
Expand Down
187 changes: 187 additions & 0 deletions Riot/Modules/Camera/CameraPresenter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import Foundation
import UIKit
import AVFoundation

@objc protocol CameraPresenterDelegate: class {
func cameraPresenter(_ presenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?)
func cameraPresenter(_ presenter: CameraPresenter, didSelectVideoAt url: URL)
func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter)
}

/// CameraPresenter enables to present native camera
@objc final class CameraPresenter: NSObject {

// MARK: - Constants

private enum Constants {
static let jpegCompressionQuality: CGFloat = 1.0
}

// MARK: - Properties

// MARK: - Private

private weak var presentingViewController: UIViewController?
private weak var cameraViewController: UIViewController?
private var mediaUTIs: [MXKUTI] = []

// MARK: - Public

@objc weak var delegate: CameraPresenterDelegate?

// MARK: - Public

@objc func presentCamera(from presentingViewController: UIViewController, with mediaUTIs: [MXKUTI], animated: Bool) {
self.presentingViewController = presentingViewController
self.mediaUTIs = mediaUTIs
self.checkCameraPermissionAndPresentCamera(animated: animated)
}

@objc func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let cameraViewController = self.cameraViewController else {
return
}
cameraViewController.dismiss(animated: animated, completion: completion)
}

// MARK: - Private

private func checkCameraPermissionAndPresentCamera(animated: Bool) {

let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)

switch authorizationStatus {
case .authorized:
self.presentCameraController(animated: animated)
case .notDetermined:
self.requestCameraAccess(completion: { (granted) in
if granted {
self.presentCameraController(animated: animated)
} else {
self.presentPermissionDeniedAlert()
}
})
case .denied, .restricted:
self.presentPermissionDeniedAlert()
@unknown default:
break
}
}

private func presentCameraController(animated: Bool) {
guard let presentingViewController = self.presentingViewController else {
return
}

guard let cameraViewController = self.buildCameraViewController() else {
return
}

presentingViewController.present(cameraViewController, animated: true, completion: nil)
self.cameraViewController = cameraViewController
}

private func buildCameraViewController() -> UIViewController? {
guard UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) else {
return nil
}

let mediaTypes = self.mediaUTIs.map { (uti) -> String in
return uti.rawValue
}

let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.sourceType = UIImagePickerController.SourceType.camera
imagePickerController.mediaTypes = mediaTypes
imagePickerController.allowsEditing = false

return imagePickerController
}

private func requestCameraAccess(completion: @escaping (_ granted: Bool) -> Void) {
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
completion(granted)
}
}
}

private func presentPermissionDeniedAlert() {
guard let presentingViewController = self.presentingViewController, let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
return
}

let appDisplayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? ""

let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraAccessNotGranted(appDisplayName), preferredStyle: .alert)

let cancelActionTitle = Bundle.mxk_localizedString(forKey: "ok")
let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: { _ in
})

let settingsActionTitle = Bundle.mxk_localizedString(forKey: "settings")
let settingsAction = UIAlertAction(title: settingsActionTitle, style: .default, handler: { _ in
UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (succeed) in
if !succeed {
print("[CameraPresenter] Fails to open settings")
}
})
})

alert.addAction(cancelAction)
alert.addAction(settingsAction)

presentingViewController.present(alert, animated: true, completion: nil)
}

private func presentCameraUnavailableAlert() {
guard let presentingViewController = self.presentingViewController else {
return
}

let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraUnavailable, preferredStyle: .alert)

let okAction = UIAlertAction(title: VectorL10n.accept, style: .default, handler: nil)

alert.addAction(okAction)

presentingViewController.present(alert, animated: true, completion: nil)
}
}

// MARK: - UIImagePickerControllerDelegate
extension CameraPresenter: UIImagePickerControllerDelegate {

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let videoURL = info[.mediaURL] as? URL {
self.delegate?.cameraPresenter(self, didSelectVideoAt: videoURL)
} else if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage, let imageData = image.jpegData(compressionQuality: Constants.jpegCompressionQuality) {
self.delegate?.cameraPresenter(self, didSelectImageData: imageData, withUTI: MXKUTI.jpeg)
}
}

func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.delegate?.cameraPresenterDidCancel(self)
}
}

// MARK: - UINavigationControllerDelegate
extension CameraPresenter: UINavigationControllerDelegate {
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
/**
The delegate for the view controller.
*/
@property (nonatomic) id<MediaAlbumContentViewControllerDelegate> delegate;
@property (nonatomic, weak) id<MediaAlbumContentViewControllerDelegate> delegate;

/**
The array of the media types listed by the view controller (default value is an array containing kUTTypeImage).
Expand Down
Loading