Refactor DeviceVerification prefix classes to KeyVerification where needed. And move key verification related classes into the same module KeyVerification.

This commit is contained in:
SBiOSoftWhare
2020-03-18 17:39:26 +01:00
parent 991e2fe5f7
commit 23a02c362c
91 changed files with 819 additions and 803 deletions
@@ -0,0 +1,325 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart
/*
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 UIKit
@objcMembers
final class KeyVerificationCoordinator: KeyVerificationCoordinatorType {
// MARK: - Properties
// MARK: Private
private let navigationRouter: NavigationRouterType
private let session: MXSession
private let otherUserId: String
private let otherDeviceId: String
private var incomingTransaction: MXIncomingSASTransaction?
private var incomingKeyVerificationRequest: MXKeyVerificationRequest?
private var verificationKind: KeyVerificationKind = .device
private var roomMember: MXRoomMember?
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: KeyVerificationCoordinatorDelegate?
// MARK: - Setup
/// Contrustor to start a verification of another device.
///
/// - Parameters:
/// - session: the MXSession
/// - otherUserId: the device user id
/// - otherDevice: the device id
init(session: MXSession, otherUserId: String, otherDeviceId: String) {
self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
self.session = session
self.otherUserId = otherUserId
self.otherDeviceId = otherDeviceId
}
init(navigationRouter: NavigationRouterType, session: MXSession, userId: String, otherDeviceId: String) {
self.navigationRouter = navigationRouter
self.session = session
self.otherUserId = userId
self.otherDeviceId = otherDeviceId
}
/// Contrustor to manage an incoming SAS device verification transaction
///
/// - Parameters:
/// - session: the MXSession
/// - transaction: an existing device verification transaction
convenience init(session: MXSession, incomingTransaction: MXIncomingSASTransaction) {
self.init(session: session,
otherUserId: incomingTransaction.otherUserId,
otherDeviceId: incomingTransaction.otherDeviceId)
self.incomingTransaction = incomingTransaction
}
/// Contrustor to manage an incoming SAS device verification transaction
///
/// - Parameters:
/// - session: the MXSession
/// - incomingKeyVerificationRequest: An existing incoming key verification request to accept
convenience init(session: MXSession, incomingKeyVerificationRequest: MXKeyVerificationRequest) {
let otherDeviceId = incomingKeyVerificationRequest.otherDevice ?? incomingKeyVerificationRequest.fromDevice
self.init(session: session, otherUserId: incomingKeyVerificationRequest.otherUser, otherDeviceId: otherDeviceId)
self.incomingKeyVerificationRequest = incomingKeyVerificationRequest
}
/// Constructor to start a user verification.
///
/// - Parameters:
/// - session: the MXSession
/// - roomMember: an other room member
init(session: MXSession, roomMember: MXRoomMember) {
self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
self.session = session
self.otherUserId = roomMember.userId
self.otherDeviceId = ""
self.roomMember = roomMember
self.verificationKind = .user
}
// MARK: - Public methods
func start() {
let rootCoordinator: Coordinator & Presentable
if let incomingKeyVerificationRequest = self.incomingKeyVerificationRequest {
rootCoordinator = self.createDataLoadingScreenCoordinator(with: incomingKeyVerificationRequest)
} else if let roomMember = self.roomMember {
rootCoordinator = self.createUserVerificationStartCoordinator(with: roomMember)
} else {
rootCoordinator = self.createDataLoadingScreenCoordinator()
}
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
if self.navigationRouter.modules.isEmpty == false {
self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in
self?.remove(childCoordinator: rootCoordinator)
})
} else {
self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in
self?.remove(childCoordinator: rootCoordinator)
}
}
}
func toPresentable() -> UIViewController {
return self.navigationRouter.toPresentable()
}
// MARK: - Private methods
private func createDataLoadingScreenCoordinator() -> KeyVerificationDataLoadingCoordinator {
let coordinator = KeyVerificationDataLoadingCoordinator(session: self.session, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
coordinator.delegate = self
coordinator.start()
return coordinator
}
private func createDataLoadingScreenCoordinator(with keyVerificationRequest: MXKeyVerificationRequest) -> KeyVerificationDataLoadingCoordinator {
let coordinator = KeyVerificationDataLoadingCoordinator(session: self.session, incomingKeyVerificationRequest: keyVerificationRequest)
coordinator.delegate = self
coordinator.start()
return coordinator
}
private func createUserVerificationStartCoordinator(with roomMember: MXRoomMember) -> UserVerificationStartCoordinator {
let coordinator = UserVerificationStartCoordinator(session: self.session, roomMember: roomMember)
coordinator.delegate = self
coordinator.start()
return coordinator
}
private func showStart(otherUser: MXUser, otherDevice: MXDeviceInfo) {
let coordinator = DeviceVerificationStartCoordinator(session: self.session, otherUser: otherUser, otherDevice: otherDevice)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)
self.navigationRouter.setRootModule(coordinator) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
private func showIncoming(otherUser: MXUser, transaction: MXIncomingSASTransaction) {
let coordinator = DeviceVerificationIncomingCoordinator(session: self.session, otherUser: otherUser, transaction: transaction)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)
self.navigationRouter.setRootModule(coordinator) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
private func showVerifyBySAS(transaction: MXSASTransaction, animated: Bool) {
let coordinator = KeyVerificationVerifyBySASCoordinator(session: self.session, transaction: transaction, verificationKind: self.verificationKind)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)
self.navigationRouter.push(coordinator, animated: animated) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
private func showVerifyByScanning(keyVerificationRequest: MXKeyVerificationRequest, animated: Bool) {
let coordinator = KeyVerificationVerifyByScanningCoordinator(session: self.session, keyVerificationRequest: keyVerificationRequest)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)
self.navigationRouter.push(coordinator, animated: animated) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
private func showVerified(animated: Bool) {
let viewController = KeyVerificationVerifiedViewController.instantiate(with: self.verificationKind)
viewController.delegate = self
self.navigationRouter.setRootModule(viewController)
}
}
extension KeyVerificationCoordinator: KeyVerificationDataLoadingCoordinatorDelegate {
func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) {
self.showVerifyByScanning(keyVerificationRequest: keyVerificationRequest, animated: true)
}
func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) {
if let incomingTransaction = self.incomingTransaction {
self.showIncoming(otherUser: user, transaction: incomingTransaction)
} else {
self.showStart(otherUser: user, otherDevice: device)
}
}
func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXKeyVerificationTransaction) {
if let sasTransaction = transaction as? MXSASTransaction {
self.showVerifyBySAS(transaction: sasTransaction, animated: true)
} else {
NSLog("[KeyVerificationCoordinator] Transaction \(transaction) is not supported")
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
func keyVerificationDataLoadingCoordinatorDidCancel(_ coordinator: KeyVerificationDataLoadingCoordinatorType) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension KeyVerificationCoordinator: DeviceVerificationStartCoordinatorDelegate {
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) {
self.showVerifyBySAS(transaction: transaction, animated: true)
}
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension KeyVerificationCoordinator: DeviceVerificationIncomingCoordinatorDelegate {
func deviceVerificationIncomingCoordinator(_ coordinator: DeviceVerificationIncomingCoordinatorType, didAcceptTransaction transaction: MXSASTransaction) {
self.showVerifyBySAS(transaction: transaction, animated: true)
}
func deviceVerificationIncomingCoordinatorDidCancel(_ coordinator: DeviceVerificationIncomingCoordinatorType) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension KeyVerificationCoordinator: KeyVerificationVerifyBySASCoordinatorDelegate {
func keyVerificationVerifyBySASCoordinatorDidComplete(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) {
self.showVerified(animated: true)
}
func keyVerificationVerifyBySASCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension KeyVerificationCoordinator: KeyVerificationVerifiedViewControllerDelegate {
func keyVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: KeyVerificationVerifiedViewController) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
func keyVerificationVerifiedViewControllerDidCancel(_ viewController: KeyVerificationVerifiedViewController) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension KeyVerificationCoordinator: UserVerificationStartCoordinatorDelegate {
func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) {
self.showVerifyByScanning(keyVerificationRequest: request, animated: true)
}
func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) {
self.showVerifyBySAS(transaction: transaction, animated: true)
}
func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
func userVerificationStartCoordinatorDidCancel(_ coordinator: UserVerificationStartCoordinatorType) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension KeyVerificationCoordinator: KeyVerificationVerifyByScanningCoordinatorDelegate {
func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) {
self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
func keyVerificationVerifyByScanningCoordinatorCannotScan(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) {
self.showVerified(animated: true)
}
func keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) {
self.showVerified(animated: true)
}
func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction) {
self.showVerifyBySAS(transaction: transaction, animated: true)
}
}
@@ -0,0 +1,125 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart
/*
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
@objc protocol KeyVerificationCoordinatorBridgePresenterDelegate {
func keyVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: KeyVerificationCoordinatorBridgePresenter, otherUserId: String, otherDeviceId: String)
}
/// KeyVerificationCoordinatorBridgePresenter enables to start KeyVerificationCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
@objcMembers
final class KeyVerificationCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var coordinator: KeyVerificationCoordinator?
// MARK: Public
weak var delegate: KeyVerificationCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(session: MXSession) {
self.session = session
super.init()
}
// MARK: - Public
// NOTE: Default value feature is not compatible with Objective-C.
// func present(from viewController: UIViewController, animated: Bool) {
// self.present(from: viewController, animated: animated)
// }
func present(from viewController: UIViewController, otherUserId: String, otherDeviceId: String, animated: Bool) {
NSLog("[KeyVerificationCoordinatorBridgePresenter] Present from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
keyVerificationCoordinator.delegate = self
viewController.present(keyVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
keyVerificationCoordinator.start()
self.coordinator = keyVerificationCoordinator
}
func present(from viewController: UIViewController, roomMember: MXRoomMember, animated: Bool) {
NSLog("[KeyVerificationCoordinatorBridgePresenter] Present from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, roomMember: roomMember)
keyVerificationCoordinator.delegate = self
viewController.present(keyVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
keyVerificationCoordinator.start()
self.coordinator = keyVerificationCoordinator
}
func present(from viewController: UIViewController, incomingTransaction: MXIncomingSASTransaction, animated: Bool) {
NSLog("[KeyVerificationCoordinatorBridgePresenter] Present incoming verification from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, incomingTransaction: incomingTransaction)
keyVerificationCoordinator.delegate = self
viewController.present(keyVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
keyVerificationCoordinator.start()
self.coordinator = keyVerificationCoordinator
}
func present(from viewController: UIViewController, incomingKeyVerificationRequest: MXKeyVerificationRequest, animated: Bool) {
NSLog("[KeyVerificationCoordinatorBridgePresenter] Present incoming key verification request from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, incomingKeyVerificationRequest: incomingKeyVerificationRequest)
keyVerificationCoordinator.delegate = self
viewController.present(keyVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
keyVerificationCoordinator.start()
self.coordinator = keyVerificationCoordinator
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
NSLog("[KeyVerificationCoordinatorBridgePresenter] Dismiss")
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil
if let completion = completion {
completion()
}
}
}
}
// MARK: - KeyVerificationCoordinatorDelegate
extension KeyVerificationCoordinatorBridgePresenter: KeyVerificationCoordinatorDelegate {
func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) {
self.delegate?.keyVerificationCoordinatorBridgePresenterDelegateDidComplete(self, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
}
}
@@ -0,0 +1,28 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart
/*
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
protocol KeyVerificationCoordinatorDelegate: class {
func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String)
}
/// `KeyVerificationCoordinatorType` is a protocol describing a Coordinator that handle key verification navigation flow.
protocol KeyVerificationCoordinatorType: Coordinator, Presentable {
var delegate: KeyVerificationCoordinatorDelegate? { get }
}
@@ -0,0 +1,21 @@
/*
Copyright 2020 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
enum KeyVerificationKind {
case device
case user
}
@@ -0,0 +1,40 @@
/*
Copyright 2020 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
final class KeyVerificationService {
private let cameraAccessManager: CameraAccessManager
init() {
self.cameraAccessManager = CameraAccessManager()
}
func supportedKeyVerificationMethods() -> [String] {
var supportedMethods: [String] = [
MXKeyVerificationMethodSAS,
MXKeyVerificationMethodQRCodeShow,
MXKeyVerificationMethodReciprocate
]
if self.cameraAccessManager.isCameraAvailable {
supportedMethods.append(MXKeyVerificationMethodQRCodeScan)
}
return supportedMethods
}
}
@@ -0,0 +1,83 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
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
final class KeyVerificationDataLoadingCoordinator: KeyVerificationDataLoadingCoordinatorType {
// MARK: - Properties
// MARK: Private
private var keyVerificationDataLoadingViewModel: KeyVerificationDataLoadingViewModelType
private let keyVerificationDataLoadingViewController: KeyVerificationDataLoadingViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: KeyVerificationDataLoadingCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUserId: String, otherDeviceId: String) {
let keyVerificationDataLoadingViewModel = KeyVerificationDataLoadingViewModel(session: session, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
let keyVerificationDataLoadingViewController = KeyVerificationDataLoadingViewController.instantiate(with: keyVerificationDataLoadingViewModel)
self.keyVerificationDataLoadingViewModel = keyVerificationDataLoadingViewModel
self.keyVerificationDataLoadingViewController = keyVerificationDataLoadingViewController
}
init(session: MXSession, incomingKeyVerificationRequest: MXKeyVerificationRequest) {
let keyVerificationDataLoadingViewModel = KeyVerificationDataLoadingViewModel(session: session, keyVerificationRequest: incomingKeyVerificationRequest)
let keyVerificationDataLoadingViewController = KeyVerificationDataLoadingViewController.instantiate(with: keyVerificationDataLoadingViewModel)
self.keyVerificationDataLoadingViewModel = keyVerificationDataLoadingViewModel
self.keyVerificationDataLoadingViewController = keyVerificationDataLoadingViewController
}
// MARK: - Public methods
func start() {
self.keyVerificationDataLoadingViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.keyVerificationDataLoadingViewController
}
}
// MARK: - KeyVerificationDataLoadingViewModelCoordinatorDelegate
extension KeyVerificationDataLoadingCoordinator: KeyVerificationDataLoadingViewModelCoordinatorDelegate {
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) {
self.delegate?.keyVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequest: keyVerificationRequest)
}
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXKeyVerificationTransaction) {
self.delegate?.keyVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequestWithTransaction: transaction)
}
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) {
self.delegate?.keyVerificationDataLoadingCoordinator(self, didLoadUser: user, device: device)
}
func keyVerificationDataLoadingViewModelDidCancel(_ viewModel: KeyVerificationDataLoadingViewModelType) {
self.delegate?.keyVerificationDataLoadingCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,31 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
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
protocol KeyVerificationDataLoadingCoordinatorDelegate: class {
func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo)
func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXKeyVerificationTransaction)
func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest)
func keyVerificationDataLoadingCoordinatorDidCancel(_ coordinator: KeyVerificationDataLoadingCoordinatorType)
}
/// `KeyVerificationDataLoadingCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol KeyVerificationDataLoadingCoordinatorType: Coordinator, Presentable {
var delegate: KeyVerificationDataLoadingCoordinatorDelegate? { get }
}
@@ -0,0 +1,25 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
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
/// KeyVerificationDataLoadingViewController view actions exposed to view model
enum KeyVerificationDataLoadingViewAction {
case loadData
case cancel
}
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dBQ-CG-VDL">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Key Verification Data Loading View Controller-->
<scene sceneID="EyC-m5-6uM">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="dBQ-CG-VDL" customClass="KeyVerificationDataLoadingViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ht4-fu-3rS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jOh-c7-uod">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="c4q-B8-hPy">
<rect key="frame" x="0.0" y="0.0" width="375" height="428"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fNE-v3-2lx">
<rect key="frame" x="0.0" y="0.0" width="375" height="428"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" priority="750" constant="500" id="9am-iX-rzi"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="fNE-v3-2lx" firstAttribute="top" secondItem="c4q-B8-hPy" secondAttribute="top" id="bHO-0I-Jjh"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="centerX" secondItem="c4q-B8-hPy" secondAttribute="centerX" id="fGs-s5-GHA"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="c4q-B8-hPy" secondAttribute="leading" id="jpJ-bp-Vmz"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fNE-v3-2lx" secondAttribute="trailing" id="juO-Zk-MPs"/>
<constraint firstAttribute="bottom" secondItem="fNE-v3-2lx" secondAttribute="bottom" id="sZa-ea-aZQ"/>
<constraint firstAttribute="height" constant="428" id="vx4-4u-WS1"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="c4q-B8-hPy" secondAttribute="bottom" id="KlD-dP-EYo"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="width" secondItem="jOh-c7-uod" secondAttribute="width" id="Tly-og-biF"/>
<constraint firstAttribute="trailing" secondItem="c4q-B8-hPy" secondAttribute="trailing" id="fNe-8B-X6c"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="h5p-NS-unN"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="top" secondItem="jOh-c7-uod" secondAttribute="top" id="zPm-BG-Pm8"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="jOh-c7-uod" secondAttribute="trailing" id="7K8-MG-xLT"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="bottom" secondItem="jOh-c7-uod" secondAttribute="bottom" id="DGP-MJ-g6l"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="leading" secondItem="Ht4-fu-3rS" secondAttribute="leading" id="TGc-b5-uMu"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="Z7r-yd-J4e"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="trailing" secondItem="6ex-OQ-2sZ" secondAttribute="trailing" id="jVN-Fr-MKN"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="top" secondItem="6ex-OQ-2sZ" secondAttribute="top" id="s7K-jf-P1z"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6ex-OQ-2sZ"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bLY-II-iJ3" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1703" y="255"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,177 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
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 UIKit
final class KeyVerificationDataLoadingViewController: UIViewController {
// MARK: - Properties
// MARK: Outlets
// MARK: Private
private var viewModel: KeyVerificationDataLoadingViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: KeyVerificationDataLoadingViewModelType) -> KeyVerificationDataLoadingViewController {
let viewController = StoryboardScene.KeyVerificationDataLoadingViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.deviceVerificationTitle
self.vc_removeBackTitle()
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide back button
self.navigationItem.setHidesBackButton(true, animated: animated)
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
}
private func render(viewState: KeyVerificationDataLoadingViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderLoaded()
case .error(let error):
self.render(error: error)
case .errorMessage(let message):
self.renderError(message: message)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
private func render(error: Error) {
var shouldDisplayError = true
var message: String?
switch error {
case KeyVerificationDataLoadingViewModelError.transactionCancelled:
message = VectorL10n.deviceVerificationCancelled
case KeyVerificationDataLoadingViewModelError.transactionCancelledByMe(reason: let reason):
if reason.value != MXTransactionCancelCode.user().value {
message = VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable)
} else {
shouldDisplayError = false
}
default:
break
}
if shouldDisplayError {
let completion = {
self.viewModel.process(viewAction: .cancel)
}
if let message = message {
self.errorPresenter.presentError(from: self, title: "", message: message, animated: true, handler: completion)
} else {
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: completion)
}
}
}
private func renderError(message: String) {
self.errorPresenter.presentError(from: self, title: "", message: message, animated: true, handler: {
self.viewModel.process(viewAction: .cancel)
})
}
// MARK: - Actions
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - KeyVerificationDataLoadingViewModelViewDelegate
extension KeyVerificationDataLoadingViewController: KeyVerificationDataLoadingViewModelViewDelegate {
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didUpdateViewState viewSate: KeyVerificationDataLoadingViewState) {
self.render(viewState: viewSate)
}
}
@@ -0,0 +1,150 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
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
enum KeyVerificationDataLoadingViewModelError: Error {
case unknown
case transactionCancelled
case transactionCancelledByMe(reason: MXTransactionCancelCode)
}
final class KeyVerificationDataLoadingViewModel: KeyVerificationDataLoadingViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let otherUserId: String?
private let otherDeviceId: String?
private let keyVerificationService = KeyVerificationService()
private let keyVerificationRequest: MXKeyVerificationRequest?
private var currentOperation: MXHTTPOperation?
// MARK: Public
weak var viewDelegate: KeyVerificationDataLoadingViewModelViewDelegate?
weak var coordinatorDelegate: KeyVerificationDataLoadingViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUserId: String, otherDeviceId: String) {
self.session = session
self.otherUserId = otherUserId
self.otherDeviceId = otherDeviceId
self.keyVerificationRequest = nil
}
init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) {
self.session = session
self.otherUserId = nil
self.otherDeviceId = nil
self.keyVerificationRequest = keyVerificationRequest
}
deinit {
self.currentOperation?.cancel()
}
// MARK: - Public
func process(viewAction: KeyVerificationDataLoadingViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .cancel:
self.coordinatorDelegate?.keyVerificationDataLoadingViewModelDidCancel(self)
}
}
// MARK: - Private
private func loadData() {
if let keyVerificationRequest = self.keyVerificationRequest {
self.acceptKeyVerificationRequest(keyVerificationRequest)
} else {
self.downloadOtherDeviceKeys()
}
}
private func acceptKeyVerificationRequest(_ keyVerificationRequest: MXKeyVerificationRequest) {
self.update(viewState: .loading)
keyVerificationRequest.accept(withMethods: self.keyVerificationService.supportedKeyVerificationMethods(), success: { [weak self] in
guard let self = self else {
return
}
self.coordinatorDelegate?.keyVerificationDataLoadingViewModel(self, didAcceptKeyVerificationRequest: keyVerificationRequest)
}, failure: { [weak self] (error) in
guard let self = self else {
return
}
self.update(viewState: .error(error))
})
}
private func downloadOtherDeviceKeys() {
guard let crypto = session.crypto,
let otherUserId = self.otherUserId,
let otherDeviceId = self.otherDeviceId else {
self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
NSLog("[KeyVerificationDataLoadingViewModel] Error session.crypto is nil")
return
}
if let otherUser = session.user(withUserId: otherUserId) {
self.update(viewState: .loading)
self.currentOperation = crypto.downloadKeys([otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap, crossSigningKeysMap) in
guard let sself = self else {
return
}
if let otherDevice = usersDevicesMap?.object(forDevice: otherDeviceId, forUser: otherUserId) {
sself.update(viewState: .loaded)
sself.coordinatorDelegate?.keyVerificationDataLoadingViewModel(sself, didLoadUser: otherUser, device: otherDevice)
} else {
sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
}
}, failure: { [weak self] (error) in
guard let sself = self else {
return
}
let finalError = error ?? KeyVerificationDataLoadingViewModelError.unknown
sself.update(viewState: .error(finalError))
})
} else {
self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
}
}
private func update(viewState: KeyVerificationDataLoadingViewState) {
self.viewDelegate?.keyVerificationDataLoadingViewModel(self, didUpdateViewState: viewState)
}
}
@@ -0,0 +1,39 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
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
protocol KeyVerificationDataLoadingViewModelViewDelegate: class {
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didUpdateViewState viewSate: KeyVerificationDataLoadingViewState)
}
protocol KeyVerificationDataLoadingViewModelCoordinatorDelegate: class {
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo)
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXKeyVerificationTransaction)
func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest)
func keyVerificationDataLoadingViewModelDidCancel(_ viewModel: KeyVerificationDataLoadingViewModelType)
}
/// Protocol describing the view model used by `KeyVerificationDataLoadingViewController`
protocol KeyVerificationDataLoadingViewModelType {
var viewDelegate: KeyVerificationDataLoadingViewModelViewDelegate? { get set }
var coordinatorDelegate: KeyVerificationDataLoadingViewModelCoordinatorDelegate? { get set }
func process(viewAction: KeyVerificationDataLoadingViewAction)
}
@@ -0,0 +1,27 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
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
/// KeyVerificationDataLoadingViewController view state
enum KeyVerificationDataLoadingViewState {
case loading
case loaded
case error(Error)
case errorMessage(String)
}
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dBQ-CG-VDL">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Key Verification Verified View Controller-->
<scene sceneID="EyC-m5-6uM">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="dBQ-CG-VDL" customClass="KeyVerificationVerifiedViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ht4-fu-3rS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jOh-c7-uod">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="c4q-B8-hPy">
<rect key="frame" x="0.0" y="0.0" width="375" height="432.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fNE-v3-2lx">
<rect key="frame" x="0.0" y="0.0" width="375" height="432.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Verified!" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1Nw-CZ-lKr">
<rect key="frame" x="20" y="60" width="335" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="30" translatesAutoresizingMaskIntoConstraints="NO" id="12d-Dc-Rlv">
<rect key="frame" x="20" y="160.5" width="335" height="102"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="You've successfully verified this device." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5dj-7z-eH5">
<rect key="frame" x="0.0" y="0.0" width="335" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Secure messages with this user are end-to-end encrypted and not able to be read by third parties." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="spd-ah-EYp">
<rect key="frame" x="0.0" y="48" width="335" height="54"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="4Ll-vk-JLe">
<rect key="frame" x="0.0" y="362.5" width="375" height="50"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Wbk-EX-kTs">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kgv-EZ-dF9">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="WcJ-IL-5KV"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
<state key="normal" title="Got it">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="validateButtonAction:" destination="dBQ-CG-VDL" eventType="touchUpInside" id="kpR-g5-ogv"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="kgv-EZ-dF9" firstAttribute="top" secondItem="Wbk-EX-kTs" secondAttribute="top" id="1mu-8N-etF"/>
<constraint firstAttribute="bottom" secondItem="kgv-EZ-dF9" secondAttribute="bottom" id="2ce-b1-aV5"/>
<constraint firstAttribute="trailing" secondItem="kgv-EZ-dF9" secondAttribute="trailing" id="OHz-zo-Uvl"/>
<constraint firstAttribute="width" priority="750" constant="500" id="eud-Ba-XSx"/>
<constraint firstItem="kgv-EZ-dF9" firstAttribute="leading" secondItem="Wbk-EX-kTs" secondAttribute="leading" id="gPb-HX-NWn"/>
</constraints>
</view>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="leading" secondItem="1Nw-CZ-lKr" secondAttribute="leading" id="5Kb-MI-4lP"/>
<constraint firstAttribute="width" priority="750" constant="500" id="9am-iX-rzi"/>
<constraint firstAttribute="trailing" secondItem="4Ll-vk-JLe" secondAttribute="trailing" id="CU4-Sr-hLT"/>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="trailing" secondItem="1Nw-CZ-lKr" secondAttribute="trailing" id="Cax-X9-xf8"/>
<constraint firstItem="4Ll-vk-JLe" firstAttribute="leading" secondItem="fNE-v3-2lx" secondAttribute="leading" id="FGu-8C-v1U"/>
<constraint firstItem="4Ll-vk-JLe" firstAttribute="top" secondItem="12d-Dc-Rlv" secondAttribute="bottom" constant="100" id="Hue-GK-ORf"/>
<constraint firstItem="1Nw-CZ-lKr" firstAttribute="leading" secondItem="fNE-v3-2lx" secondAttribute="leading" constant="20" id="LK5-9b-xDf"/>
<constraint firstItem="1Nw-CZ-lKr" firstAttribute="top" secondItem="fNE-v3-2lx" secondAttribute="top" constant="60" id="MUK-4D-vke"/>
<constraint firstAttribute="bottom" secondItem="4Ll-vk-JLe" secondAttribute="bottom" constant="20" id="Vn1-zQ-G8t"/>
<constraint firstAttribute="trailing" secondItem="1Nw-CZ-lKr" secondAttribute="trailing" constant="20" id="WKJ-Ta-cVg"/>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="centerX" secondItem="fNE-v3-2lx" secondAttribute="centerX" id="ksz-nC-DeX"/>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="top" secondItem="1Nw-CZ-lKr" secondAttribute="bottom" constant="80" id="rhF-BF-2cR"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="fNE-v3-2lx" firstAttribute="top" secondItem="c4q-B8-hPy" secondAttribute="top" id="bHO-0I-Jjh"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="centerX" secondItem="c4q-B8-hPy" secondAttribute="centerX" id="fGs-s5-GHA"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="c4q-B8-hPy" secondAttribute="leading" id="jpJ-bp-Vmz"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fNE-v3-2lx" secondAttribute="trailing" id="juO-Zk-MPs"/>
<constraint firstAttribute="bottom" secondItem="fNE-v3-2lx" secondAttribute="bottom" id="sZa-ea-aZQ"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="c4q-B8-hPy" secondAttribute="bottom" id="KlD-dP-EYo"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="width" secondItem="jOh-c7-uod" secondAttribute="width" id="Tly-og-biF"/>
<constraint firstAttribute="trailing" secondItem="c4q-B8-hPy" secondAttribute="trailing" id="fNe-8B-X6c"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="h5p-NS-unN"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="top" secondItem="jOh-c7-uod" secondAttribute="top" id="zPm-BG-Pm8"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="jOh-c7-uod" secondAttribute="trailing" id="7K8-MG-xLT"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="bottom" secondItem="jOh-c7-uod" secondAttribute="bottom" id="DGP-MJ-g6l"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="leading" secondItem="Ht4-fu-3rS" secondAttribute="leading" id="TGc-b5-uMu"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="Z7r-yd-J4e"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="trailing" secondItem="6ex-OQ-2sZ" secondAttribute="trailing" id="jVN-Fr-MKN"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="top" secondItem="6ex-OQ-2sZ" secondAttribute="top" id="s7K-jf-P1z"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6ex-OQ-2sZ"/>
</view>
<connections>
<outlet property="description1Label" destination="5dj-7z-eH5" id="y4w-i9-5hB"/>
<outlet property="description2Label" destination="spd-ah-EYp" id="TXD-8P-gF0"/>
<outlet property="okButton" destination="kgv-EZ-dF9" id="do0-Ot-OKn"/>
<outlet property="okButtonBackgroundView" destination="Wbk-EX-kTs" id="2gv-gg-ROL"/>
<outlet property="titleLabel" destination="1Nw-CZ-lKr" id="zXP-Xt-Zl9"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bLY-II-iJ3" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1703" y="255"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,141 @@
// File created from simpleScreenTemplate
// $ createSimpleScreen.sh DeviceVerification/Verified DeviceVerificationVerified
/*
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 UIKit
protocol KeyVerificationVerifiedViewControllerDelegate: class {
func keyVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: KeyVerificationVerifiedViewController)
func keyVerificationVerifiedViewControllerDidCancel(_ viewController: KeyVerificationVerifiedViewController)
}
final class KeyVerificationVerifiedViewController: UIViewController {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var description1Label: UILabel!
@IBOutlet private weak var description2Label: UILabel!
@IBOutlet private weak var okButtonBackgroundView: UIView!
@IBOutlet private weak var okButton: UIButton!
// MARK: Private
private var theme: Theme!
private var verificationKind: KeyVerificationKind = .user
// MARK: Public
weak var delegate: KeyVerificationVerifiedViewControllerDelegate?
// MARK: - Setup
class func instantiate(with verificationKind: KeyVerificationKind) -> KeyVerificationVerifiedViewController {
let viewController = StoryboardScene.KeyVerificationVerifiedViewController.initialScene.instantiate()
viewController.theme = ThemeService.shared().theme
viewController.verificationKind = verificationKind
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.vc_removeBackTitle()
self.setupViews()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide back button
self.navigationItem.setHidesBackButton(true, animated: animated)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func setupViews() {
let title: String
let bodyTitle: String
let descriptionTextPart1: String
let descriptionTextPart2: String
switch self.verificationKind {
case .device:
title = VectorL10n.deviceVerificationTitle
bodyTitle = VectorL10n.deviceVerificationVerifiedTitle
descriptionTextPart1 = VectorL10n.deviceVerificationVerifiedDescription1
descriptionTextPart2 = VectorL10n.deviceVerificationVerifiedDescription2
case .user:
title = VectorL10n.keyVerificationUserTitle
bodyTitle = VectorL10n.deviceVerificationVerifiedTitle
descriptionTextPart1 = VectorL10n.keyVerificationVerifiedUserDescription1
descriptionTextPart2 = VectorL10n.keyVerificationVerifiedUserDescription2
}
self.title = title
self.titleLabel.text = bodyTitle
self.description1Label.text = descriptionTextPart1
self.description2Label.text = descriptionTextPart2
self.okButton.setTitle(VectorL10n.deviceVerificationVerifiedGotItButton, for: .normal)
}
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.description1Label.textColor = theme.textPrimaryColor
self.description2Label.textColor = theme.textPrimaryColor
self.okButtonBackgroundView.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.okButton)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
// MARK: - Actions
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
@IBAction private func validateButtonAction(_ sender: Any) {
self.delegate?.keyVerificationVerifiedViewControllerDidTapSetupAction(self)
}
}
@@ -0,0 +1,71 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
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
final class KeyVerificationVerifyBySASCoordinator: KeyVerificationVerifyBySASCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var keyVerificationVerifyViewModel: KeyVerificationVerifyBySASViewModelType
private let keyVerificationVerifyViewController: KeyVerificationVerifyBySASViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: KeyVerificationVerifyBySASCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, transaction: MXSASTransaction, verificationKind: KeyVerificationKind) {
self.session = session
let keyVerificationVerifyViewModel = KeyVerificationVerifyBySASViewModel(session: self.session, transaction: transaction, verificationKind: verificationKind)
let keyVerificationVerifyViewController = KeyVerificationVerifyBySASViewController.instantiate(with: keyVerificationVerifyViewModel)
self.keyVerificationVerifyViewModel = keyVerificationVerifyViewModel
self.keyVerificationVerifyViewController = keyVerificationVerifyViewController
}
// MARK: - Public methods
func start() {
self.keyVerificationVerifyViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.keyVerificationVerifyViewController
}
}
// MARK: - DeviceVerificationVerifyViewModelCoordinatorDelegate
extension KeyVerificationVerifyBySASCoordinator: KeyVerificationVerifyBySASViewModelCoordinatorDelegate {
func keyVerificationVerifyViewModelDidComplete(_ viewModel: KeyVerificationVerifyBySASViewModelType) {
self.delegate?.keyVerificationVerifyBySASCoordinatorDidComplete(self)
}
func keyVerificationVerifyViewModelDidCancel(_ viewModel: KeyVerificationVerifyBySASViewModelType) {
self.delegate?.keyVerificationVerifyBySASCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
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
protocol KeyVerificationVerifyBySASCoordinatorDelegate: class {
func keyVerificationVerifyBySASCoordinatorDidComplete(_ coordinator: KeyVerificationVerifyBySASCoordinatorType)
func keyVerificationVerifyBySASCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyBySASCoordinatorType)
}
/// `KeyVerificationVerifyBySASCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol KeyVerificationVerifyBySASCoordinatorType: Coordinator, Presentable {
var delegate: KeyVerificationVerifyBySASCoordinatorDelegate? { get }
}
@@ -0,0 +1,27 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
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
/// KeyVerificationVerifyBySASViewController view actions exposed to view model
enum KeyVerificationVerifyBySASViewAction {
case loadData
case confirm
case complete
case cancel
}
@@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Key Verification Verify BySAS View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="KeyVerificationVerifyBySASViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="375" height="485"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="375" height="485"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Verify this device by confirming the following emoji appear on the screen of the partner" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="he8-pl-xE9">
<rect key="frame" x="20" y="35" width="335" height="61"/>
<constraints>
<constraint firstAttribute="height" constant="61" id="Nam-ca-50k"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="For maximum security, we recommend you do this in person or use another trusted means of communication." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="20" y="115" width="335" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="3ag-pn-F2b"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3152 3307 8179" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="RD6-ue-X5c">
<rect key="frame" x="37.5" y="290.5" width="300" height="29"/>
<constraints>
<constraint firstAttribute="width" constant="300" id="6Q5-M2-ifj"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DtR-jx-UKY">
<rect key="frame" x="0.0" y="415" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
<rect key="frame" x="156.5" y="10" width="62" height="30"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" title="Continue">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="continueButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="uvI-tt-Nfj"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerY" secondItem="DtR-jx-UKY" secondAttribute="centerY" id="5eX-a5-zpP"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerX" secondItem="DtR-jx-UKY" secondAttribute="centerX" id="6v9-MN-mk2"/>
<constraint firstAttribute="height" constant="50" id="QNq-au-ZdL"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Waiting for partner to confirm..." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6yX-xD-4X5">
<rect key="frame" x="20" y="242" width="335" height="54"/>
<constraints>
<constraint firstAttribute="width" constant="335" id="9C0-ev-AVw"/>
<constraint firstAttribute="height" constant="54" id="pta-eP-0yH"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="34i-3X-YvQ">
<rect key="frame" x="0.0" y="215" width="375" height="180"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="180" id="kpT-ty-CDI"/>
</constraints>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="0.0" id="mHm-Tg-xbO">
<size key="itemSize" width="80" height="80"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="VerifyEmojiCollectionViewCell" id="iG2-Pq-pYr" customClass="VerifyEmojiCollectionViewCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="🐶" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5Cy-oI-To3">
<rect key="frame" x="0.0" y="6" width="80" height="43"/>
<fontDescription key="fontDescription" type="system" pointSize="36"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="dog" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="axP-53-KGQ">
<rect key="frame" x="0.0" y="58" width="80" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</view>
<constraints>
<constraint firstAttribute="trailing" secondItem="5Cy-oI-To3" secondAttribute="trailing" id="0vM-mM-9UI"/>
<constraint firstItem="axP-53-KGQ" firstAttribute="leading" secondItem="iG2-Pq-pYr" secondAttribute="leading" id="LeM-oa-STt"/>
<constraint firstItem="5Cy-oI-To3" firstAttribute="top" secondItem="iG2-Pq-pYr" secondAttribute="top" constant="6" id="ZBA-TS-ThT"/>
<constraint firstItem="5Cy-oI-To3" firstAttribute="leading" secondItem="iG2-Pq-pYr" secondAttribute="leading" id="hZz-hI-6k2"/>
<constraint firstAttribute="bottom" secondItem="axP-53-KGQ" secondAttribute="bottom" constant="6" id="ja0-Lm-Kej"/>
<constraint firstAttribute="trailing" secondItem="axP-53-KGQ" secondAttribute="trailing" id="yur-4a-SMc"/>
</constraints>
<connections>
<outlet property="emoji" destination="5Cy-oI-To3" id="pOP-P0-x8a"/>
<outlet property="name" destination="axP-53-KGQ" id="JYh-RY-edf"/>
</connections>
</collectionViewCell>
</cells>
<connections>
<outlet property="dataSource" destination="V8j-Lb-PgC" id="3m4-oH-KIj"/>
</connections>
</collectionView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="6yX-xD-4X5" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="47" id="6Bh-e4-HVr"/>
<constraint firstItem="34i-3X-YvQ" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="Bb1-EE-rq0"/>
<constraint firstAttribute="bottom" secondItem="DtR-jx-UKY" secondAttribute="bottom" constant="20" id="Cnb-UW-hYt"/>
<constraint firstAttribute="trailing" secondItem="DtR-jx-UKY" secondAttribute="trailing" id="K7y-Df-dgz"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="bxI-mu-qng" secondAttribute="leading" id="Q9n-7p-gHl"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" id="QSg-yz-aaB"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="Rg4-jV-Nht"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="top" secondItem="he8-pl-xE9" secondAttribute="top" constant="80" id="UL4-x0-oFT"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="ZP8-mV-RBh"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" id="Zeg-U8-uis"/>
<constraint firstAttribute="width" priority="750" constant="375" id="glD-Sz-73O"/>
<constraint firstItem="RD6-ue-X5c" firstAttribute="centerY" secondItem="34i-3X-YvQ" secondAttribute="centerY" id="h8F-gc-do4"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="hK0-aA-d9H"/>
<constraint firstAttribute="trailing" secondItem="34i-3X-YvQ" secondAttribute="trailing" id="hg6-kq-M6U"/>
<constraint firstItem="RD6-ue-X5c" firstAttribute="centerX" secondItem="34i-3X-YvQ" secondAttribute="centerX" id="lVE-w6-pIa"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="top" secondItem="34i-3X-YvQ" secondAttribute="bottom" constant="20" id="liF-Qn-tiw"/>
<constraint firstItem="34i-3X-YvQ" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="20" id="r7A-9g-Mmb"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="35" id="s3k-Io-834"/>
</constraints>
<variation key="default">
<mask key="subviews">
<exclude reference="6yX-xD-4X5"/>
</mask>
</variation>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" constant="70" id="Y46-NP-zAc"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<connections>
<outlet property="continueButton" destination="DOt-5E-FjF" id="ktw-U4-efQ"/>
<outlet property="continueButtonBackgroundView" destination="DtR-jx-UKY" id="9yG-wP-u8A"/>
<outlet property="decimalLabel" destination="RD6-ue-X5c" id="wlK-nl-CB6"/>
<outlet property="emojisCollectionView" destination="34i-3X-YvQ" id="wDE-oG-peo"/>
<outlet property="informationLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="titleLabel" destination="he8-pl-xE9" id="btA-kv-E2B"/>
<outlet property="waitingPartnerLabel" destination="6yX-xD-4X5" id="fre-bc-Kma"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3199.1999999999998" y="-647.22638680659679"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,256 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
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 UIKit
final class KeyVerificationVerifyBySASViewController: UIViewController {
// MARK: - Constants
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var decimalLabel: UILabel!
@IBOutlet private weak var emojisCollectionView: UICollectionView!
@IBOutlet private weak var waitingPartnerLabel: UILabel!
@IBOutlet private weak var continueButtonBackgroundView: UIView!
@IBOutlet private weak var continueButton: UIButton!
// MARK: Private
private var viewModel: KeyVerificationVerifyBySASViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: KeyVerificationVerifyBySASViewModelType) -> KeyVerificationVerifyBySASViewController {
let viewController = StoryboardScene.KeyVerificationVerifyBySASViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.vc_removeBackTitle()
self.setupViews()
self.errorPresenter = MXKErrorAlertPresentation()
self.activityPresenter = ActivityIndicatorPresenter()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide back button
self.navigationItem.setHidesBackButton(true, animated: animated)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.informationLabel.textColor = theme.textPrimaryColor
self.decimalLabel.textColor = theme.textPrimaryColor
self.waitingPartnerLabel.textColor = theme.textPrimaryColor
self.continueButtonBackgroundView.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.continueButton)
emojisCollectionView.reloadData()
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.scrollView.keyboardDismissMode = .interactive
let isVerificationByEmoji = viewModel.emojis != nil
if isVerificationByEmoji {
self.decimalLabel.isHidden = true
} else {
self.emojisCollectionView.isHidden = true
self.decimalLabel.text = self.viewModel.decimal
}
let title: String
let instructionText: String
let adviceText: String
switch viewModel.verificationKind {
case .device:
title = VectorL10n.deviceVerificationTitle
instructionText = isVerificationByEmoji ? VectorL10n.deviceVerificationVerifyTitleEmoji : VectorL10n.deviceVerificationVerifyTitleNumber
adviceText = VectorL10n.deviceVerificationSecurityAdvice
case .user:
title = VectorL10n.keyVerificationUserTitle
instructionText = isVerificationByEmoji ? VectorL10n.keyVerificationVerifyUserTitleEmoji : VectorL10n.keyVerificationVerifyUserTitleNumber
adviceText = VectorL10n.deviceVerificationSecurityAdvice
}
self.title = title
self.titleLabel.text = instructionText
self.informationLabel.text = adviceText
self.waitingPartnerLabel.text = VectorL10n.deviceVerificationVerifyWaitPartner
self.waitingPartnerLabel.isHidden = true
self.continueButton.setTitle(VectorL10n.continue, for: .normal)
}
private func render(viewState: KeyVerificationVerifyViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderVerified()
case .cancelled(let reason):
self.renderCancelled(reason: reason)
case .cancelledByMe(let reason):
self.renderCancelledByMe(reason: reason)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderVerified() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.continueButtonBackgroundView.isHidden = true
self.waitingPartnerLabel.isHidden = false
}
private func renderCancelled(reason: MXTransactionCancelCode) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
private func renderCancelledByMe(reason: MXTransactionCancelCode) {
if reason.value != MXTransactionCancelCode.user().value {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) {
self.viewModel.process(viewAction: .cancel)
}
} else {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
// MARK: - Actions
@IBAction private func continueButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .confirm)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - DeviceVerificationVerifyViewModelViewDelegate
extension KeyVerificationVerifyBySASViewController: KeyVerificationVerifyBySASViewModelViewDelegate {
func keyVerificationVerifyBySASViewModel(_ viewModel: KeyVerificationVerifyBySASViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyViewState) {
self.render(viewState: viewSate)
}
}
extension KeyVerificationVerifyBySASViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let emojis = self.viewModel.emojis else {
return 0
}
return emojis.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: VerifyEmojiCollectionViewCell.self)
guard let emoji = self.viewModel.emojis?[indexPath.row] else {
return UICollectionViewCell()
}
cell.emoji.text = emoji.emoji
cell.name.text = VectorL10n.tr("Vector", "device_verification_emoji_\(emoji.name)")
cell.update(theme: self.theme)
return cell
}
}
@@ -0,0 +1,126 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
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
final class KeyVerificationVerifyBySASViewModel: KeyVerificationVerifyBySASViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let transaction: MXSASTransaction
// MARK: Public
weak var viewDelegate: KeyVerificationVerifyBySASViewModelViewDelegate?
weak var coordinatorDelegate: KeyVerificationVerifyBySASViewModelCoordinatorDelegate?
let emojis: [MXEmojiRepresentation]?
let decimal: String?
let verificationKind: KeyVerificationKind
// MARK: - Setup
init(session: MXSession, transaction: MXSASTransaction, verificationKind: KeyVerificationKind) {
self.session = session
self.transaction = transaction
self.emojis = self.transaction.sasEmoji
self.decimal = self.transaction.sasDecimal
self.verificationKind = verificationKind
}
deinit {
}
// MARK: - Public
func process(viewAction: KeyVerificationVerifyBySASViewAction) {
switch viewAction {
case .loadData:
self.registerTransactionDidStateChangeNotification(transaction: transaction)
case .confirm:
self.confirmTransaction()
case .complete:
self.coordinatorDelegate?.keyVerificationVerifyViewModelDidComplete(self)
case .cancel:
self.cancelTransaction()
self.coordinatorDelegate?.keyVerificationVerifyViewModelDidCancel(self)
}
}
// MARK: - Private
private func confirmTransaction() {
self.update(viewState: .loading)
self.transaction.confirmSASMatch()
}
private func cancelTransaction() {
self.transaction.cancel(with: MXTransactionCancelCode.user())
}
private func update(viewState: KeyVerificationVerifyViewState) {
self.viewDelegate?.keyVerificationVerifyBySASViewModel(self, didUpdateViewState: viewState)
}
// MARK: - MXKeyVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification(transaction: MXSASTransaction) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction)
}
private func unregisterTransactionDidStateChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXSASTransaction else {
return
}
switch transaction.state {
case MXSASTransactionStateVerified:
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .loaded)
self.coordinatorDelegate?.keyVerificationVerifyViewModelDidComplete(self)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateError:
guard let error = transaction.error else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .error(error))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelledByMe(reason))
default:
break
}
}
}
@@ -0,0 +1,41 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
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
protocol KeyVerificationVerifyBySASViewModelViewDelegate: class {
func keyVerificationVerifyBySASViewModel(_ viewModel: KeyVerificationVerifyBySASViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyViewState)
}
protocol KeyVerificationVerifyBySASViewModelCoordinatorDelegate: class {
func keyVerificationVerifyViewModelDidComplete(_ viewModel: KeyVerificationVerifyBySASViewModelType)
func keyVerificationVerifyViewModelDidCancel(_ viewModel: KeyVerificationVerifyBySASViewModelType)
}
/// Protocol describing the view model used by `KeyVerificationVerifyBySASViewController`
protocol KeyVerificationVerifyBySASViewModelType {
var viewDelegate: KeyVerificationVerifyBySASViewModelViewDelegate? { get set }
var coordinatorDelegate: KeyVerificationVerifyBySASViewModelCoordinatorDelegate? { get set }
func process(viewAction: KeyVerificationVerifyBySASViewAction)
var emojis: [MXEmojiRepresentation]? { get }
var decimal: String? { get }
var verificationKind: KeyVerificationKind { get }
}
@@ -0,0 +1,28 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
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
/// KeyVerificationVerifyBySASViewController view state
enum KeyVerificationVerifyViewState {
case loading
case loaded // verified
case cancelled(MXTransactionCancelCode)
case cancelledByMe(MXTransactionCancelCode)
case error(Error)
}
@@ -0,0 +1,27 @@
/*
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 UIKit
import Reusable
class VerifyEmojiCollectionViewCell: UICollectionViewCell, Reusable, Themable {
@IBOutlet weak var emoji: UILabel!
@IBOutlet weak var name: UILabel!
func update(theme: Theme) {
name.textColor = theme.textPrimaryColor
}
}
@@ -0,0 +1,82 @@
// File created from ScreenTemplate
// $ createScreen.sh Verify KeyVerificationVerifyByScanning
/*
Copyright 2020 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
final class KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let keyVerificationRequest: MXKeyVerificationRequest
private var keyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType
private let keyVerificationVerifyByScanningViewController: KeyVerificationVerifyByScanningViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) {
self.session = session
self.keyVerificationRequest = keyVerificationRequest
let keyVerificationVerifyByScanningViewModel = KeyVerificationVerifyByScanningViewModel(session: self.session, keyVerificationRequest: keyVerificationRequest)
let keyVerificationVerifyByScanningViewController = KeyVerificationVerifyByScanningViewController.instantiate(with: keyVerificationVerifyByScanningViewModel)
self.keyVerificationVerifyByScanningViewModel = keyVerificationVerifyByScanningViewModel
self.keyVerificationVerifyByScanningViewController = keyVerificationVerifyByScanningViewController
}
// MARK: - Public methods
func start() {
self.keyVerificationVerifyByScanningViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.keyVerificationVerifyByScanningViewController
}
}
// MARK: - KeyVerificationVerifyByScanningViewModelCoordinatorDelegate
extension KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate {
func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) {
self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCancel(self)
}
func keyVerificationVerifyByScanningViewModelCannotScan(_ viewModel: KeyVerificationVerifyByScanningViewModelType) {
self.delegate?.keyVerificationVerifyByScanningCoordinatorCannotScan(self)
}
func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) {
self.delegate?.keyVerificationVerifyByScanningCoordinator(self, didCompleteWithSASTransaction: transaction)
}
func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType) {
self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(self)
}
}
@@ -0,0 +1,31 @@
// File created from ScreenTemplate
// $ createScreen.sh Verify KeyVerificationVerifyByScanning
/*
Copyright 2020 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
protocol KeyVerificationVerifyByScanningCoordinatorDelegate: class {
func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType)
func keyVerificationVerifyByScanningCoordinatorCannotScan(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType)
func keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType)
func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction)
}
/// `KeyVerificationVerifyByScanningCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol KeyVerificationVerifyByScanningCoordinatorType: Coordinator, Presentable {
var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate? { get }
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh Verify KeyVerificationVerifyByScanning
/*
Copyright 2020 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
/// KeyVerificationVerifyByScanningViewController view actions exposed to view model
enum KeyVerificationVerifyByScanningViewAction {
case loadData
case cancel
case scannedCode(payloadData: Data)
case cannotScan
case acknowledgeOtherScannedMyCode(Bool)
case acknowledgeMyUserScannedOtherCode
}
@@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="F9j-7h-dzQ">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Key Verification Verify By Scanning View Controller-->
<scene sceneID="aq0-GT-wx2">
<objects>
<viewController id="F9j-7h-dzQ" customClass="KeyVerificationVerifyByScanningViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="WNg-GS-gGF">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edw-lO-NVl">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fyB-h5-5v2">
<rect key="frame" x="0.0" y="0.0" width="375" height="453"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="D7P-C8-cqw">
<rect key="frame" x="0.0" y="0.0" width="375" height="103"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="lPc-YT-wnY">
<rect key="frame" x="0.0" y="0.0" width="375" height="103"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="J1F-ba-sZ7">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="254" text="Verify by scanning" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VsP-5V-z35">
<rect key="frame" x="20" y="20" width="288" height="30"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1PP-lU-Ags" customClass="CloseButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="318" y="13" width="44" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="Hfb-fa-cN0"/>
<constraint firstAttribute="height" constant="44" id="SEW-Kx-jgN"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<state key="normal" image="close_button"/>
<connections>
<action selector="closeButtonAction:" destination="F9j-7h-dzQ" eventType="touchUpInside" id="iMU-hO-xBX"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="VsP-5V-z35" secondAttribute="bottom" id="A4U-o4-Q6E"/>
<constraint firstItem="VsP-5V-z35" firstAttribute="centerY" secondItem="1PP-lU-Ags" secondAttribute="centerY" id="F8E-af-0ee"/>
<constraint firstItem="VsP-5V-z35" firstAttribute="leading" secondItem="J1F-ba-sZ7" secondAttribute="leading" constant="20" id="gIh-y4-VEc"/>
<constraint firstAttribute="height" priority="250" id="guf-gx-dH4"/>
<constraint firstItem="1PP-lU-Ags" firstAttribute="leading" secondItem="VsP-5V-z35" secondAttribute="trailing" constant="10" id="lfn-WB-Ilq"/>
<constraint firstItem="1PP-lU-Ags" firstAttribute="top" secondItem="J1F-ba-sZ7" secondAttribute="top" constant="13" id="swy-iI-xCv"/>
<constraint firstAttribute="trailing" secondItem="1PP-lU-Ags" secondAttribute="trailing" constant="13" id="xNP-5O-bnD"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SLA-sa-fBw">
<rect key="frame" x="0.0" y="50" width="375" height="53"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="253" text="Scan the code to securely verify each other." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="y2w-7m-BE3">
<rect key="frame" x="20" y="15" width="335" height="38"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="y2w-7m-BE3" firstAttribute="top" secondItem="SLA-sa-fBw" secondAttribute="top" constant="15" id="4Hi-G4-ifY"/>
<constraint firstAttribute="trailing" secondItem="y2w-7m-BE3" secondAttribute="trailing" constant="20" id="5xb-BK-tXG"/>
<constraint firstAttribute="bottom" secondItem="y2w-7m-BE3" secondAttribute="bottom" id="NIx-Ms-nsQ"/>
<constraint firstAttribute="height" priority="250" id="xbN-ph-dHU"/>
<constraint firstItem="y2w-7m-BE3" firstAttribute="leading" secondItem="SLA-sa-fBw" secondAttribute="leading" constant="20" id="zoQ-Pi-GNl"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="J1F-ba-sZ7" firstAttribute="width" secondItem="lPc-YT-wnY" secondAttribute="width" id="AYv-UT-c0c"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="lPc-YT-wnY" secondAttribute="trailing" id="DDG-hn-hkd"/>
<constraint firstAttribute="height" priority="250" id="Uwm-dQ-xwa"/>
<constraint firstItem="lPc-YT-wnY" firstAttribute="leading" secondItem="D7P-C8-cqw" secondAttribute="leading" id="dU8-Pf-s7B"/>
<constraint firstItem="lPc-YT-wnY" firstAttribute="top" secondItem="D7P-C8-cqw" secondAttribute="top" id="k5A-9l-2eO"/>
<constraint firstAttribute="bottom" secondItem="lPc-YT-wnY" secondAttribute="bottom" id="qcw-CH-EM7"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bQd-8A-8hf">
<rect key="frame" x="0.0" y="103" width="375" height="350"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vrz-UO-PDk">
<rect key="frame" x="0.0" y="0.0" width="375" height="220"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="NFT-6Y-5rt">
<rect key="frame" x="97.5" y="20" width="180" height="180"/>
<constraints>
<constraint firstAttribute="width" constant="180" id="X3y-ER-PrC"/>
<constraint firstAttribute="width" secondItem="NFT-6Y-5rt" secondAttribute="height" multiplier="1:1" id="sOD-Bb-lIr"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="NFT-6Y-5rt" secondAttribute="bottom" constant="20" id="0Wi-uC-aoj"/>
<constraint firstItem="NFT-6Y-5rt" firstAttribute="centerX" secondItem="vrz-UO-PDk" secondAttribute="centerX" id="8UT-Ob-m92"/>
<constraint firstItem="NFT-6Y-5rt" firstAttribute="top" secondItem="vrz-UO-PDk" secondAttribute="top" constant="20" id="Sfi-ob-xej"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="s4G-bW-EGe">
<rect key="frame" x="0.0" y="220" width="375" height="55"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="a4h-x5-COe">
<rect key="frame" x="20" y="0.0" width="335" height="50"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="kzi-B5-Tih"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="25" maxY="0.0"/>
<state key="normal" title="Scan their code" image="camera">
<color key="titleColor" red="0.01176470588" green="0.70196078429999997" blue="0.50588235290000005" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="scanButtonAction:" destination="F9j-7h-dzQ" eventType="touchUpInside" id="zA6-wp-M7j"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="a4h-x5-COe" firstAttribute="leading" secondItem="s4G-bW-EGe" secondAttribute="leading" constant="20" id="5RF-dz-YHM"/>
<constraint firstItem="a4h-x5-COe" firstAttribute="top" secondItem="s4G-bW-EGe" secondAttribute="top" id="dcq-Rd-PvW"/>
<constraint firstAttribute="bottom" secondItem="a4h-x5-COe" secondAttribute="bottom" constant="5" id="wCP-xw-ZzO"/>
<constraint firstAttribute="trailing" secondItem="a4h-x5-COe" secondAttribute="trailing" constant="20" id="xZg-No-LxJ"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="n73-GU-j8x">
<rect key="frame" x="0.0" y="275" width="375" height="75"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bRZ-4Z-DEJ">
<rect key="frame" x="20" y="5" width="335" height="50"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="HdO-4x-2rZ"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="15" maxY="0.0"/>
<state key="normal" title="Can't scan?">
<color key="titleColor" red="0.01176470588" green="0.70196078429999997" blue="0.50588235290000005" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="cannotScanAction:" destination="F9j-7h-dzQ" eventType="touchUpInside" id="p4e-8Q-uNc"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="bRZ-4Z-DEJ" firstAttribute="top" secondItem="n73-GU-j8x" secondAttribute="top" constant="5" id="6br-Zp-d8P"/>
<constraint firstItem="bRZ-4Z-DEJ" firstAttribute="leading" secondItem="n73-GU-j8x" secondAttribute="leading" constant="20" id="ZXo-Vs-cfX"/>
<constraint firstAttribute="trailing" secondItem="bRZ-4Z-DEJ" secondAttribute="trailing" constant="20" id="dTd-nX-JFc"/>
<constraint firstAttribute="bottom" secondItem="bRZ-4Z-DEJ" secondAttribute="bottom" constant="20" id="oGZ-eZ-A2S"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="vrz-UO-PDk" firstAttribute="width" secondItem="bQd-8A-8hf" secondAttribute="width" id="N3C-LY-d1y"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="bQd-8A-8hf" secondAttribute="bottom" id="2S1-ZV-utk"/>
<constraint firstItem="bQd-8A-8hf" firstAttribute="top" secondItem="D7P-C8-cqw" secondAttribute="bottom" id="BWQ-lJ-Pc5"/>
<constraint firstItem="D7P-C8-cqw" firstAttribute="leading" secondItem="fyB-h5-5v2" secondAttribute="leading" id="C3O-Qm-05a"/>
<constraint firstItem="bQd-8A-8hf" firstAttribute="leading" secondItem="fyB-h5-5v2" secondAttribute="leading" id="Kba-mR-aQ9"/>
<constraint firstItem="D7P-C8-cqw" firstAttribute="top" secondItem="fyB-h5-5v2" secondAttribute="top" id="PuA-2S-9Zc"/>
<constraint firstAttribute="trailing" secondItem="bQd-8A-8hf" secondAttribute="trailing" id="Uu5-jh-cbL"/>
<constraint firstAttribute="trailing" secondItem="D7P-C8-cqw" secondAttribute="trailing" id="uSM-cG-Cny"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="fyB-h5-5v2" secondAttribute="bottom" id="6d9-sr-RWX"/>
<constraint firstAttribute="trailing" secondItem="fyB-h5-5v2" secondAttribute="trailing" id="G93-5y-DsI"/>
<constraint firstItem="fyB-h5-5v2" firstAttribute="leading" secondItem="edw-lO-NVl" secondAttribute="leading" id="PQI-Kt-aMD"/>
<constraint firstItem="fyB-h5-5v2" firstAttribute="top" secondItem="edw-lO-NVl" secondAttribute="top" id="jLA-tf-HEW"/>
<constraint firstItem="fyB-h5-5v2" firstAttribute="width" secondItem="edw-lO-NVl" secondAttribute="width" id="v7g-Rh-vPb"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="edw-lO-NVl" secondAttribute="bottom" id="9bx-Fp-MtP"/>
<constraint firstItem="edw-lO-NVl" firstAttribute="leading" secondItem="8yo-Sy-Oxb" secondAttribute="leading" id="CmF-tu-jrz"/>
<constraint firstItem="edw-lO-NVl" firstAttribute="trailing" secondItem="8yo-Sy-Oxb" secondAttribute="trailing" id="Wzg-67-vjz"/>
<constraint firstItem="edw-lO-NVl" firstAttribute="top" secondItem="8yo-Sy-Oxb" secondAttribute="top" id="sAk-S7-Gts"/>
</constraints>
<viewLayoutGuide key="safeArea" id="8yo-Sy-Oxb"/>
</view>
<connections>
<outlet property="cannotScanButton" destination="bRZ-4Z-DEJ" id="O51-k2-JJY"/>
<outlet property="closeButton" destination="1PP-lU-Ags" id="cwn-nr-esg"/>
<outlet property="codeImageView" destination="NFT-6Y-5rt" id="0FH-o1-GBM"/>
<outlet property="informationLabel" destination="y2w-7m-BE3" id="uXB-RC-Ppc"/>
<outlet property="qrCodeContainerView" destination="vrz-UO-PDk" id="mMS-rd-ESe"/>
<outlet property="scanButtonContainerView" destination="s4G-bW-EGe" id="TL4-jJ-EDC"/>
<outlet property="scanCodeButton" destination="a4h-x5-COe" id="8Cl-iJ-be8"/>
<outlet property="titleLabel" destination="VsP-5V-z35" id="t5i-0x-a7m"/>
<outlet property="titleView" destination="J1F-ba-sZ7" id="zVT-Mg-8di"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="uyZ-jd-xN3" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-175.19999999999999" y="124.58770614692655"/>
</scene>
</scenes>
<resources>
<image name="camera" width="24" height="24"/>
<image name="close_button" width="16" height="16"/>
</resources>
</document>
@@ -0,0 +1,321 @@
// File created from ScreenTemplate
// $ createScreen.sh Verify KeyVerificationVerifyByScanning
/*
Copyright 2020 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 UIKit
final class KeyVerificationVerifyByScanningViewController: UIViewController {
// MARK: - Constants
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var closeButton: UIButton!
@IBOutlet private weak var titleView: UIView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var codeImageView: UIImageView!
@IBOutlet private weak var scanCodeButton: UIButton!
@IBOutlet private weak var cannotScanButton: UIButton!
@IBOutlet weak var qrCodeContainerView: UIView!
@IBOutlet weak var scanButtonContainerView: UIView!
// MARK: Private
private var viewModel: KeyVerificationVerifyByScanningViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var cameraAccessAlertPresenter: CameraAccessAlertPresenter!
private var cameraAccessManager: CameraAccessManager!
private weak var qrCodeReaderViewController: QRCodeReaderViewController!
// MARK: - Setup
class func instantiate(with viewModel: KeyVerificationVerifyByScanningViewModelType) -> KeyVerificationVerifyByScanningViewController {
let viewController = StoryboardScene.KeyVerificationVerifyByScanningViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.cameraAccessAlertPresenter = CameraAccessAlertPresenter()
self.cameraAccessManager = CameraAccessManager()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide back button
self.navigationItem.setHidesBackButton(true, animated: animated)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.informationLabel.textColor = theme.textPrimaryColor
if let themableCloseButton = self.closeButton as? Themable {
themableCloseButton.update(theme: theme)
}
theme.applyStyle(onButton: self.scanCodeButton)
theme.applyStyle(onButton: self.cannotScanButton)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.titleView.isHidden = self.navigationController != nil
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.title = VectorL10n.keyVerificationVerifyQrCodeTitle
self.titleLabel.text = VectorL10n.keyVerificationVerifyQrCodeTitle
self.informationLabel.text = VectorL10n.keyVerificationVerifyQrCodeInformation
self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeAction, for: .normal)
self.cannotScanButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeCannotScanAction, for: .normal)
}
private func render(viewState: KeyVerificationVerifyByScanningViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(viewData: let viewData):
self.renderLoaded(viewData: viewData)
case .error(let error):
self.render(error: error)
case .scannedCodeValidated(let isValid):
self.renderScannedCode(valid: isValid)
case .otherUserScannedMyCode:
self.renderOtherScannedMyCode()
case .cancelled(let reason):
self.renderCancelled(reason: reason)
case .cancelledByMe(let reason):
self.renderCancelledByMe(reason: reason)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(viewData: KeyVerificationVerifyByScanningViewData) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
let hideQRCodeImage: Bool
if let qrCodePayloadData = viewData.qrCodeData {
hideQRCodeImage = false
self.codeImageView.image = self.qrCodeImage(from: qrCodePayloadData)
} else {
hideQRCodeImage = true
}
self.qrCodeContainerView.isHidden = hideQRCodeImage
self.scanButtonContainerView.isHidden = !viewData.showScanAction
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func qrCodeImage(from data: Data) -> UIImage? {
let codeGenerator = QRCodeGenerator()
return codeGenerator.generateCode(from: data, with: self.codeImageView.frame.size)
}
private func presentQRCodeReader(animated: Bool) {
let qrCodeViewController = QRCodeReaderViewController.instantiate()
qrCodeViewController.delegate = self
self.present(qrCodeViewController, animated: animated, completion: nil)
self.qrCodeReaderViewController = qrCodeViewController
}
private func renderScannedCode(valid: Bool) {
if valid {
self.qrCodeReaderViewController.view.isUserInteractionEnabled = false
self.qrCodeReaderViewController.stopScanning()
self.presentCodeValidated(animated: true) {
self.dismiss(animated: true, completion: {
self.viewModel.process(viewAction: .acknowledgeMyUserScannedOtherCode)
})
}
}
}
private func renderOtherScannedMyCode() {
let alert = UIAlertController(title: VectorL10n.keyVerificationVerifyQrCodeOtherScanMyCodeTitle,
message: nil,
preferredStyle: .alert)
let okAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "yes"), style: .default, handler: { _ in
self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(true))
})
alert.addAction(okAction)
let cancelAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "no"), style: .cancel, handler: { _ in
self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(false))
})
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
}
private func renderCancelled(reason: MXTransactionCancelCode) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
private func renderCancelledByMe(reason: MXTransactionCancelCode) {
if reason.value != MXTransactionCancelCode.user().value {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) {
self.viewModel.process(viewAction: .cancel)
}
} else {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
}
private func presentCodeValidated(animated: Bool, completion: @escaping (() -> Void)) {
let alert = UIAlertController(title: VectorL10n.keyVerificationVerifyQrCodeScanOtherCodeSuccessTitle,
message: VectorL10n.keyVerificationVerifyQrCodeScanOtherCodeSuccessMessage,
preferredStyle: .alert)
let okAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "ok"), style: .default, handler: { _ in
completion()
})
alert.addAction(okAction)
if let qrCodeReaderViewController = self.qrCodeReaderViewController {
qrCodeReaderViewController.present(alert, animated: animated, completion: nil)
}
}
private func checkCameraAccessAndPresentQRCodeReader(animated: Bool) {
guard self.cameraAccessManager.isCameraAvailable else {
self.cameraAccessAlertPresenter.presentCameraUnavailableAlert(from: self, animated: animated)
return
}
self.cameraAccessManager.askAndRequestCameraAccessIfNeeded { (granted) in
if granted {
self.presentQRCodeReader(animated: animated)
} else {
self.cameraAccessAlertPresenter.presentPermissionDeniedAlert(from: self, animated: animated)
}
}
}
// MARK: - Actions
@IBAction private func scanButtonAction(_ sender: Any) {
self.checkCameraAccessAndPresentQRCodeReader(animated: true)
}
@IBAction private func cannotScanAction(_ sender: Any) {
self.viewModel.process(viewAction: .cannotScan)
}
@IBAction private func closeButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .cancel)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - KeyVerificationVerifyByScanningViewModelViewDelegate
extension KeyVerificationVerifyByScanningViewController: KeyVerificationVerifyByScanningViewModelViewDelegate {
func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState) {
self.render(viewState: viewSate)
}
}
// MARK: - QRCodeReaderViewControllerDelegate
extension KeyVerificationVerifyByScanningViewController: QRCodeReaderViewControllerDelegate {
func qrCodeReaderViewController(_ viewController: QRCodeReaderViewController, didFound payloadData: Data) {
self.viewModel.process(viewAction: .scannedCode(payloadData: payloadData))
}
func qrCodeReaderViewControllerDidCancel(_ viewController: QRCodeReaderViewController) {
self.dismiss(animated: true, completion: nil)
}
}
@@ -0,0 +1,254 @@
// File created from ScreenTemplate
// $ createScreen.sh Verify KeyVerificationVerifyByScanning
/*
Copyright 2020 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
enum KeyVerificationVerifyByScanningViewModelError: Error {
case unknown
}
final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let keyVerificationRequest: MXKeyVerificationRequest
private let qrCodeDataCoder: MXQRCodeDataCoder
private let keyVerificationManager: MXKeyVerificationManager
private var qrCodeTransaction: MXQRCodeTransaction?
private var scannedQRCodeData: MXQRCodeData?
// MARK: Public
weak var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate?
weak var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) {
self.session = session
self.keyVerificationManager = self.session.crypto.keyVerificationManager
self.keyVerificationRequest = keyVerificationRequest
self.qrCodeDataCoder = MXQRCodeDataCoder()
}
deinit {
self.removePendingQRCodeTransaction()
}
// MARK: - Public
func process(viewAction: KeyVerificationVerifyByScanningViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .scannedCode(payloadData: let payloadData):
self.scannedQRCode(payloadData: payloadData)
case .cannotScan:
self.startSASVerification()
case .acknowledgeOtherScannedMyCode(let acknowledgeOtherScannedMyCode):
self.acknowledgeOtherScannedMyCode(acknowledgeOtherScannedMyCode)
case .cancel:
self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self)
case .acknowledgeMyUserScannedOtherCode:
self.acknowledgeScanOtherCode()
}
}
// MARK: - Private
private func loadData() {
let qrCodePlayloadData: Data?
let canShowScanAction: Bool
self.qrCodeTransaction = self.keyVerificationManager.qrCodeTransaction(withTransactionId: self.keyVerificationRequest.requestId)
if let supportedVerificationMethods = self.keyVerificationRequest.myMethods {
if let qrCodeData = self.qrCodeTransaction?.qrCodeData {
qrCodePlayloadData = self.qrCodeDataCoder.encode(qrCodeData)
} else {
qrCodePlayloadData = nil
}
canShowScanAction = self.canShowScanAction(from: supportedVerificationMethods)
} else {
qrCodePlayloadData = nil
canShowScanAction = false
}
let viewData = KeyVerificationVerifyByScanningViewData(qrCodeData: qrCodePlayloadData,
showScanAction: canShowScanAction)
self.update(viewState: .loaded(viewData: viewData))
self.registerTransactionDidStateChangeNotification()
}
private func canShowScanAction(from verificationMethods: [String]) -> Bool {
return verificationMethods.contains(MXKeyVerificationMethodQRCodeScan)
}
private func update(viewState: KeyVerificationVerifyByScanningViewState) {
self.viewDelegate?.keyVerificationVerifyByScanningViewModel(self, didUpdateViewState: viewState)
}
// MARK: QR code
private func scannedQRCode(payloadData: Data) {
self.scannedQRCodeData = self.qrCodeDataCoder.decode(payloadData)
let isQRCodeValid = self.scannedQRCodeData != nil
self.update(viewState: .scannedCodeValidated(isValid: isQRCodeValid))
}
private func acknowledgeScanOtherCode() {
guard let scannedQRCodeData = self.scannedQRCodeData else {
return
}
guard let qrCodeTransaction = self.qrCodeTransaction else {
return
}
qrCodeTransaction.userHasScannedOtherQrCodeData(scannedQRCodeData)
self.update(viewState: .loading)
}
private func acknowledgeOtherScannedMyCode(_ acknowledgeOtherScannedMyCode: Bool) {
guard let qrCodeTransaction = self.qrCodeTransaction else {
return
}
self.update(viewState: .loading)
qrCodeTransaction.otherUserScannedMyQrCode(acknowledgeOtherScannedMyCode)
}
private func removePendingQRCodeTransaction() {
guard let qrCodeTransaction = self.qrCodeTransaction else {
return
}
self.keyVerificationManager.removeQRCodeTransaction(withTransactionId: qrCodeTransaction.transactionId)
}
// MARK: SAS
private func startSASVerification() {
self.update(viewState: .loading)
self.session.crypto.keyVerificationManager.beginKeyVerification(from: self.keyVerificationRequest, method: MXKeyVerificationMethodSAS, success: { [weak self] (keyVerificationTransaction) in
guard let self = self else {
return
}
// Remove pending QR code transaction, as we are going to use SAS verification
self.removePendingQRCodeTransaction()
if keyVerificationTransaction is MXOutgoingSASTransaction == false {
NSLog("[KeyVerificationVerifyByScanningViewModel] SAS transaction should be outgoing")
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .error(KeyVerificationVerifyByScanningViewModelError.unknown))
}
}, failure: { [weak self] (error) in
guard let self = self else {
return
}
self.update(viewState: .error(error))
}
)
}
// MARK: - MXKeyVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: nil)
}
private func unregisterTransactionDidStateChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXKeyVerificationTransaction else {
return
}
guard let transactionDMEventId = transaction.dmEventId,
self.keyVerificationRequest.requestId == transactionDMEventId else {
return
}
if let sasTransaction = transaction as? MXSASTransaction {
self.sasTransactionDidStateChange(sasTransaction)
} else if let qrCodeTransaction = transaction as? MXQRCodeTransaction {
self.qrCodeTransactionDidStateChange(qrCodeTransaction)
}
}
private func sasTransactionDidStateChange(_ transaction: MXSASTransaction) {
switch transaction.state {
case MXSASTransactionStateShowSAS:
self.unregisterTransactionDidStateChangeNotification()
self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didStartSASVerificationWithTransaction: transaction)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelledByMe(reason))
default:
break
}
}
private func qrCodeTransactionDidStateChange(_ transaction: MXQRCodeTransaction) {
switch transaction.state {
case .verified:
self.unregisterTransactionDidStateChangeNotification()
self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(self)
case .qrScannedByOther:
self.update(viewState: .otherUserScannedMyCode)
case .cancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelled(reason))
case .cancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelledByMe(reason))
default:
break
}
}
}
@@ -0,0 +1,38 @@
// File created from ScreenTemplate
// $ createScreen.sh Verify KeyVerificationVerifyByScanning
/*
Copyright 2020 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
protocol KeyVerificationVerifyByScanningViewModelViewDelegate: class {
func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState)
}
protocol KeyVerificationVerifyByScanningViewModelCoordinatorDelegate: class {
func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType)
func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType)
func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction)
}
/// Protocol describing the view model used by `KeyVerificationVerifyByScanningViewController`
protocol KeyVerificationVerifyByScanningViewModelType {
var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate? { get set }
var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate? { get set }
func process(viewAction: KeyVerificationVerifyByScanningViewAction)
}
@@ -0,0 +1,35 @@
// File created from ScreenTemplate
// $ createScreen.sh Verify KeyVerificationVerifyByScanning
/*
Copyright 2020 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
struct KeyVerificationVerifyByScanningViewData {
let qrCodeData: Data?
let showScanAction: Bool
}
/// KeyVerificationVerifyByScanningViewController view state
enum KeyVerificationVerifyByScanningViewState {
case loading
case loaded(viewData: KeyVerificationVerifyByScanningViewData)
case scannedCodeValidated(isValid: Bool)
case otherUserScannedMyCode
case cancelled(MXTransactionCancelCode)
case cancelledByMe(MXTransactionCancelCode)
case error(Error)
}