Secrets setup: Handle set up recovery passphrase screen.

This commit is contained in:
SBiOSoftWhare
2020-06-23 18:17:38 +02:00
parent 26f7102918
commit e347def7f7
9 changed files with 960 additions and 0 deletions
@@ -0,0 +1,73 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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 SecretsSetupRecoveryPassphraseCoordinator: SecretsSetupRecoveryPassphraseCoordinatorType {
// MARK: - Properties
// MARK: Private
private var secretsSetupRecoveryPassphraseViewModel: SecretsSetupRecoveryPassphraseViewModelType
private let secretsSetupRecoveryPassphraseViewController: SecretsSetupRecoveryPassphraseViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: SecretsSetupRecoveryPassphraseCoordinatorDelegate?
// MARK: - Setup
init(passphraseInput: SecretsSetupRecoveryPassphraseInput) {
let secretsSetupRecoveryPassphraseViewModel = SecretsSetupRecoveryPassphraseViewModel(passphraseInput: passphraseInput)
let secretsSetupRecoveryPassphraseViewController = SecretsSetupRecoveryPassphraseViewController.instantiate(with: secretsSetupRecoveryPassphraseViewModel)
self.secretsSetupRecoveryPassphraseViewModel = secretsSetupRecoveryPassphraseViewModel
self.secretsSetupRecoveryPassphraseViewController = secretsSetupRecoveryPassphraseViewController
}
// MARK: - Public methods
func start() {
self.secretsSetupRecoveryPassphraseViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.secretsSetupRecoveryPassphraseViewController
}
}
// MARK: - SecretsSetupRecoveryPassphraseViewModelCoordinatorDelegate
extension SecretsSetupRecoveryPassphraseCoordinator: SecretsSetupRecoveryPassphraseViewModelCoordinatorDelegate {
func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didEnterNewPassphrase passphrase: String) {
self.delegate?.secretsSetupRecoveryPassphraseCoordinator(self, didEnterNewPassphrase: passphrase)
}
func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didConfirmPassphrase passphrase: String) {
self.delegate?.secretsSetupRecoveryPassphraseCoordinator(self, didConfirmPassphrase: passphrase)
}
func secretsSetupRecoveryPassphraseViewModelDidCancel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType) {
self.delegate?.secretsSetupRecoveryPassphraseCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,30 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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 SecretsSetupRecoveryPassphraseCoordinatorDelegate: class {
func secretsSetupRecoveryPassphraseCoordinator(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType, didEnterNewPassphrase passphrase: String)
func secretsSetupRecoveryPassphraseCoordinator(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType, didConfirmPassphrase passphrase: String)
func secretsSetupRecoveryPassphraseCoordinatorDidCancel(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType)
}
/// `SecretsSetupRecoveryPassphraseCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol SecretsSetupRecoveryPassphraseCoordinatorType: Coordinator, Presentable {
var delegate: SecretsSetupRecoveryPassphraseCoordinatorDelegate? { get }
}
@@ -0,0 +1,24 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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 SecretsSetupRecoveryPassphraseInput {
case new
case confirm(_ passphrase: String)
}
@@ -0,0 +1,27 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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
/// SecretsSetupRecoveryPassphraseViewController view actions exposed to view model
enum SecretsSetupRecoveryPassphraseViewAction {
case loadData
case updatePassphrase(_ passphrase: String?)
case validate
case cancel
}
@@ -0,0 +1,285 @@
<?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="bIB-0i-ukm">
<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>
<!--Secrets Setup Recovery Passphrase View Controller-->
<scene sceneID="etY-7t-lyY">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="bIB-0i-ukm" customClass="SecretsSetupRecoveryPassphraseViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="D8T-l5-mMj">
<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="o4q-2F-ezk">
<rect key="frame" x="0.0" y="44" width="414" height="852"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dME-t7-qZD">
<rect key="frame" x="0.0" y="0.0" width="414" height="598"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NHb-J1-L5X">
<rect key="frame" x="0.0" y="0.0" width="414" height="598"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="secrets_setup_passphrase" translatesAutoresizingMaskIntoConstraints="NO" id="ZX3-oS-5DJ">
<rect key="frame" x="185" y="35" width="44" height="48"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="380-Pr-HJO"/>
<constraint firstAttribute="width" constant="44" id="Ypb-bz-jVe"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ecv-l3-ryP">
<rect key="frame" x="20" y="113" width="374" height="54"/>
<string key="text">Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5eP-6U-QZi">
<rect key="frame" x="0.0" y="207" width="414" height="251.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="mPf-Xv-wqH">
<rect key="frame" x="0.0" y="0.0" width="414" height="251.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4z6-DU-d2T">
<rect key="frame" x="0.0" y="0.0" width="414" height="78.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="enter" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iM1-le-WDv">
<rect key="frame" x="20" y="10" width="39.5" height="58.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Enter passphrase" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="wuc-Sr-qja">
<rect key="frame" x="79.5" y="14.5" width="280.5" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="oeq-kq-s6b"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="next" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="bIB-0i-ukm" id="ZTp-o9-59m"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H1x-SK-Bsn">
<rect key="frame" x="360" y="17.5" width="44" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="qC3-Mk-XiH"/>
<constraint firstAttribute="height" constant="44" id="uwx-Uv-7kY"/>
</constraints>
<state key="normal" image="reveal_password_button"/>
<connections>
<action selector="passphraseVisibilityButtonAction:" destination="bIB-0i-ukm" eventType="touchUpInside" id="cic-gC-Lfl"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="iM1-le-WDv" firstAttribute="leading" secondItem="4z6-DU-d2T" secondAttribute="leading" constant="20" id="5Qr-ay-AD3"/>
<constraint firstAttribute="trailing" secondItem="H1x-SK-Bsn" secondAttribute="trailing" constant="10" id="9bF-iX-wf7"/>
<constraint firstItem="iM1-le-WDv" firstAttribute="centerY" secondItem="4z6-DU-d2T" secondAttribute="centerY" id="E3g-fR-guv"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="wuc-Sr-qja" secondAttribute="bottom" constant="5" id="EF3-tn-aHa"/>
<constraint firstItem="H1x-SK-Bsn" firstAttribute="centerY" secondItem="wuc-Sr-qja" secondAttribute="centerY" id="QWg-IV-bRt"/>
<constraint firstAttribute="bottom" secondItem="iM1-le-WDv" secondAttribute="bottom" constant="10" id="VIu-zS-irW"/>
<constraint firstItem="wuc-Sr-qja" firstAttribute="top" relation="greaterThanOrEqual" secondItem="4z6-DU-d2T" secondAttribute="top" constant="5" id="WEZ-WG-H3c"/>
<constraint firstItem="wuc-Sr-qja" firstAttribute="leading" secondItem="iM1-le-WDv" secondAttribute="trailing" priority="750" constant="10" id="XG2-GF-pwv"/>
<constraint firstItem="H1x-SK-Bsn" firstAttribute="leading" secondItem="wuc-Sr-qja" secondAttribute="trailing" id="g2t-e4-oKl"/>
<constraint firstItem="iM1-le-WDv" firstAttribute="top" secondItem="4z6-DU-d2T" secondAttribute="top" constant="10" id="iKt-i6-jnP"/>
<constraint firstItem="iM1-le-WDv" firstAttribute="centerY" secondItem="wuc-Sr-qja" secondAttribute="centerY" id="tkC-ad-yyK"/>
<constraint firstItem="wuc-Sr-qja" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="iM1-le-WDv" secondAttribute="trailing" constant="20" id="uOM-mT-X0Z"/>
</constraints>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pQ6-Ke-kTc">
<rect key="frame" x="0.0" y="78.5" width="414" height="173"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="XEQ-L8-Tbl">
<rect key="frame" x="0.0" y="5" width="414" height="153"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="O4X-SY-HCD">
<rect key="frame" x="0.0" y="0.0" width="414" height="5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Ur-Gj-5yT" customClass="PasswordStrengthView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="20" y="0.0" width="374" height="5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="5" id="LGA-bA-pUr"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="7Ur-Gj-5yT" firstAttribute="top" secondItem="O4X-SY-HCD" secondAttribute="top" id="hAi-DD-d7R"/>
<constraint firstItem="7Ur-Gj-5yT" firstAttribute="leading" secondItem="O4X-SY-HCD" secondAttribute="leading" constant="20" id="qLK-02-VvY"/>
<constraint firstAttribute="bottom" secondItem="7Ur-Gj-5yT" secondAttribute="bottom" id="sye-7J-VNw"/>
<constraint firstAttribute="trailing" secondItem="7Ur-Gj-5yT" secondAttribute="trailing" constant="20" id="uYv-z7-RLE"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Yd2-sG-uwh">
<rect key="frame" x="0.0" y="20" width="414" height="133"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Try adding a word" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="A2u-5W-L5q">
<rect key="frame" x="20" y="0.0" width="374" height="133"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" priority="250" id="I6i-Jg-mZ3"/>
<constraint firstItem="A2u-5W-L5q" firstAttribute="top" secondItem="Yd2-sG-uwh" secondAttribute="top" id="Qib-J0-MO4"/>
<constraint firstAttribute="bottom" secondItem="A2u-5W-L5q" secondAttribute="bottom" id="UZc-rg-rAc"/>
<constraint firstAttribute="trailing" secondItem="A2u-5W-L5q" secondAttribute="trailing" constant="20" id="YYn-et-fHT"/>
<constraint firstItem="A2u-5W-L5q" firstAttribute="leading" secondItem="Yd2-sG-uwh" secondAttribute="leading" constant="20" id="bLx-1L-c23"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="O4X-SY-HCD" firstAttribute="width" secondItem="XEQ-L8-Tbl" secondAttribute="width" id="NRY-zi-9gP"/>
<constraint firstItem="Yd2-sG-uwh" firstAttribute="width" secondItem="XEQ-L8-Tbl" secondAttribute="width" id="Wxj-rm-tK6"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="XEQ-L8-Tbl" firstAttribute="leading" secondItem="pQ6-Ke-kTc" secondAttribute="leading" id="aVG-9g-4t9"/>
<constraint firstItem="XEQ-L8-Tbl" firstAttribute="top" secondItem="pQ6-Ke-kTc" secondAttribute="top" constant="5" id="qz3-J5-Jc0"/>
<constraint firstAttribute="bottom" secondItem="XEQ-L8-Tbl" secondAttribute="bottom" constant="15" id="tG9-Jd-R6N"/>
<constraint firstAttribute="trailing" secondItem="XEQ-L8-Tbl" secondAttribute="trailing" id="vAD-HS-KZy"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="pQ6-Ke-kTc" firstAttribute="width" secondItem="mPf-Xv-wqH" secondAttribute="width" id="FJz-yd-QPP"/>
<constraint firstItem="4z6-DU-d2T" firstAttribute="width" secondItem="mPf-Xv-wqH" secondAttribute="width" id="Tjm-Zw-JKh"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="mPf-Xv-wqH" firstAttribute="top" secondItem="5eP-6U-QZi" secondAttribute="top" id="1fu-vG-gQo"/>
<constraint firstAttribute="trailing" secondItem="mPf-Xv-wqH" secondAttribute="trailing" id="3aT-0u-Dqp"/>
<constraint firstAttribute="bottom" secondItem="mPf-Xv-wqH" secondAttribute="bottom" id="QyV-Wc-l5Z"/>
<constraint firstItem="mPf-Xv-wqH" firstAttribute="leading" secondItem="5eP-6U-QZi" secondAttribute="leading" id="czh-ku-UT4"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Don't use your account password." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2LW-AX-yzs">
<rect key="frame" x="20" y="473.5" width="374" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2kp-HO-dRr">
<rect key="frame" x="0.0" y="528" width="414" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5Ow-Ba-bLe" customClass="RoundedButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="20" y="0.0" width="374" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="aYo-rF-vZL"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
<state key="normal" title="Validate">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="validateButtonAction:" destination="bIB-0i-ukm" eventType="touchUpInside" id="CRT-SQ-iBx"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="5Ow-Ba-bLe" secondAttribute="bottom" id="Ey4-nH-44x"/>
<constraint firstItem="5Ow-Ba-bLe" firstAttribute="width" secondItem="2kp-HO-dRr" secondAttribute="width" priority="750" id="d9G-hg-c38"/>
<constraint firstItem="5Ow-Ba-bLe" firstAttribute="top" secondItem="2kp-HO-dRr" secondAttribute="top" id="p83-Ml-a6D"/>
<constraint firstItem="5Ow-Ba-bLe" firstAttribute="centerX" secondItem="2kp-HO-dRr" secondAttribute="centerX" id="taR-rx-sJn"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="5eP-6U-QZi" secondAttribute="trailing" id="0ve-ds-mJr"/>
<constraint firstItem="ecv-l3-ryP" firstAttribute="leading" secondItem="NHb-J1-L5X" secondAttribute="leading" constant="20" id="1Wo-9r-ycH"/>
<constraint firstItem="2LW-AX-yzs" firstAttribute="leading" secondItem="NHb-J1-L5X" secondAttribute="leading" constant="20" id="6je-oq-7RI"/>
<constraint firstItem="2LW-AX-yzs" firstAttribute="top" secondItem="5eP-6U-QZi" secondAttribute="bottom" constant="15" id="7ul-hJ-K6H"/>
<constraint firstAttribute="trailing" secondItem="ecv-l3-ryP" secondAttribute="trailing" constant="20" id="9jK-Nm-ExO"/>
<constraint firstAttribute="width" priority="750" constant="500" id="Gg8-aF-YB1"/>
<constraint firstAttribute="trailing" secondItem="2LW-AX-yzs" secondAttribute="trailing" constant="20" id="HxK-zm-30M"/>
<constraint firstItem="ecv-l3-ryP" firstAttribute="top" secondItem="ZX3-oS-5DJ" secondAttribute="bottom" constant="30" id="Sgu-ux-Ec9"/>
<constraint firstAttribute="trailing" secondItem="2kp-HO-dRr" secondAttribute="trailing" id="TQA-Lg-F8a"/>
<constraint firstItem="2kp-HO-dRr" firstAttribute="leading" secondItem="NHb-J1-L5X" secondAttribute="leading" id="Yqh-Ug-xoQ"/>
<constraint firstItem="ZX3-oS-5DJ" firstAttribute="centerX" secondItem="NHb-J1-L5X" secondAttribute="centerX" id="dhm-uO-9qa"/>
<constraint firstItem="5eP-6U-QZi" firstAttribute="leading" secondItem="NHb-J1-L5X" secondAttribute="leading" id="em6-Xe-8LR"/>
<constraint firstItem="ZX3-oS-5DJ" firstAttribute="top" secondItem="NHb-J1-L5X" secondAttribute="top" constant="35" id="gxm-Dc-zzj"/>
<constraint firstItem="2kp-HO-dRr" firstAttribute="top" secondItem="2LW-AX-yzs" secondAttribute="bottom" constant="40" id="hMN-TD-hYF"/>
<constraint firstAttribute="bottom" secondItem="2kp-HO-dRr" secondAttribute="bottom" constant="20" id="svg-Pb-ME3"/>
<constraint firstItem="5eP-6U-QZi" firstAttribute="top" secondItem="ecv-l3-ryP" secondAttribute="bottom" constant="40" id="vDP-O1-IhH"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="NHb-J1-L5X" secondAttribute="bottom" id="53C-6L-LrF"/>
<constraint firstItem="NHb-J1-L5X" firstAttribute="top" secondItem="dME-t7-qZD" secondAttribute="top" id="ZlU-kl-Rzq"/>
<constraint firstItem="5Ow-Ba-bLe" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dME-t7-qZD" secondAttribute="leading" constant="20" id="ZmL-F4-HZY"/>
<constraint firstItem="NHb-J1-L5X" firstAttribute="centerX" secondItem="dME-t7-qZD" secondAttribute="centerX" id="cUL-iD-JJv"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="NHb-J1-L5X" secondAttribute="trailing" id="eLO-V9-4WS"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="5Ow-Ba-bLe" secondAttribute="trailing" constant="20" id="lAb-bG-jNQ"/>
<constraint firstItem="NHb-J1-L5X" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dME-t7-qZD" secondAttribute="leading" id="qix-Tx-bKx"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="dME-t7-qZD" firstAttribute="leading" secondItem="o4q-2F-ezk" secondAttribute="leading" id="Eqv-Ts-WXt"/>
<constraint firstItem="dME-t7-qZD" firstAttribute="width" secondItem="o4q-2F-ezk" secondAttribute="width" id="PLv-tO-F3G"/>
<constraint firstAttribute="trailing" secondItem="dME-t7-qZD" secondAttribute="trailing" id="WWJ-PK-ihX"/>
<constraint firstItem="dME-t7-qZD" firstAttribute="top" secondItem="o4q-2F-ezk" secondAttribute="top" id="Wqq-0l-F66"/>
<constraint firstAttribute="bottom" secondItem="dME-t7-qZD" secondAttribute="bottom" id="itF-04-4ea"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="o4q-2F-ezk" firstAttribute="leading" secondItem="dKy-p2-wAx" secondAttribute="leading" id="Jje-qL-QFy"/>
<constraint firstItem="dKy-p2-wAx" firstAttribute="trailing" secondItem="o4q-2F-ezk" secondAttribute="trailing" id="Jow-ri-Vds"/>
<constraint firstAttribute="bottom" secondItem="o4q-2F-ezk" secondAttribute="bottom" id="flc-Xx-pJR"/>
<constraint firstItem="dKy-p2-wAx" firstAttribute="top" secondItem="o4q-2F-ezk" secondAttribute="top" id="rly-LW-com"/>
</constraints>
<viewLayoutGuide key="safeArea" id="dKy-p2-wAx"/>
</view>
<connections>
<outlet property="additionalInformationLabel" destination="2LW-AX-yzs" id="sSr-zi-cYk"/>
<outlet property="formBackgroundView" destination="5eP-6U-QZi" id="DSp-VW-aQt"/>
<outlet property="informationLabel" destination="ecv-l3-ryP" id="cJ6-CH-05z"/>
<outlet property="passphraseAdditionalInfoView" destination="pQ6-Ke-kTc" id="0tj-vn-lWG"/>
<outlet property="passphraseAdditionalLabel" destination="A2u-5W-L5q" id="OI1-F7-Rsm"/>
<outlet property="passphraseStrengthContainerView" destination="O4X-SY-HCD" id="Ibs-aY-NAO"/>
<outlet property="passphraseStrengthView" destination="7Ur-Gj-5yT" id="4um-Bj-28E"/>
<outlet property="passphraseTextField" destination="wuc-Sr-qja" id="FPu-mI-s69"/>
<outlet property="passphraseTitleLabel" destination="iM1-le-WDv" id="MXH-F1-72q"/>
<outlet property="passphraseVisibilityButton" destination="H1x-SK-Bsn" id="hJB-hW-7Jk"/>
<outlet property="scrollView" destination="o4q-2F-ezk" id="yWy-nx-irL"/>
<outlet property="securePassphraseImageView" destination="ZX3-oS-5DJ" id="wi7-vp-rvG"/>
<outlet property="validateButton" destination="5Ow-Ba-bLe" id="Ugv-xk-l5i"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lob-0d-tfH" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3772.4637681159425" y="-774.10714285714278"/>
</scene>
</scenes>
<resources>
<image name="reveal_password_button" width="24" height="18"/>
<image name="secrets_setup_passphrase" width="48" height="48"/>
</resources>
</document>
@@ -0,0 +1,321 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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 SecretsSetupRecoveryPassphraseViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let animationDuration: TimeInterval = 0.3
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var securePassphraseImageView: UIImageView!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var formBackgroundView: UIView!
@IBOutlet private weak var passphraseTitleLabel: UILabel!
@IBOutlet private weak var passphraseTextField: UITextField!
@IBOutlet private weak var passphraseVisibilityButton: UIButton!
@IBOutlet private weak var passphraseAdditionalInfoView: UIView!
@IBOutlet private weak var passphraseStrengthContainerView: UIView!
@IBOutlet private weak var passphraseStrengthView: PasswordStrengthView!
@IBOutlet private weak var passphraseAdditionalLabel: UILabel!
@IBOutlet private weak var additionalInformationLabel: UILabel!
@IBOutlet private weak var validateButton: RoundedButton!
// MARK: Private
private var viewModel: SecretsSetupRecoveryPassphraseViewModelType!
private var theme: Theme!
private var keyboardAvoider: KeyboardAvoider?
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var isFirstViewAppearing: Bool = true
private var isPassphraseTextFieldEditedOnce: Bool = false
private var currentViewData: SecretsSetupRecoveryPassphraseViewData?
// MARK: - Setup
class func instantiate(with viewModel: SecretsSetupRecoveryPassphraseViewModelType) -> SecretsSetupRecoveryPassphraseViewController {
let viewController = StoryboardScene.SecretsSetupRecoveryPassphraseViewController.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.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.scrollView)
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAvoider?.startAvoiding()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.isFirstViewAppearing {
self.isFirstViewAppearing = false
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.view.endEditing(true)
self.keyboardAvoider?.stopAvoiding()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.vc_removeBackTitle()
self.title = VectorL10n.secretsSetupRecoveryPassphraseTitle
self.scrollView.keyboardDismissMode = .interactive
self.securePassphraseImageView.image = Asset.Images.secretsSetupPassphrase.image.withRenderingMode(.alwaysTemplate)
self.passphraseTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
self.passphraseAdditionalInfoView.isHidden = true
let visibilityImage = Asset.Images.revealPasswordButton.image.withRenderingMode(.alwaysTemplate)
self.passphraseVisibilityButton.setImage(visibilityImage, for: .normal)
self.additionalInformationLabel.text = VectorL10n.secretsSetupRecoveryPassphraseAdditionalInformation
}
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.securePassphraseImageView.tintColor = theme.textPrimaryColor
self.informationLabel.textColor = theme.textPrimaryColor
self.formBackgroundView.backgroundColor = theme.backgroundColor
self.passphraseTitleLabel.textColor = theme.textPrimaryColor
theme.applyStyle(onTextField: self.passphraseTextField)
let passphraseTitle: String
if let viewData = self.currentViewData, case .confimPassphrase = viewData.mode {
passphraseTitle = VectorL10n.secretsSetupRecoveryPassphraseConfirmPassphrasePlaceholder
} else {
passphraseTitle = VectorL10n.keyBackupSetupPassphrasePassphrasePlaceholder
}
self.passphraseTextField.attributedPlaceholder = NSAttributedString(string: passphraseTitle,
attributes: [.foregroundColor: theme.placeholderTextColor])
self.passphraseVisibilityButton.tintColor = theme.tintColor
self.additionalInformationLabel.textColor = theme.textSecondaryColor
self.validateButton.update(theme: theme)
}
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 render(viewState: SecretsSetupRecoveryPassphraseViewState) {
switch viewState {
case .loaded(let viewData):
self.renderLoaded(viewData: viewData)
case .formUpdated(let viewData):
self.renderFormUpdated(viewData: viewData)
case .error(let error):
self.render(error: error)
}
}
private func renderLoaded(viewData: SecretsSetupRecoveryPassphraseViewData) {
self.currentViewData = viewData
let informationText: String
let passphraseTitle: String
let showPasswordStrength: Bool
switch viewData.mode {
case .newPassphrase(strength: let strength):
informationText = VectorL10n.secretsSetupRecoveryPassphraseInformation
passphraseTitle = VectorL10n.keyBackupSetupPassphrasePassphraseTitle
showPasswordStrength = true
self.passphraseStrengthView.strength = strength
case .confimPassphrase:
informationText = VectorL10n.secretsSetupRecoveryPassphraseConfirmInformation
passphraseTitle = VectorL10n.secretsSetupRecoveryPassphraseConfirmPassphraseTitle
showPasswordStrength = false
}
self.informationLabel.text = informationText
self.passphraseTitleLabel.text = passphraseTitle
self.passphraseStrengthContainerView.isHidden = !showPasswordStrength
self.update(theme: self.theme)
}
private func renderFormUpdated(viewData: SecretsSetupRecoveryPassphraseViewData) {
self.currentViewData = viewData
if case .newPassphrase(strength: let strength) = viewData.mode {
self.passphraseStrengthView.strength = strength
}
self.validateButton.isEnabled = viewData.isFormValid
self.updatePassphraseAdditionalLabel(viewData: viewData)
// Show passphrase additional info at first character entered
if self.isPassphraseTextFieldEditedOnce == false, self.passphraseTextField.text?.isEmpty == false {
self.isPassphraseTextFieldEditedOnce = true
self.showPassphraseAdditionalInfo(animated: true)
}
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func showPassphraseAdditionalInfo(animated: Bool) {
guard self.passphraseAdditionalInfoView.isHidden else {
return
}
// Workaround to layout passphraseStrengthView corner radius
self.passphraseStrengthView.setNeedsLayout()
UIView.animate(withDuration: Constants.animationDuration) {
self.passphraseAdditionalInfoView.isHidden = false
}
}
private func updatePassphraseAdditionalLabel(viewData: SecretsSetupRecoveryPassphraseViewData) {
let text: String
let textColor: UIColor
if viewData.isFormValid {
switch viewData.mode {
case .newPassphrase:
text = VectorL10n.keyBackupSetupPassphrasePassphraseValid
case .confimPassphrase:
text = VectorL10n.keyBackupSetupPassphraseConfirmPassphraseValid
}
textColor = self.theme.tintColor
} else {
switch viewData.mode {
case .newPassphrase:
text = VectorL10n.keyBackupSetupPassphrasePassphraseInvalid
case .confimPassphrase:
text = VectorL10n.keyBackupSetupPassphraseConfirmPassphraseInvalid
}
textColor = self.theme.noticeColor
}
self.passphraseAdditionalLabel.text = text
self.passphraseAdditionalLabel.textColor = textColor
}
// MARK: - Actions
@IBAction private func passphraseVisibilityButtonAction(_ sender: Any) {
guard self.isPassphraseTextFieldEditedOnce else {
return
}
self.passphraseTextField.isSecureTextEntry.toggle()
}
@objc private func textFieldDidChange(_ textField: UITextField) {
guard textField == self.passphraseTextField else {
return
}
self.viewModel.process(viewAction: .updatePassphrase(textField.text))
}
@IBAction private func validateButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .validate)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - SecretsSetupRecoveryPassphraseViewModelViewDelegate
extension SecretsSetupRecoveryPassphraseViewController: SecretsSetupRecoveryPassphraseViewModelViewDelegate {
func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didUpdateViewState viewSate: SecretsSetupRecoveryPassphraseViewState) {
self.render(viewState: viewSate)
}
}
@@ -0,0 +1,126 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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 SecretsSetupRecoveryPassphraseViewModel: SecretsSetupRecoveryPassphraseViewModelType {
// MARK: - Properties
// MARK: Private
private let passphraseInput: SecretsSetupRecoveryPassphraseInput
private let passwordStrengthManager: PasswordStrengthManager
private var currentViewData: SecretsSetupRecoveryPassphraseViewData?
private var passphrase: String?
// MARK: Public
weak var viewDelegate: SecretsSetupRecoveryPassphraseViewModelViewDelegate?
weak var coordinatorDelegate: SecretsSetupRecoveryPassphraseViewModelCoordinatorDelegate?
// MARK: - Setup
init(passphraseInput: SecretsSetupRecoveryPassphraseInput) {
self.passphraseInput = passphraseInput
self.passwordStrengthManager = PasswordStrengthManager()
}
// MARK: - Public
func process(viewAction: SecretsSetupRecoveryPassphraseViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .updatePassphrase(let passphrase):
self.updatePassphrase(passphrase)
case .validate:
self.validate()
case .cancel:
self.coordinatorDelegate?.secretsSetupRecoveryPassphraseViewModelDidCancel(self)
}
}
// MARK: - Private
private func loadData() {
let viewDataMode: SecretsSetupRecoveryPassphraseViewDataMode
switch self.passphraseInput {
case .new:
viewDataMode = .newPassphrase(strength: .tooGuessable)
case .confirm:
viewDataMode = .confimPassphrase
}
let viewData = SecretsSetupRecoveryPassphraseViewData(mode: viewDataMode, isFormValid: false)
self.update(viewState: .loaded(viewData))
}
private func update(viewState: SecretsSetupRecoveryPassphraseViewState) {
self.viewDelegate?.secretsSetupRecoveryPassphraseViewModel(self, didUpdateViewState: viewState)
}
private func updatePassphrase(_ passphrase: String?) {
let viewDataMode: SecretsSetupRecoveryPassphraseViewDataMode
let isFormValid: Bool
switch self.passphraseInput {
case .new:
let passphraseStrength = self.passwordStrength(for: passphrase)
viewDataMode = .newPassphrase(strength: passphraseStrength)
isFormValid = passphraseStrength == .veryUnguessable
case .confirm(let passphraseToConfirm):
viewDataMode = .confimPassphrase
isFormValid = passphrase == passphraseToConfirm
}
let viewData = SecretsSetupRecoveryPassphraseViewData(mode: viewDataMode, isFormValid: isFormValid)
self.passphrase = passphrase
self.currentViewData = viewData
self.update(viewState: .formUpdated(viewData))
}
private func validate() {
guard let viewData = self.currentViewData,
viewData.isFormValid,
let passphrase = self.passphrase else {
return
}
switch self.passphraseInput {
case .new:
self.coordinatorDelegate?.secretsSetupRecoveryPassphraseViewModel(self, didEnterNewPassphrase: passphrase)
case .confirm:
self.coordinatorDelegate?.secretsSetupRecoveryPassphraseViewModel(self, didConfirmPassphrase: passphrase)
}
}
private func passwordStrength(for password: String?) -> PasswordStrength {
guard let password = password else {
return .tooGuessable
}
return self.passwordStrengthManager.passwordStrength(for: password)
}
}
@@ -0,0 +1,38 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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 SecretsSetupRecoveryPassphraseViewModelViewDelegate: class {
func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didUpdateViewState viewSate: SecretsSetupRecoveryPassphraseViewState)
}
protocol SecretsSetupRecoveryPassphraseViewModelCoordinatorDelegate: class {
func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didEnterNewPassphrase passphrase: String)
func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didConfirmPassphrase passphrase: String)
func secretsSetupRecoveryPassphraseViewModelDidCancel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType)
}
/// Protocol describing the view model used by `SecretsSetupRecoveryPassphraseViewController`
protocol SecretsSetupRecoveryPassphraseViewModelType {
var viewDelegate: SecretsSetupRecoveryPassphraseViewModelViewDelegate? { get set }
var coordinatorDelegate: SecretsSetupRecoveryPassphraseViewModelCoordinatorDelegate? { get set }
func process(viewAction: SecretsSetupRecoveryPassphraseViewAction)
}
@@ -0,0 +1,36 @@
// File created from ScreenTemplate
// $ createScreen.sh Test SecretsSetupRecoveryPassphrase
/*
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 SecretsSetupRecoveryPassphraseViewDataMode {
case newPassphrase(strength: PasswordStrength)
case confimPassphrase
}
struct SecretsSetupRecoveryPassphraseViewData {
let mode: SecretsSetupRecoveryPassphraseViewDataMode
let isFormValid: Bool
}
/// SecretsSetupRecoveryPassphraseViewController view state
enum SecretsSetupRecoveryPassphraseViewState {
case loaded(_ viewData: SecretsSetupRecoveryPassphraseViewData)
case formUpdated(_ viewData: SecretsSetupRecoveryPassphraseViewData)
case error(Error)
}