mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 16:13:42 +02:00
Secrets setup: Handle set up recovery passphrase screen.
This commit is contained in:
+73
@@ -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)
|
||||
}
|
||||
}
|
||||
+30
@@ -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 }
|
||||
}
|
||||
+24
@@ -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)
|
||||
}
|
||||
+27
@@ -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
|
||||
}
|
||||
+285
@@ -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>
|
||||
+321
@@ -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)
|
||||
}
|
||||
}
|
||||
+126
@@ -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)
|
||||
}
|
||||
}
|
||||
+38
@@ -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)
|
||||
}
|
||||
+36
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user