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 732ae10eb4
commit 25e8f7fccf
91 changed files with 819 additions and 803 deletions
@@ -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)
}