KeyVerification: Add new QR code confirmation screen.

This commit is contained in:
SBiOSoftWhare
2020-04-16 18:42:14 +02:00
parent 57e17815be
commit b6302ce07f
18 changed files with 863 additions and 0 deletions
@@ -0,0 +1,76 @@
// File created from ScreenTemplate
// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation
/*
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
enum KeyVerificationScanning {
case scannedOtherQRCode(MXQRCodeData)
case myQRCodeScanned
}
final class KeyVerificationScanConfirmationCoordinator: KeyVerificationScanConfirmationCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var keyVerificationScanConfirmationViewModel: KeyVerificationScanConfirmationViewModelType
private let keyVerificationScanConfirmationViewController: KeyVerificationScanConfirmationViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: KeyVerificationScanConfirmationCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, transaction: MXQRCodeTransaction, codeScanning: KeyVerificationScanning, verificationKind: KeyVerificationKind) {
self.session = session
let keyVerificationScanConfirmationViewModel = KeyVerificationScanConfirmationViewModel(session: self.session, transaction: transaction, codeScanning: codeScanning, verificationKind: verificationKind)
let keyVerificationScanConfirmationViewController = KeyVerificationScanConfirmationViewController.instantiate(with: keyVerificationScanConfirmationViewModel)
self.keyVerificationScanConfirmationViewModel = keyVerificationScanConfirmationViewModel
self.keyVerificationScanConfirmationViewController = keyVerificationScanConfirmationViewController
}
// MARK: - Public methods
func start() {
self.keyVerificationScanConfirmationViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.keyVerificationScanConfirmationViewController
}
}
// MARK: - KeyVerificationScanConfirmationViewModelCoordinatorDelegate
extension KeyVerificationScanConfirmationCoordinator: KeyVerificationScanConfirmationViewModelCoordinatorDelegate {
func keyVerificationScanConfirmationViewModelDidComplete(_ viewModel: KeyVerificationScanConfirmationViewModelType) {
self.delegate?.keyVerificationScanConfirmationCoordinatorDidComplete(self)
}
func keyVerificationScanConfirmationViewModelDidCancel(_ viewModel: KeyVerificationScanConfirmationViewModelType) {
self.delegate?.keyVerificationScanConfirmationCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation
/*
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 KeyVerificationScanConfirmationCoordinatorDelegate: class {
func keyVerificationScanConfirmationCoordinatorDidComplete(_ coordinator: KeyVerificationScanConfirmationCoordinatorType)
func keyVerificationScanConfirmationCoordinatorDidCancel(_ coordinator: KeyVerificationScanConfirmationCoordinatorType)
}
/// `KeyVerificationScanConfirmationCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol KeyVerificationScanConfirmationCoordinatorType: Coordinator, Presentable {
var delegate: KeyVerificationScanConfirmationCoordinatorDelegate? { get }
}
@@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation
/*
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
/// KeyVerificationScanConfirmationViewController view actions exposed to view model
enum KeyVerificationScanConfirmationViewAction {
case loadData
case acknowledgeOtherScannedMyCode(_ otherScannedMyCode: Bool)
case cancel
}
@@ -0,0 +1,172 @@
<?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="retina6_1" 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 Scan Confirmation View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="KeyVerificationScanConfirmationViewController" 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="414" height="896"/>
<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="44" width="414" height="852"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="36d-db-nP0">
<rect key="frame" x="0.0" y="0.0" width="414" height="376.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="A5W-J0-Sfd">
<rect key="frame" x="0.0" y="0.0" width="414" height="376.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Almost there!" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FO8-rD-Y21">
<rect key="frame" x="20" y="40" width="374" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="key_verification_success_shield" translatesAutoresizingMaskIntoConstraints="NO" id="Uad-3w-cQ4">
<rect key="frame" x="147" y="88" width="120" height="120"/>
<constraints>
<constraint firstAttribute="width" constant="120" id="3RB-i8-S1k"/>
<constraint firstAttribute="width" secondItem="Uad-3w-cQ4" secondAttribute="height" multiplier="1:1" id="vrj-Sp-Wpu"/>
</constraints>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="t0L-ns-MGv">
<rect key="frame" x="20" y="238" width="374" height="118.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Waiting..." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hwq-X6-Gl2">
<rect key="frame" x="151.5" y="0.0" width="71.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.3803921569" green="0.43921568630000002" blue="0.5450980392" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="F6b-sv-4cq">
<rect key="frame" x="0.0" y="30.5" width="374" height="88"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Is showing the same shield?" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kf8-7j-cc5">
<rect key="frame" x="20" y="0.0" width="334" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="8Ou-Xh-0rC">
<rect key="frame" x="0.0" y="48" width="374" height="40"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kBt-2D-25V">
<rect key="frame" x="0.0" y="0.0" width="182" height="40"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
<state key="normal" title="No">
<color key="titleColor" red="1" green="0.29411764709999999" blue="0.33333333329999998" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="rejectButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="qCV-Oh-YAG"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="03A-fe-h3s">
<rect key="frame" x="192" y="0.0" width="182" height="40"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
<state key="normal" title="Yes">
<color key="titleColor" red="0.01176470588" green="0.70196078429999997" blue="0.50588235290000005" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="confirmButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="IFj-mO-ebJ"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="kBt-2D-25V" firstAttribute="height" secondItem="8Ou-Xh-0rC" secondAttribute="height" priority="999" id="2aF-Ln-OuB"/>
<constraint firstItem="03A-fe-h3s" firstAttribute="height" secondItem="8Ou-Xh-0rC" secondAttribute="height" priority="999" id="9Fl-fu-Ldy"/>
<constraint firstAttribute="height" priority="750" constant="40" id="aKz-Qr-c6u"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="kf8-7j-cc5" firstAttribute="leading" secondItem="F6b-sv-4cq" secondAttribute="leading" constant="20" id="2yh-8a-QGS"/>
<constraint firstAttribute="height" priority="250" id="GKj-m6-fxU"/>
<constraint firstAttribute="bottom" secondItem="8Ou-Xh-0rC" secondAttribute="bottom" id="PwU-nj-Ybq"/>
<constraint firstItem="8Ou-Xh-0rC" firstAttribute="top" secondItem="kf8-7j-cc5" secondAttribute="bottom" constant="30" id="cn0-yI-1uc"/>
<constraint firstItem="8Ou-Xh-0rC" firstAttribute="centerX" secondItem="F6b-sv-4cq" secondAttribute="centerX" id="cqV-7V-njr"/>
<constraint firstItem="kf8-7j-cc5" firstAttribute="top" secondItem="F6b-sv-4cq" secondAttribute="top" id="fk7-35-vPQ"/>
<constraint firstAttribute="trailing" secondItem="8Ou-Xh-0rC" secondAttribute="trailing" id="nU1-G8-Ph6"/>
<constraint firstItem="8Ou-Xh-0rC" firstAttribute="leading" secondItem="F6b-sv-4cq" secondAttribute="leading" id="ydL-Vi-9wL"/>
<constraint firstAttribute="trailing" secondItem="kf8-7j-cc5" secondAttribute="trailing" constant="20" id="zFS-EY-yzQ"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="F6b-sv-4cq" firstAttribute="width" secondItem="t0L-ns-MGv" secondAttribute="width" id="DOp-os-mCG"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Uad-3w-cQ4" firstAttribute="centerX" secondItem="A5W-J0-Sfd" secondAttribute="centerX" id="9qA-xE-FuB"/>
<constraint firstAttribute="bottom" secondItem="t0L-ns-MGv" secondAttribute="bottom" constant="20" id="BiO-bi-ZSe"/>
<constraint firstAttribute="trailing" secondItem="FO8-rD-Y21" secondAttribute="trailing" constant="20" id="K4J-VT-PtB"/>
<constraint firstItem="FO8-rD-Y21" firstAttribute="leading" secondItem="A5W-J0-Sfd" secondAttribute="leading" constant="20" id="TBg-VP-Ogk"/>
<constraint firstAttribute="trailing" secondItem="t0L-ns-MGv" secondAttribute="trailing" constant="20" id="X3d-TI-hpD"/>
<constraint firstAttribute="width" priority="750" constant="500" id="bf9-gx-A9v"/>
<constraint firstItem="t0L-ns-MGv" firstAttribute="top" secondItem="Uad-3w-cQ4" secondAttribute="bottom" constant="30" id="iid-Cr-vcT"/>
<constraint firstItem="FO8-rD-Y21" firstAttribute="top" secondItem="A5W-J0-Sfd" secondAttribute="top" constant="40" id="tCz-QS-z3b"/>
<constraint firstItem="Uad-3w-cQ4" firstAttribute="top" secondItem="FO8-rD-Y21" secondAttribute="bottom" constant="30" id="xAe-qf-pOf"/>
<constraint firstItem="t0L-ns-MGv" firstAttribute="leading" secondItem="A5W-J0-Sfd" secondAttribute="leading" constant="20" id="xD5-z1-arO"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="A5W-J0-Sfd" firstAttribute="top" secondItem="36d-db-nP0" secondAttribute="top" id="AYb-Z5-QpJ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="A5W-J0-Sfd" secondAttribute="trailing" id="MlC-05-MUl"/>
<constraint firstItem="A5W-J0-Sfd" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="36d-db-nP0" secondAttribute="leading" id="NMb-JL-a8b"/>
<constraint firstAttribute="bottom" secondItem="A5W-J0-Sfd" secondAttribute="bottom" id="ynD-q2-fXQ"/>
<constraint firstItem="A5W-J0-Sfd" firstAttribute="centerX" secondItem="36d-db-nP0" secondAttribute="centerX" id="zjx-Lb-Y8e"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="36d-db-nP0" secondAttribute="trailing" id="3si-T4-awd"/>
<constraint firstAttribute="bottom" secondItem="36d-db-nP0" secondAttribute="bottom" id="785-0a-28p"/>
<constraint firstItem="36d-db-nP0" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="9cj-0J-0x7"/>
<constraint firstItem="36d-db-nP0" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="JyV-ui-HPz"/>
<constraint firstItem="36d-db-nP0" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="TXd-y3-ovp"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" 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="confirmButton" destination="03A-fe-h3s" id="lDk-ec-qrl"/>
<outlet property="rejectButton" destination="kBt-2D-25V" id="pS1-rh-isR"/>
<outlet property="scannedContentView" destination="F6b-sv-4cq" id="Avm-Uy-d0r"/>
<outlet property="scannedInformationLabel" destination="kf8-7j-cc5" id="eMm-Vz-yRr"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="titleLabel" destination="FO8-rD-Y21" id="vHJ-Co-DCJ"/>
<outlet property="waitingLabel" destination="hwq-X6-Gl2" id="Ygo-8s-HyH"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198" y="-647"/>
</scene>
</scenes>
<resources>
<image name="key_verification_success_shield" width="120" height="120"/>
</resources>
</document>
@@ -0,0 +1,241 @@
// File created from ScreenTemplate
// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation
/*
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 KeyVerificationScanConfirmationViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let buttonBackgroundColorAlpha: CGFloat = 0.2
static let buttonCornerRadius: CGFloat = 6.0
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var waitingLabel: UILabel!
@IBOutlet private weak var scannedContentView: UIView!
@IBOutlet private weak var scannedInformationLabel: UILabel!
@IBOutlet private weak var rejectButton: UIButton!
@IBOutlet private weak var confirmButton: UIButton!
// MARK: Private
private var viewModel: KeyVerificationScanConfirmationViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: KeyVerificationScanConfirmationViewModelType) -> KeyVerificationScanConfirmationViewController {
let viewController = StoryboardScene.KeyVerificationScanConfirmationViewController.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.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if self.scannedContentView.isHidden == false {
self.confirmButton.layer.cornerRadius = Constants.buttonCornerRadius
self.rejectButton.layer.cornerRadius = Constants.buttonCornerRadius
}
}
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.waitingLabel.textColor = theme.textSecondaryColor
self.scannedInformationLabel.textColor = theme.textPrimaryColor
self.confirmButton.vc_setBackgroundColor(theme.tintColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal)
self.rejectButton.vc_setBackgroundColor(theme.noticeColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal)
}
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.title = VectorL10n.keyVerificationVerifyQrCodeTitle
self.confirmButton.layer.masksToBounds = true
self.rejectButton.layer.masksToBounds = true
}
private func render(viewState: KeyVerificationScanConfirmationViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(let viewData):
self.renderLoaded(viewData: viewData)
case .error(let error):
self.render(error: error)
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: KeyVerificationScanConfirmationViewData) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.waitingLabel.isHidden = !viewData.isScanning
self.scannedContentView.isHidden = viewData.isScanning
var title: String
var waitingInfo: String?
var scannedInfo: String?
if viewData.isScanning {
title = VectorL10n.keyVerificationScanConfirmationScanningTitle
switch viewData.verificationKind {
case .device:
waitingInfo = VectorL10n.keyVerificationScanConfirmationScanningDeviceWaitingOther
case .user:
waitingInfo = VectorL10n.keyVerificationScanConfirmationScanningUserWaitingOther(viewData.otherDisplayName)
}
} else {
title = VectorL10n.keyVerificationScanConfirmationScannedTitle
switch viewData.verificationKind {
case .device:
scannedInfo = VectorL10n.keyVerificationScanConfirmationScannedDeviceInformation
case .user:
scannedInfo = VectorL10n.keyVerificationScanConfirmationScannedUserInformation(viewData.otherDisplayName)
}
}
self.titleLabel.text = title
self.waitingLabel.text = waitingInfo
self.scannedInformationLabel.text = scannedInfo
}
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 rejectButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(false))
}
@IBAction private func confirmButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(true))
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - KeyVerificationScanConfirmationViewModelViewDelegate
extension KeyVerificationScanConfirmationViewController: KeyVerificationScanConfirmationViewModelViewDelegate {
func keyVerificationScanConfirmationViewModel(_ viewModel: KeyVerificationScanConfirmationViewModelType, didUpdateViewState viewSate: KeyVerificationScanConfirmationViewState) {
self.render(viewState: viewSate)
}
}
@@ -0,0 +1,137 @@
// File created from ScreenTemplate
// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation
/*
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 KeyVerificationScanConfirmationViewModel: KeyVerificationScanConfirmationViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let transaction: MXQRCodeTransaction
private let codeScanning: KeyVerificationScanning
private let verificationKind: KeyVerificationKind
private var isScanning: Bool {
if case .scannedOtherQRCode = self.codeScanning {
return true
}
return false
}
// MARK: Public
weak var viewDelegate: KeyVerificationScanConfirmationViewModelViewDelegate?
weak var coordinatorDelegate: KeyVerificationScanConfirmationViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession,
transaction: MXQRCodeTransaction,
codeScanning: KeyVerificationScanning,
verificationKind: KeyVerificationKind) {
self.session = session
self.transaction = transaction
self.codeScanning = codeScanning
self.verificationKind = verificationKind
}
deinit {
}
// MARK: - Public
func process(viewAction: KeyVerificationScanConfirmationViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .acknowledgeOtherScannedMyCode(let otherHasScannedMyCode):
self.transaction.otherUserScannedMyQrCode(otherHasScannedMyCode)
if otherHasScannedMyCode == false { self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidCancel(self)
} else {
self.update(viewState: .loading)
}
case .cancel:
self.cancel()
}
}
// MARK: - Private
private func loadData() {
let otherUserId = self.transaction.otherUserId
let otherUser = self.session.user(withUserId: otherUserId)
let otherDisplayName = otherUser?.displayname ?? otherUserId
let viewData = KeyVerificationScanConfirmationViewData(isScanning: self.isScanning, verificationKind: self.verificationKind, otherDisplayName: otherDisplayName)
self.update(viewState: .loaded(viewData))
self.registerTransactionDidStateChangeNotification()
if case .scannedOtherQRCode(let qrCodeData) = self.codeScanning {
self.transaction.userHasScannedOtherQrCodeData(qrCodeData)
}
}
private func update(viewState: KeyVerificationScanConfirmationViewState) {
self.viewDelegate?.keyVerificationScanConfirmationViewModel(self, didUpdateViewState: viewState)
}
private func cancel() {
self.transaction.cancel(with: MXTransactionCancelCode.user())
self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidCancel(self)
}
// MARK: - MXKeyVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: self.transaction)
}
private func unregisterTransactionDidStateChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXQRCodeTransaction else {
return
}
switch transaction.state {
case .verified:
self.unregisterTransactionDidStateChangeNotification()
self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidComplete(self)
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,37 @@
// File created from ScreenTemplate
// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation
/*
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 KeyVerificationScanConfirmationViewModelViewDelegate: class {
func keyVerificationScanConfirmationViewModel(_ viewModel: KeyVerificationScanConfirmationViewModelType, didUpdateViewState viewSate: KeyVerificationScanConfirmationViewState)
}
protocol KeyVerificationScanConfirmationViewModelCoordinatorDelegate: class {
func keyVerificationScanConfirmationViewModelDidComplete(_ viewModel: KeyVerificationScanConfirmationViewModelType)
func keyVerificationScanConfirmationViewModelDidCancel(_ viewModel: KeyVerificationScanConfirmationViewModelType)
}
/// Protocol describing the view model used by `KeyVerificationScanConfirmationViewController`
protocol KeyVerificationScanConfirmationViewModelType {
var viewDelegate: KeyVerificationScanConfirmationViewModelViewDelegate? { get set }
var coordinatorDelegate: KeyVerificationScanConfirmationViewModelCoordinatorDelegate? { get set }
func process(viewAction: KeyVerificationScanConfirmationViewAction)
}
@@ -0,0 +1,34 @@
// File created from ScreenTemplate
// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation
/*
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 KeyVerificationScanConfirmationViewData {
let isScanning: Bool
let verificationKind: KeyVerificationKind
let otherDisplayName: String
}
/// KeyVerificationScanConfirmationViewController view state
enum KeyVerificationScanConfirmationViewState {
case loading
case loaded(_ viewData: KeyVerificationScanConfirmationViewData)
case cancelled(MXTransactionCancelCode)
case cancelledByMe(MXTransactionCancelCode)
case error(Error)
}