Implement key backup setup recovery key scene with view controller, view model and coordinator

This commit is contained in:
SBiOSoftWhare
2019-01-22 11:04:06 +01:00
parent 4511d06743
commit e947ed8452
9 changed files with 748 additions and 3 deletions
@@ -0,0 +1,71 @@
/*
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
protocol KeyBackupSetupRecoveryKeyCoordinatorDelegate: class {
func keyBackupSetupRecoveryKeyCoordinatorDidCreateBackup(_ keyBackupSetupRecoveryKeyCoordinator: KeyBackupSetupRecoveryKeyCoordinatorType)
func keyBackupSetupRecoveryKeyCoordinatorDidCancel(_ keyBackupSetupRecoveryKeyCoordinator: KeyBackupSetupRecoveryKeyCoordinatorType)
}
final class KeyBackupSetupRecoveryKeyCoordinator: KeyBackupSetupRecoveryKeyCoordinatorType {
// MARK: - Properties
// MARK: Private
private var keyBackupSetupRecoveryKeyViewModel: KeyBackupSetupRecoveryKeyViewModelType
private let keyBackupSetupRecoveryKeyViewController: KeyBackupSetupRecoveryKeyViewController
// MARK: Public
var childCoordinators: [Coordinator] = []
weak var delegate: KeyBackupSetupRecoveryKeyCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, megolmBackupCreationInfo: MXMegolmBackupCreationInfo) {
let keyBackup = MXKeyBackup(matrixSession: session)
let keyBackupSetupRecoveryKeyViewModel = KeyBackupSetupRecoveryKeyViewModel(keyBackup: keyBackup, megolmBackupCreationInfo: megolmBackupCreationInfo)
let keyBackupSetupRecoveryKeyViewController = KeyBackupSetupRecoveryKeyViewController.instantiate(with: keyBackupSetupRecoveryKeyViewModel)
self.keyBackupSetupRecoveryKeyViewModel = keyBackupSetupRecoveryKeyViewModel
self.keyBackupSetupRecoveryKeyViewController = keyBackupSetupRecoveryKeyViewController
}
// MARK: - Public methods
func start() {
self.keyBackupSetupRecoveryKeyViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.keyBackupSetupRecoveryKeyViewController
}
}
// MARK: - KeyBackupSetupRecoveryKeyViewModelCoordinatorDelegate
extension KeyBackupSetupRecoveryKeyCoordinator: KeyBackupSetupRecoveryKeyViewModelCoordinatorDelegate {
func keyBackupSetupRecoveryKeyViewModelDidCancel(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType) {
self.delegate?.keyBackupSetupRecoveryKeyCoordinatorDidCancel(self)
}
func keyBackupSetupRecoveryKeyViewModelDidCreateBackup(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType) {
self.delegate?.keyBackupSetupRecoveryKeyCoordinatorDidCreateBackup(self)
}
}
@@ -0,0 +1,21 @@
/*
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
/// `KeyBackupSetupRecoveryKeyCoordinatorType` is a protocol describing a Coordinator that handle key backup setup recovery key navigation flow.
protocol KeyBackupSetupRecoveryKeyCoordinatorType: Coordinator, Presentable {
}
@@ -0,0 +1,25 @@
/*
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
/// KeyBackupSetupRecoveryKeyViewController view actions exposed to view model
enum KeyBackupSetupRecoveryKeyViewAction {
case madeCopy
case skip
case skipAlertSkip
case skipAlertContinue
}
@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="7tU-4q-Tiu">
<device id="retina5_9" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Key Backup Setup Recovery Key View Controller-->
<scene sceneID="TEb-QH-dId">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="7tU-4q-Tiu" customClass="KeyBackupSetupRecoveryKeyViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="1HX-5t-MPe">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qIs-mh-48y">
<rect key="frame" x="0.0" y="44" width="375" height="768"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JzB-pp-kxM">
<rect key="frame" x="0.0" y="0.0" width="375" height="440.66666666666669"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="af2-M1-8eQ">
<rect key="frame" x="0.0" y="0.0" width="375" height="440.66666666666669"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PuO-1b-aTN">
<rect key="frame" x="20" y="40.000000000000007" width="335" height="89.666666666666686"/>
<string key="text">Secure your encrypted message history with a Recovery Passphrase.
Youll need it if you log out, or lose access to this device.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kdt-vE-UuX">
<rect key="frame" x="0.0" y="169.66666666666666" width="375" height="170.99999999999997"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="rne-yg-sYg">
<rect key="frame" x="0.0" y="0.0" width="375" height="171"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jkn-Y7-xeS">
<rect key="frame" x="0.0" y="0.0" width="375" height="100"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Recovery Key" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="U4Z-pE-shF">
<rect key="frame" x="20" y="20" width="335" height="30.666666666666671"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="HHWJ-Y8DK-OD04-BQEN-FQ4V-M4O8" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jsl-Yk-BMr">
<rect key="frame" x="20" y="60.666666666666664" width="335" height="19.333333333333336"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Jsl-Yk-BMr" firstAttribute="leading" secondItem="U4Z-pE-shF" secondAttribute="leading" id="1eL-CW-lcd"/>
<constraint firstItem="U4Z-pE-shF" firstAttribute="top" secondItem="jkn-Y7-xeS" secondAttribute="top" constant="20" id="Ky1-hy-8aS"/>
<constraint firstItem="Jsl-Yk-BMr" firstAttribute="top" secondItem="U4Z-pE-shF" secondAttribute="bottom" constant="10" id="L4b-Ws-p2l"/>
<constraint firstItem="Jsl-Yk-BMr" firstAttribute="trailing" secondItem="U4Z-pE-shF" secondAttribute="trailing" id="Rnq-ta-med"/>
<constraint firstAttribute="bottom" secondItem="Jsl-Yk-BMr" secondAttribute="bottom" constant="20" id="U1F-ql-koE"/>
<constraint firstItem="U4Z-pE-shF" firstAttribute="leading" secondItem="jkn-Y7-xeS" secondAttribute="leading" constant="20" id="d1k-sS-f5J"/>
<constraint firstAttribute="trailing" secondItem="U4Z-pE-shF" secondAttribute="trailing" constant="20" id="pye-BM-Ekk"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9PN-LB-l9L">
<rect key="frame" x="0.0" y="100.00000000000003" width="375" height="1"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WUA-O8-9NI">
<rect key="frame" x="5" y="0.0" width="365" height="1"/>
<color key="backgroundColor" red="0.93333333330000001" green="0.93725490199999995" blue="0.93725490199999995" alpha="1" colorSpace="calibratedRGB"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="WUA-O8-9NI" firstAttribute="top" secondItem="9PN-LB-l9L" secondAttribute="top" id="0UH-jo-wFN"/>
<constraint firstAttribute="trailing" secondItem="WUA-O8-9NI" secondAttribute="trailing" constant="5" id="1ex-qO-wLx"/>
<constraint firstAttribute="height" constant="1" id="EhR-0L-zBg"/>
<constraint firstItem="WUA-O8-9NI" firstAttribute="leading" secondItem="9PN-LB-l9L" secondAttribute="leading" constant="5" id="aou-FT-2zV"/>
<constraint firstAttribute="bottom" secondItem="WUA-O8-9NI" secondAttribute="bottom" id="pKN-UK-55m"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Qen-8q-fND">
<rect key="frame" x="0.0" y="101.00000000000003" width="375" height="70"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="YNX-yH-3rP">
<rect key="frame" x="0.0" y="10" width="375" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="kfi-J3-DaX"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
<state key="normal" title="Make a copy">
<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="makeCopyButtonAction:" destination="7tU-4q-Tiu" eventType="touchUpInside" id="xBq-Dp-bUL"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="YNX-yH-3rP" firstAttribute="leading" secondItem="Qen-8q-fND" secondAttribute="leading" id="6m3-qx-s76"/>
<constraint firstItem="YNX-yH-3rP" firstAttribute="top" secondItem="Qen-8q-fND" secondAttribute="top" constant="10" id="STB-nV-X2q"/>
<constraint firstAttribute="bottom" secondItem="YNX-yH-3rP" secondAttribute="bottom" constant="10" id="ZxP-hG-WWA"/>
<constraint firstAttribute="trailing" secondItem="YNX-yH-3rP" secondAttribute="trailing" id="ivr-c9-Srh"/>
</constraints>
</view>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="rne-yg-sYg" secondAttribute="trailing" id="ERR-Gc-L6O"/>
<constraint firstItem="Qen-8q-fND" firstAttribute="width" secondItem="kdt-vE-UuX" secondAttribute="width" id="JBi-vC-m7B"/>
<constraint firstAttribute="bottom" secondItem="rne-yg-sYg" secondAttribute="bottom" id="Ua1-YG-MSP"/>
<constraint firstItem="9PN-LB-l9L" firstAttribute="width" secondItem="kdt-vE-UuX" secondAttribute="width" id="ew1-RD-bYJ"/>
<constraint firstItem="jkn-Y7-xeS" firstAttribute="width" secondItem="kdt-vE-UuX" secondAttribute="width" id="hkd-OA-57j"/>
<constraint firstItem="rne-yg-sYg" firstAttribute="leading" secondItem="kdt-vE-UuX" secondAttribute="leading" id="p9Y-sw-8bp"/>
<constraint firstItem="rne-yg-sYg" firstAttribute="top" secondItem="kdt-vE-UuX" secondAttribute="top" id="yoy-PH-8FQ"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cfN-uP-hjj">
<rect key="frame" x="0.0" y="370.66666666666669" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VIn-AF-W4c">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="clD-aX-gE4"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" title="I've made a copy">
<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="madeCopyButtonAction:" destination="7tU-4q-Tiu" eventType="touchUpInside" id="U0n-xV-2RC"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="VIn-AF-W4c" secondAttribute="bottom" id="bwp-sq-oBO"/>
<constraint firstAttribute="trailing" secondItem="VIn-AF-W4c" secondAttribute="trailing" id="fYt-2E-Q9a"/>
<constraint firstItem="VIn-AF-W4c" firstAttribute="leading" secondItem="cfN-uP-hjj" secondAttribute="leading" id="gTK-So-teO"/>
<constraint firstItem="VIn-AF-W4c" firstAttribute="top" secondItem="cfN-uP-hjj" secondAttribute="top" id="oE0-9f-N3Q"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="PuO-1b-aTN" firstAttribute="top" secondItem="af2-M1-8eQ" secondAttribute="top" constant="40" id="1DU-I9-O6E"/>
<constraint firstAttribute="bottom" secondItem="cfN-uP-hjj" secondAttribute="bottom" constant="20" id="1Qi-R7-EKJ"/>
<constraint firstAttribute="trailing" secondItem="PuO-1b-aTN" secondAttribute="trailing" constant="20" id="DnP-Um-adC"/>
<constraint firstAttribute="trailing" secondItem="cfN-uP-hjj" secondAttribute="trailing" id="I7p-F2-zJV"/>
<constraint firstAttribute="width" priority="750" constant="500" id="IeN-Uu-4Fs"/>
<constraint firstItem="kdt-vE-UuX" firstAttribute="top" secondItem="PuO-1b-aTN" secondAttribute="bottom" constant="40" id="Itt-CH-nZd"/>
<constraint firstItem="PuO-1b-aTN" firstAttribute="leading" secondItem="af2-M1-8eQ" secondAttribute="leading" constant="20" id="OF9-Xm-4hp"/>
<constraint firstItem="cfN-uP-hjj" firstAttribute="top" secondItem="kdt-vE-UuX" secondAttribute="bottom" constant="30" id="Z2f-Ib-HHZ"/>
<constraint firstAttribute="trailing" secondItem="kdt-vE-UuX" secondAttribute="trailing" id="bkk-of-6G3"/>
<constraint firstItem="kdt-vE-UuX" firstAttribute="leading" secondItem="af2-M1-8eQ" secondAttribute="leading" id="iX1-gP-xfu"/>
<constraint firstItem="cfN-uP-hjj" firstAttribute="leading" secondItem="af2-M1-8eQ" secondAttribute="leading" id="pdQ-hN-7Yq"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="af2-M1-8eQ" firstAttribute="top" secondItem="JzB-pp-kxM" secondAttribute="top" id="3IX-cD-2XH"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="af2-M1-8eQ" secondAttribute="trailing" id="d22-rl-hi0"/>
<constraint firstItem="af2-M1-8eQ" firstAttribute="centerX" secondItem="JzB-pp-kxM" secondAttribute="centerX" id="jRG-CF-RAt"/>
<constraint firstItem="af2-M1-8eQ" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="JzB-pp-kxM" secondAttribute="leading" id="lFq-J6-ibs"/>
<constraint firstAttribute="bottom" secondItem="af2-M1-8eQ" secondAttribute="bottom" id="wBx-lS-blw"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="JzB-pp-kxM" secondAttribute="trailing" id="8Nr-e6-rmn"/>
<constraint firstItem="JzB-pp-kxM" firstAttribute="width" secondItem="qIs-mh-48y" secondAttribute="width" id="KJr-CH-TVQ"/>
<constraint firstAttribute="bottom" secondItem="JzB-pp-kxM" secondAttribute="bottom" id="eqH-1c-gCN"/>
<constraint firstItem="JzB-pp-kxM" firstAttribute="top" secondItem="qIs-mh-48y" secondAttribute="top" id="oJt-Jz-YxN"/>
<constraint firstItem="JzB-pp-kxM" firstAttribute="leading" secondItem="qIs-mh-48y" secondAttribute="leading" id="yKR-m4-TDD"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="qIs-mh-48y" firstAttribute="leading" secondItem="j1h-pE-7aN" secondAttribute="leading" id="ZjG-fO-w4K"/>
<constraint firstAttribute="bottom" secondItem="qIs-mh-48y" secondAttribute="bottom" id="aQz-js-pqp"/>
<constraint firstItem="j1h-pE-7aN" firstAttribute="top" secondItem="qIs-mh-48y" secondAttribute="top" id="gP9-XY-TF7"/>
<constraint firstItem="j1h-pE-7aN" firstAttribute="trailing" secondItem="qIs-mh-48y" secondAttribute="trailing" id="jEW-fP-dnN"/>
</constraints>
<viewLayoutGuide key="safeArea" id="j1h-pE-7aN"/>
</view>
<connections>
<outlet property="informationLabel" destination="PuO-1b-aTN" id="JAl-aJ-P3g"/>
<outlet property="madeCopyButton" destination="VIn-AF-W4c" id="Pxt-1x-pNg"/>
<outlet property="madeCopyButtonBackgroundView" destination="cfN-uP-hjj" id="cYM-6G-5Al"/>
<outlet property="makeCopyButton" destination="YNX-yH-3rP" id="H5L-XH-zhz"/>
<outlet property="recoveryKeyBackgroundView" destination="kdt-vE-UuX" id="gcd-SN-Wc2"/>
<outlet property="recoveryKeyLabel" destination="Jsl-Yk-BMr" id="7ym-2H-BoZ"/>
<outlet property="recoveryKeyTitleLabel" destination="U4Z-pE-shF" id="vx1-WZ-wbu"/>
<outlet property="separatorView" destination="WUA-O8-9NI" id="ndh-dA-rBB"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="tCQ-kF-dqC" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2940" y="-565.27093596059115"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,234 @@
/*
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 KeyBackupSetupRecoveryKeyViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let animationDuration: TimeInterval = 0.3
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var recoveryKeyBackgroundView: UIView!
@IBOutlet private weak var recoveryKeyTitleLabel: UILabel!
@IBOutlet private weak var recoveryKeyLabel: UILabel!
@IBOutlet private weak var separatorView: UIView!
@IBOutlet private weak var makeCopyButton: UIButton!
@IBOutlet private weak var madeCopyButtonBackgroundView: UIView!
@IBOutlet private weak var madeCopyButton: UIButton!
// MARK: Private
private var theme: Theme!
private var hasMadeACopy: Bool = false
private var viewModel: KeyBackupSetupRecoveryKeyViewModelType!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private weak var skipAlertController: UIAlertController?
// MARK: - Setup
class func instantiate(with viewModel: KeyBackupSetupRecoveryKeyViewModelType) -> KeyBackupSetupRecoveryKeyViewController {
let viewController = StoryboardScene.KeyBackupSetupRecoveryKeyViewController.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.keyBackupSetupTitle
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.setupViews()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
}
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.informationLabel.textColor = theme.textPrimaryColor
self.recoveryKeyBackgroundView.backgroundColor = theme.backgroundColor
self.recoveryKeyTitleLabel.textColor = theme.textPrimaryColor
self.recoveryKeyLabel.textColor = theme.textPrimaryColor
self.separatorView.backgroundColor = theme.separatorColor
theme.applyStyle(onButton: self.makeCopyButton)
self.madeCopyButtonBackgroundView.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.madeCopyButton)
}
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 skipBarButtonItem = MXKBarButtonItem(title: VectorL10n.keyBackupSetupSkipAction, style: .plain) { [weak self] in
self?.skipButtonAction()
}
self.navigationItem.rightBarButtonItem = skipBarButtonItem
self.informationLabel.text = VectorL10n.keyBackupSetupRecoveryKeyInfo
self.recoveryKeyTitleLabel.text = VectorL10n.keyBackupSetupRecoveryKeyRecoveryKeyTitle
self.recoveryKeyLabel.text = self.viewModel.recoveryKey
self.makeCopyButton.setTitle(VectorL10n.keyBackupSetupRecoveryKeyMakeCopyAction, for: .normal)
self.madeCopyButton.setTitle(VectorL10n.keyBackupSetupRecoveryKeyMadeCopyAction, for: .normal)
self.updateMadeCopyButton()
}
private func shareRecoveryKey() {
// Set up activity view controller
let activityItems: [Any] = [ self.viewModel.recoveryKey ]
let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
activityViewController.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
// Enable made copy button only if user has selected an activity item
if completed {
self.hasMadeACopy = true
self.updateMadeCopyButton()
}
}
// Configure source view when activity view controller is presented with a popover
if let popoverPresentationController = activityViewController.popoverPresentationController {
popoverPresentationController.sourceView = self.makeCopyButton
popoverPresentationController.sourceRect = self.makeCopyButton.bounds
popoverPresentationController.permittedArrowDirections = [.down, .up]
}
self.present(activityViewController, animated: true)
}
private func updateMadeCopyButton() {
self.madeCopyButton.isEnabled = self.hasMadeACopy
}
private func render(viewState: KeyBackupSetupRecoveryKeyViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderLoaded()
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.view.endEditing(true)
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
private func render(error: Error) {
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func showSkipAlert() {
guard self.skipAlertController == nil else {
return
}
let alertController = UIAlertController(title: VectorL10n.keyBackupSetupSkipAlertTitle,
message: VectorL10n.keyBackupSetupSkipAlertMessage,
preferredStyle:.alert)
alertController.addAction(UIAlertAction(title: VectorL10n.continue, style: .cancel, handler: { action in
self.viewModel.process(viewAction: .skipAlertContinue)
}))
alertController.addAction(UIAlertAction(title: VectorL10n.keyBackupSetupSkipAlertSkipAction, style: .default, handler: { action in
self.viewModel.process(viewAction: .skipAlertSkip)
}))
self.present(alertController, animated: true, completion: nil)
self.skipAlertController = alertController
}
// MARK: - Actions
@IBAction private func makeCopyButtonAction(_ sender: Any) {
self.shareRecoveryKey()
}
@IBAction private func madeCopyButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .madeCopy)
}
private func skipButtonAction() {
self.viewModel.process(viewAction: .skip)
}
}
// MARK: - KeyBackupSetupRecoveryKeyViewModelViewDelegate
extension KeyBackupSetupRecoveryKeyViewController: KeyBackupSetupRecoveryKeyViewModelViewDelegate {
func keyBackupSetupRecoveryKeyViewModel(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType, didUpdateViewState viewSate: KeyBackupSetupRecoveryKeyViewState) {
self.render(viewState: viewSate)
}
func keyBackupSetupPassphraseViewModelShowSkipAlert(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType) {
self.showSkipAlert()
}
}
@@ -0,0 +1,102 @@
/*
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 KeyBackupSetupRecoveryKeyViewModel: KeyBackupSetupRecoveryKeyViewModelType {
// MARK: - Properties
// MARK: Private
private let megolmBackupCreationInfo: MXMegolmBackupCreationInfo
private let keyBackup: MXKeyBackup
private let coordinatorDelegateQueue: OperationQueue
private var createKeyBackupOperation: MXHTTPOperation?
// MARK: Public
let recoveryKey: String
weak var viewDelegate: KeyBackupSetupRecoveryKeyViewModelViewDelegate?
weak var coordinatorDelegate: KeyBackupSetupRecoveryKeyViewModelCoordinatorDelegate?
// MARK: - Setup
init(keyBackup: MXKeyBackup, megolmBackupCreationInfo: MXMegolmBackupCreationInfo) {
self.megolmBackupCreationInfo = megolmBackupCreationInfo
self.recoveryKey = megolmBackupCreationInfo.recoveryKey
self.keyBackup = keyBackup
let coordinatorDelegateQueue = OperationQueue()
coordinatorDelegateQueue.name = "KeyBackupSetupRecoveryKeyViewModel.coordinatorDelegateQueue"
coordinatorDelegateQueue.maxConcurrentOperationCount = 1
self.coordinatorDelegateQueue = coordinatorDelegateQueue
}
deinit {
self.createKeyBackupOperation?.cancel()
}
// MARK: - Public
func process(viewAction: KeyBackupSetupRecoveryKeyViewAction) {
switch viewAction {
case .madeCopy:
self.createBackup()
case .skip:
self.pauseCoordinatorOperations()
self.viewDelegate?.keyBackupSetupPassphraseViewModelShowSkipAlert(self)
case.skipAlertContinue:
self.resumeCoordinatorOperations()
case.skipAlertSkip:
self.createKeyBackupOperation?.cancel()
self.cancelCoordinatorOperations()
self.coordinatorDelegate?.keyBackupSetupRecoveryKeyViewModelDidCancel(self)
}
}
// MARK: - Private
func createBackup() {
self.viewDelegate?.keyBackupSetupRecoveryKeyViewModel(self, didUpdateViewState: .loading)
self.keyBackup.createKeyBackupVersion(self.megolmBackupCreationInfo, success: { [weak self] (keyBackupVersion) in
guard let sself = self else {
return
}
sself.viewDelegate?.keyBackupSetupRecoveryKeyViewModel(sself, didUpdateViewState: .loaded)
sself.coordinatorDelegate?.keyBackupSetupRecoveryKeyViewModelDidCreateBackup(sself)
}, failure: { [weak self] error in
guard let sself = self else {
return
}
sself.viewDelegate?.keyBackupSetupRecoveryKeyViewModel(sself, didUpdateViewState: .error(error))
})
}
private func pauseCoordinatorOperations() {
self.coordinatorDelegateQueue.isSuspended = true
}
private func resumeCoordinatorOperations() {
self.coordinatorDelegateQueue.isSuspended = false
}
private func cancelCoordinatorOperations() {
self.coordinatorDelegateQueue.cancelAllOperations()
}
}
@@ -0,0 +1,38 @@
/*
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 KeyBackupSetupRecoveryKeyViewModelViewDelegate: class {
func keyBackupSetupRecoveryKeyViewModel(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType, didUpdateViewState viewSate: KeyBackupSetupRecoveryKeyViewState)
func keyBackupSetupPassphraseViewModelShowSkipAlert(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType)
}
protocol KeyBackupSetupRecoveryKeyViewModelCoordinatorDelegate: class {
func keyBackupSetupRecoveryKeyViewModelDidCreateBackup(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType)
func keyBackupSetupRecoveryKeyViewModelDidCancel(_ viewModel: KeyBackupSetupRecoveryKeyViewModelType)
}
/// Protocol describing the view model used by `KeyBackupSetupRecoveryKeyViewController`
protocol KeyBackupSetupRecoveryKeyViewModelType {
var recoveryKey: String { get }
var viewDelegate: KeyBackupSetupRecoveryKeyViewModelViewDelegate? { get set }
var coordinatorDelegate: KeyBackupSetupRecoveryKeyViewModelCoordinatorDelegate? { get set }
func process(viewAction: KeyBackupSetupRecoveryKeyViewAction)
}
@@ -0,0 +1,24 @@
/*
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
/// KeyBackupSetupRecoveryKeyViewController view state
enum KeyBackupSetupRecoveryKeyViewState {
case loading
case loaded
case error(Error)
}