Implement key backup setup passphrase scene with view controller, view model, coordinator

This commit is contained in:
SBiOSoftWhare
2019-01-22 10:25:36 +01:00
parent 8bfb79af89
commit 036fdeee53
10 changed files with 1220 additions and 0 deletions
@@ -0,0 +1,73 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
protocol KeyBackupSetupPassphraseCoordinatorDelegate: class {
func keyBackupSetupPassphraseCoordinator(_ keyBackupSetupPassphraseCoordinator: KeyBackupSetupPassphraseCoordinator, didCompleteWithMegolmBackupCreationInfo megolmBackupCreationInfo: MXMegolmBackupCreationInfo)
func keyBackupSetupPassphraseCoordinatorDidCancel(_ keyBackupSetupPassphraseCoordinator: KeyBackupSetupPassphraseCoordinator)
}
final class KeyBackupSetupPassphraseCoordinator: KeyBackupSetupPassphraseCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var keyBackupSetupPassphraseViewModel: KeyBackupSetupPassphraseViewModelType
private let keyBackupSetupPassphraseViewController: KeyBackupSetupPassphraseViewController
// MARK: Public
var childCoordinators: [Coordinator] = []
weak var delegate: KeyBackupSetupPassphraseCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession) {
self.session = session
let keyBackup = MXKeyBackup(matrixSession: session)
let keyBackupSetupPassphraseViewModel = KeyBackupSetupPassphraseViewModel(keyBackup: keyBackup)
let keyBackupSetupPassphraseViewController = KeyBackupSetupPassphraseViewController.instantiate(with: keyBackupSetupPassphraseViewModel)
self.keyBackupSetupPassphraseViewModel = keyBackupSetupPassphraseViewModel
self.keyBackupSetupPassphraseViewController = keyBackupSetupPassphraseViewController
}
// MARK: - Public methods
func start() {
self.keyBackupSetupPassphraseViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.keyBackupSetupPassphraseViewController
}
}
// MARK: - KeyBackupSetupPassphraseViewModelCoordinatorDelegate
extension KeyBackupSetupPassphraseCoordinator: KeyBackupSetupPassphraseViewModelCoordinatorDelegate {
func keyBackupSetupPassphraseViewModelDidCancel(_ viewModel: KeyBackupSetupPassphraseViewModelType) {
self.delegate?.keyBackupSetupPassphraseCoordinatorDidCancel(self)
}
func keyBackupSetupPassphraseViewModel(_ viewModel: KeyBackupSetupPassphraseViewModelType, didCompleteWithMegolmBackupCreationInfo megolmBackupCreationInfo: MXMegolmBackupCreationInfo) {
self.delegate?.keyBackupSetupPassphraseCoordinator(self, didCompleteWithMegolmBackupCreationInfo: megolmBackupCreationInfo)
}
}
@@ -0,0 +1,21 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// `KeyBackupSetupPassphraseCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol KeyBackupSetupPassphraseCoordinatorType: Coordinator, Presentable {
}
@@ -0,0 +1,25 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// KeyBackupSetupPassphraseViewController view actions exposed to view model
enum KeyBackupSetupPassphraseViewAction {
case setupPassphrase
case skip
case skipAlertSkip
case skipAlertContinue
}
@@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Key Backup Setup Passphrase View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="KeyBackupSetupPassphraseViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="375" height="563"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="375" height="563"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="20" y="40" width="335" height="90"/>
<string key="text">Secure your encrypted message history with a Recovery Passphrase.
Youll need it if you log out, or lose access to this device.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NqM-Go-gVF">
<rect key="frame" x="0.0" y="170" width="375" height="293"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="4zf-cV-r09">
<rect key="frame" x="0.0" y="0.0" width="375" height="293"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4ga-7Y-87s">
<rect key="frame" x="0.0" y="0.0" width="375" 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="w95-PK-mJ8">
<rect key="frame" x="20" y="10" width="55.5" height="58.5"/>
<fontDescription key="fontDescription" type="system" 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="WeI-qi-2Hw">
<rect key="frame" x="95.5" y="14.5" width="235.5" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="85g-2l-RZB"/>
</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="V8j-Lb-PgC" id="obR-yu-AUD"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iZ1-z0-bNp">
<rect key="frame" x="331" y="17.5" width="44" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="B2V-ki-5ds"/>
<constraint firstAttribute="height" constant="44" id="QVT-oC-sIM"/>
</constraints>
<state key="normal" image="reveal_password_button"/>
<connections>
<action selector="passphraseVisibilityButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="7Xw-if-TRP"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="w95-PK-mJ8" firstAttribute="top" secondItem="4ga-7Y-87s" secondAttribute="top" constant="10" id="2u9-gy-fQe"/>
<constraint firstItem="iZ1-z0-bNp" firstAttribute="leading" secondItem="WeI-qi-2Hw" secondAttribute="trailing" id="A5n-gL-KBQ"/>
<constraint firstItem="iZ1-z0-bNp" firstAttribute="centerY" secondItem="WeI-qi-2Hw" secondAttribute="centerY" id="DDf-za-izU"/>
<constraint firstAttribute="trailing" secondItem="iZ1-z0-bNp" secondAttribute="trailing" id="KAz-Dj-GGP"/>
<constraint firstItem="WeI-qi-2Hw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="w95-PK-mJ8" secondAttribute="trailing" constant="20" id="KIP-Tr-rCO"/>
<constraint firstItem="w95-PK-mJ8" firstAttribute="leading" secondItem="4ga-7Y-87s" secondAttribute="leading" constant="20" id="MR9-Za-ZxE"/>
<constraint firstItem="WeI-qi-2Hw" firstAttribute="leading" secondItem="w95-PK-mJ8" secondAttribute="trailing" priority="750" constant="10" id="NuZ-iP-bCk"/>
<constraint firstItem="w95-PK-mJ8" firstAttribute="centerY" secondItem="4ga-7Y-87s" secondAttribute="centerY" id="WhA-lW-DZN"/>
<constraint firstItem="WeI-qi-2Hw" firstAttribute="top" relation="greaterThanOrEqual" secondItem="4ga-7Y-87s" secondAttribute="top" constant="5" id="X2X-P2-y2h"/>
<constraint firstAttribute="bottom" secondItem="w95-PK-mJ8" secondAttribute="bottom" constant="10" id="kwC-rH-fYk"/>
<constraint firstItem="w95-PK-mJ8" firstAttribute="centerY" secondItem="WeI-qi-2Hw" secondAttribute="centerY" id="mew-SD-WYe"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="WeI-qi-2Hw" secondAttribute="bottom" constant="5" id="sZh-FK-eiY"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RCF-wt-i5I">
<rect key="frame" x="0.0" y="78.5" width="375" height="67.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="G2S-Kn-gi7" customClass="PasswordStrengthView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="20" y="5" width="335" height="5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="5" id="MtV-wv-14Z"/>
</constraints>
</view>
<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="GW1-yJ-6Io">
<rect key="frame" x="20" y="25" width="335" height="27.5"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<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="bottom" secondItem="GW1-yJ-6Io" secondAttribute="bottom" constant="15" id="5EM-kN-XFF"/>
<constraint firstAttribute="trailing" secondItem="G2S-Kn-gi7" secondAttribute="trailing" constant="20" id="JEV-2Q-Bqw"/>
<constraint firstItem="G2S-Kn-gi7" firstAttribute="top" secondItem="RCF-wt-i5I" secondAttribute="top" constant="5" id="Sc9-G8-VyZ"/>
<constraint firstItem="G2S-Kn-gi7" firstAttribute="leading" secondItem="RCF-wt-i5I" secondAttribute="leading" constant="20" id="Tc8-Ko-flK"/>
<constraint firstItem="GW1-yJ-6Io" firstAttribute="trailing" secondItem="G2S-Kn-gi7" secondAttribute="trailing" id="p3f-nT-YAc"/>
<constraint firstItem="GW1-yJ-6Io" firstAttribute="leading" secondItem="G2S-Kn-gi7" secondAttribute="leading" id="tJw-mn-Aud"/>
<constraint firstItem="GW1-yJ-6Io" firstAttribute="top" secondItem="G2S-Kn-gi7" secondAttribute="bottom" constant="15" id="vVD-cA-KEd"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HaJ-Or-kUN">
<rect key="frame" x="0.0" y="146" width="375" height="1"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zyC-Xx-o8z">
<rect key="frame" x="5" y="0.0" width="365" height="1"/>
<color key="backgroundColor" red="0.93333333333333335" green="0.93725490196078431" blue="0.93725490196078431" alpha="1" colorSpace="calibratedRGB"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="zyC-Xx-o8z" secondAttribute="bottom" id="1Nu-hx-vme"/>
<constraint firstItem="zyC-Xx-o8z" firstAttribute="leading" secondItem="HaJ-Or-kUN" secondAttribute="leading" constant="5" id="E6e-Vq-5iv"/>
<constraint firstItem="zyC-Xx-o8z" firstAttribute="top" secondItem="HaJ-Or-kUN" secondAttribute="top" id="OBC-Ri-0D4"/>
<constraint firstAttribute="height" constant="1" id="OwJ-NK-dhU"/>
<constraint firstAttribute="trailing" secondItem="zyC-Xx-o8z" secondAttribute="trailing" constant="5" id="TPd-BE-4Oe"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zoU-2f-4tr">
<rect key="frame" x="0.0" y="147" width="375" height="78.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="confirm" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bIh-bf-CD3">
<rect key="frame" x="20" y="10" width="55.5" height="58.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Confirm passphrase" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="tN0-Fi-84k">
<rect key="frame" x="95.5" y="14.5" width="259.5" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="8qv-7N-hum"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="done" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="V8j-Lb-PgC" id="8CT-LS-ydI"/>
</connections>
</textField>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="bIh-bf-CD3" firstAttribute="centerY" secondItem="tN0-Fi-84k" secondAttribute="centerY" id="0Li-bg-lpN"/>
<constraint firstAttribute="bottom" secondItem="bIh-bf-CD3" secondAttribute="bottom" constant="10" id="9of-ZE-kOD"/>
<constraint firstItem="tN0-Fi-84k" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="bIh-bf-CD3" secondAttribute="trailing" constant="20" id="PfM-ta-kE2"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="tN0-Fi-84k" secondAttribute="bottom" constant="5" id="SN0-N0-7Pi"/>
<constraint firstItem="bIh-bf-CD3" firstAttribute="top" secondItem="zoU-2f-4tr" secondAttribute="top" constant="10" id="VyA-9J-GCr"/>
<constraint firstItem="bIh-bf-CD3" firstAttribute="leading" secondItem="zoU-2f-4tr" secondAttribute="leading" constant="20" id="bs8-Fi-kCU"/>
<constraint firstItem="tN0-Fi-84k" firstAttribute="top" relation="greaterThanOrEqual" secondItem="zoU-2f-4tr" secondAttribute="top" constant="5" id="hhK-zc-94W"/>
<constraint firstItem="bIh-bf-CD3" firstAttribute="centerY" secondItem="zoU-2f-4tr" secondAttribute="centerY" id="ope-YR-FvF"/>
<constraint firstAttribute="trailing" secondItem="tN0-Fi-84k" secondAttribute="trailing" constant="20" id="vN5-77-0RI"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8in-A5-jGb">
<rect key="frame" x="0.0" y="225.5" width="375" height="67.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Passphrase doesnt match" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QLJ-x6-fEO">
<rect key="frame" x="20" y="0.0" width="335" height="52.5"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<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="bottom" secondItem="QLJ-x6-fEO" secondAttribute="bottom" constant="15" id="0Zp-3U-I1I"/>
<constraint firstItem="QLJ-x6-fEO" firstAttribute="leading" secondItem="8in-A5-jGb" secondAttribute="leading" constant="20" id="XQe-TR-0tX"/>
<constraint firstItem="QLJ-x6-fEO" firstAttribute="top" secondItem="8in-A5-jGb" secondAttribute="top" id="egl-FV-3UJ"/>
<constraint firstAttribute="trailing" secondItem="QLJ-x6-fEO" secondAttribute="trailing" constant="20" id="qei-tu-wbZ"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="tN0-Fi-84k" firstAttribute="leading" secondItem="WeI-qi-2Hw" secondAttribute="leading" id="zby-aJ-k4L"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="RCF-wt-i5I" firstAttribute="width" secondItem="NqM-Go-gVF" secondAttribute="width" id="5Bn-r5-XXO"/>
<constraint firstItem="4ga-7Y-87s" firstAttribute="width" secondItem="NqM-Go-gVF" secondAttribute="width" id="83l-eu-7vQ"/>
<constraint firstAttribute="bottom" secondItem="4zf-cV-r09" secondAttribute="bottom" id="D0l-0d-h5d"/>
<constraint firstItem="HaJ-Or-kUN" firstAttribute="width" secondItem="NqM-Go-gVF" secondAttribute="width" id="UJV-NT-dOh"/>
<constraint firstItem="8in-A5-jGb" firstAttribute="width" secondItem="NqM-Go-gVF" secondAttribute="width" id="rQV-Lt-g05"/>
<constraint firstAttribute="trailing" secondItem="4zf-cV-r09" secondAttribute="trailing" id="uTZ-GI-QfT"/>
<constraint firstItem="4zf-cV-r09" firstAttribute="top" secondItem="NqM-Go-gVF" secondAttribute="top" id="wQN-ZJ-ZJj"/>
<constraint firstItem="4zf-cV-r09" firstAttribute="leading" secondItem="NqM-Go-gVF" secondAttribute="leading" id="xmU-LO-Kyc"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oxZ-I4-CTh">
<rect key="frame" x="0.0" y="493" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xXL-d6-V7f">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="6k5-t4-uPs"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" title="Set Recovery Passphrase ">
<color key="titleColor" red="0.47843137254901957" green="0.78823529411764703" blue="0.63137254901960782" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137254901957" green="0.78823529411764703" blue="0.63137254901960782" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="setPassphraseButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="Zph-IR-pNT"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="xXL-d6-V7f" secondAttribute="trailing" id="IS2-cy-CxX"/>
<constraint firstAttribute="bottom" secondItem="xXL-d6-V7f" secondAttribute="bottom" id="J3s-St-4lL"/>
<constraint firstItem="xXL-d6-V7f" firstAttribute="top" secondItem="oxZ-I4-CTh" secondAttribute="top" id="YDL-xv-yqV"/>
<constraint firstItem="xXL-d6-V7f" firstAttribute="leading" secondItem="oxZ-I4-CTh" secondAttribute="leading" id="gjp-yd-bno"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="bxI-mu-qng" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" id="3fO-gc-E1G"/>
<constraint firstItem="NqM-Go-gVF" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="40" id="BaV-Yg-8AU"/>
<constraint firstItem="NqM-Go-gVF" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="JRa-bh-kd2"/>
<constraint firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" constant="20" id="Q2g-xU-EQR"/>
<constraint firstAttribute="trailing" secondItem="NqM-Go-gVF" secondAttribute="trailing" id="bxm-bA-yg6"/>
<constraint firstItem="oxZ-I4-CTh" firstAttribute="top" secondItem="NqM-Go-gVF" secondAttribute="bottom" constant="30" id="fop-Qu-P8P"/>
<constraint firstAttribute="width" priority="750" constant="500" id="glD-Sz-73O"/>
<constraint firstAttribute="bottom" secondItem="oxZ-I4-CTh" secondAttribute="bottom" constant="20" id="nmb-rx-bht"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="40" id="qgy-FA-ETV"/>
<constraint firstAttribute="trailing" secondItem="oxZ-I4-CTh" secondAttribute="trailing" id="tQa-RS-aVB"/>
<constraint firstItem="oxZ-I4-CTh" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="weK-ZJ-30E"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" id="Y46-NP-zAc"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<connections>
<outlet property="confirmPassphraseAdditionalInfoView" destination="8in-A5-jGb" id="sc7-9K-rLl"/>
<outlet property="confirmPassphraseAdditionalLabel" destination="QLJ-x6-fEO" id="0mC-8y-pPN"/>
<outlet property="confirmPassphraseTextField" destination="tN0-Fi-84k" id="eF8-h1-BBP"/>
<outlet property="confirmPassphraseTitleLabel" destination="bIh-bf-CD3" id="xOS-Co-S1R"/>
<outlet property="formBackgroundView" destination="NqM-Go-gVF" id="dFl-9I-Bb3"/>
<outlet property="formSeparatorView" destination="zyC-Xx-o8z" id="mWq-wr-TSq"/>
<outlet property="informationLabel" destination="bxI-mu-qng" id="Q6Q-b2-Ac7"/>
<outlet property="passphraseAdditionalInfoView" destination="RCF-wt-i5I" id="1qO-rg-lfP"/>
<outlet property="passphraseAdditionalLabel" destination="GW1-yJ-6Io" id="Yy6-Pb-EVC"/>
<outlet property="passphraseStrengthView" destination="G2S-Kn-gi7" id="hg5-Dw-RBB"/>
<outlet property="passphraseTextField" destination="WeI-qi-2Hw" id="Nz4-K5-lf3"/>
<outlet property="passphraseTitleLabel" destination="w95-PK-mJ8" id="m9O-Z5-qsA"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="setPassphraseButton" destination="xXL-d6-V7f" id="k8E-Ul-m9g"/>
<outlet property="setPassphraseButtonBackgroundView" destination="oxZ-I4-CTh" id="8If-if-aMp"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2940" y="-565"/>
</scene>
</scenes>
<resources>
<image name="reveal_password_button" width="24" height="18"/>
</resources>
</document>
@@ -0,0 +1,395 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class KeyBackupSetupPassphraseViewController: 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 informationLabel: UILabel!
@IBOutlet private weak var formBackgroundView: UIView!
@IBOutlet private weak var passphraseTitleLabel: UILabel!
@IBOutlet private weak var passphraseTextField: UITextField!
@IBOutlet private weak var passphraseAdditionalInfoView: UIView!
@IBOutlet private weak var passphraseStrengthView: PasswordStrengthView!
@IBOutlet private weak var passphraseAdditionalLabel: UILabel!
@IBOutlet private weak var formSeparatorView: UIView!
@IBOutlet private weak var confirmPassphraseTitleLabel: UILabel!
@IBOutlet private weak var confirmPassphraseTextField: UITextField!
@IBOutlet private weak var confirmPassphraseAdditionalInfoView: UIView!
@IBOutlet private weak var confirmPassphraseAdditionalLabel: UILabel!
@IBOutlet private weak var setPassphraseButtonBackgroundView: UIView!
@IBOutlet private weak var setPassphraseButton: UIButton!
// MARK: Private
private var isFirstViewAppearing: Bool = true
private var isPassphraseTextFieldEditedOnce: Bool = false
private var isConfirmPassphraseTextFieldEditedOnce: Bool = false
private var keyboardAvoider: KeyboardAvoider?
private var viewModel: KeyBackupSetupPassphraseViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private weak var skipAlertController: UIAlertController?
// MARK: - Setup
class func instantiate(with viewModel: KeyBackupSetupPassphraseViewModelType) -> KeyBackupSetupPassphraseViewController {
let viewController = StoryboardScene.KeyBackupSetupPassphraseViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.keyBackupSetupTitle
self.vc_removeBackTitle()
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
}
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 func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if self.isFirstViewAppearing {
// Workaround to layout passphraseStrengthView corner radius
self.passphraseStrengthView.setNeedsLayout()
}
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.informationLabel.textColor = theme.textPrimaryColor
self.formBackgroundView.backgroundColor = theme.backgroundColor
self.passphraseTitleLabel.textColor = theme.textPrimaryColor
theme.applyStyle(onTextField: self.passphraseTextField)
self.passphraseTextField.attributedPlaceholder = NSAttributedString(string: VectorL10n.keyBackupSetupPassphrasePassphrasePlaceholder,
attributes: [.foregroundColor : theme.placeholderTextColor])
self.updatePassphraseAdditionalLabel()
self.formSeparatorView.backgroundColor = theme.separatorColor
self.confirmPassphraseTitleLabel.textColor = theme.textPrimaryColor
theme.applyStyle(onTextField: self.confirmPassphraseTextField)
self.confirmPassphraseTextField.attributedPlaceholder = NSAttributedString(string: VectorL10n.keyBackupSetupPassphraseConfirmPassphraseTitle,
attributes: [.foregroundColor : theme.placeholderTextColor])
self.updateConfirmPassphraseAdditionalLabel()
self.setPassphraseButton.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.setPassphraseButton)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
let skipBarButtonItem = MXKBarButtonItem(title: VectorL10n.keyBackupSetupSkipAction, style: .plain) { [weak self] in
self?.skipButtonAction()
}
self.navigationItem.rightBarButtonItem = skipBarButtonItem
self.scrollView.keyboardDismissMode = .interactive
self.informationLabel.text = VectorL10n.keyBackupSetupPassphraseInfo
self.passphraseTitleLabel.text = VectorL10n.keyBackupSetupPassphrasePassphraseTitle
self.passphraseTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
self.passphraseStrengthView.strength = self.viewModel.passphraseStrength
self.passphraseAdditionalInfoView.isHidden = true
self.confirmPassphraseTitleLabel.text = VectorL10n.keyBackupSetupPassphraseConfirmPassphraseTitle
self.confirmPassphraseTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
self.confirmPassphraseAdditionalInfoView.isHidden = true
self.setPassphraseButton.titleLabel?.numberOfLines = 0
self.setPassphraseButton.setTitle(VectorL10n.keyBackupSetupPassphraseSetPassphraseAction, for: .normal)
self.updateSetPassphraseButton()
}
private func showPassphraseAdditionalInfo(animated: Bool) {
guard self.passphraseAdditionalInfoView.isHidden else {
return
}
UIView.animate(withDuration: Constants.animationDuration) {
self.passphraseAdditionalInfoView.isHidden = false
}
}
private func showConfirmPassphraseAdditionalInfo(animated: Bool) {
guard self.confirmPassphraseAdditionalInfoView.isHidden else {
return
}
UIView.animate(withDuration: Constants.animationDuration) {
self.confirmPassphraseAdditionalInfoView.isHidden = false
}
}
private func hideConfirmPassphraseAdditionalInfo(animated: Bool) {
guard self.confirmPassphraseAdditionalInfoView.isHidden == false else {
return
}
UIView.animate(withDuration: Constants.animationDuration) {
self.confirmPassphraseAdditionalInfoView.isHidden = true
}
}
private func updatePassphraseStrengthView() {
self.passphraseStrengthView.strength = self.viewModel.passphraseStrength
}
private func updatePassphraseAdditionalLabel() {
let text: String
let textColor: UIColor
if self.viewModel.isPassphraseValid {
text = VectorL10n.keyBackupSetupPassphrasePassphraseValid
textColor = self.theme.tintColor
} else {
text = VectorL10n.keyBackupSetupPassphrasePassphraseInvalid
textColor = self.theme.notificationPrimaryColor
}
self.passphraseAdditionalLabel.text = text
self.passphraseAdditionalLabel.textColor = textColor
}
private func updateConfirmPassphraseAdditionalLabel() {
let text: String
let textColor: UIColor
if self.viewModel.isConfirmPassphraseValid {
text = VectorL10n.keyBackupSetupPassphraseConfirmPassphraseValid
textColor = self.theme.tintColor
} else {
text = VectorL10n.keyBackupSetupPassphraseConfirmPassphraseInvalid
textColor = self.theme.notificationPrimaryColor
}
self.confirmPassphraseAdditionalLabel.text = text
self.confirmPassphraseAdditionalLabel.textColor = textColor
}
private func updateSetPassphraseButton() {
self.setPassphraseButton.isEnabled = self.viewModel.isFormValid
}
private func render(viewState: KeyBackupSetupPassphraseViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderLoaded()
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.view.endEditing(true)
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.hideSkipAlert(animated: false)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func showSkipAlert() {
guard self.skipAlertController == nil else {
return
}
let alertController = UIAlertController(title: VectorL10n.keyBackupSetupSkipAlertTitle,
message: VectorL10n.keyBackupSetupSkipAlertMessage,
preferredStyle:.alert)
alertController.addAction(UIAlertAction(title: VectorL10n.continue, style: .cancel, handler: { action in
self.viewModel.process(viewAction: .skipAlertContinue)
}))
alertController.addAction(UIAlertAction(title: VectorL10n.keyBackupSetupSkipAlertSkipAction , style: .default, handler: { action in
self.viewModel.process(viewAction: .skipAlertSkip)
}))
self.present(alertController, animated: true, completion: nil)
self.skipAlertController = alertController
}
private func hideSkipAlert(animated: Bool) {
self.skipAlertController?.dismiss(animated: true, completion: nil)
}
// MARK: - Actions
@IBAction private func passphraseVisibilityButtonAction(_ sender: Any) {
guard self.isPassphraseTextFieldEditedOnce else {
return
}
self.passphraseTextField.isSecureTextEntry.toggle()
}
@objc func textFieldDidChange(_ textField: UITextField) {
if textField == self.passphraseTextField {
self.viewModel.passphrase = textField.text
self.updatePassphraseAdditionalLabel()
self.updatePassphraseStrengthView()
// Show passphrase additional info at first character entered
if self.isPassphraseTextFieldEditedOnce == false && textField.text?.isEmpty == false {
self.isPassphraseTextFieldEditedOnce = true
self.showPassphraseAdditionalInfo(animated: true)
}
} else {
self.viewModel.confirmPassphrase = textField.text
}
// Show confirm passphrase additional info if needed
self.updateConfirmPassphraseAdditionalLabel()
if self.viewModel.confirmPassphrase?.isEmpty == false && self.viewModel.isPassphraseValid {
self.showConfirmPassphraseAdditionalInfo(animated: true)
} else {
self.hideConfirmPassphraseAdditionalInfo(animated: true)
}
// Enable validate button if form is valid
self.updateSetPassphraseButton()
}
@IBAction private func setPassphraseButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .setupPassphrase)
}
private func skipButtonAction() {
self.viewModel.process(viewAction: .skip)
}
}
// MARK: - UITextFieldDelegate
extension KeyBackupSetupPassphraseViewController: UITextFieldDelegate {
func textFieldShouldClear(_ textField: UITextField) -> Bool {
self.textFieldDidChange(textField)
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == self.passphraseTextField {
self.confirmPassphraseTextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
}
// MARK: - KeyBackupSetupPassphraseViewModelViewDelegate
extension KeyBackupSetupPassphraseViewController: KeyBackupSetupPassphraseViewModelViewDelegate {
func keyBackupSetupPassphraseViewModel(_ viewModel: KeyBackupSetupPassphraseViewModelType, didUpdateViewState viewSate: KeyBackupSetupPassphraseViewState) {
self.render(viewState: viewSate)
}
func keyBackupSetupPassphraseViewModelShowSkipAlert(_ viewModel: KeyBackupSetupPassphraseViewModelType) {
self.showSkipAlert()
}
}
@@ -0,0 +1,138 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
final class KeyBackupSetupPassphraseViewModel: KeyBackupSetupPassphraseViewModelType {
// MARK: - Properties
// MARK: Private
private(set) var passphraseStrength: PasswordStrength = .tooGuessable
private let passwordStrengthManager: PasswordStrengthManager
private let keyBackup: MXKeyBackup
private let coordinatorDelegateQueue: OperationQueue
// MARK: Public
var passphrase: String? {
didSet {
self.updatePassphraseStrength()
}
}
var confirmPassphrase: String?
var isPassphraseValid: Bool {
return self.passphraseStrength == .veryUnguessable
}
var isConfirmPassphraseValid: Bool {
guard self.isPassphraseValid, let confirmPassphrase = self.confirmPassphrase else {
return false
}
return confirmPassphrase == passphrase
}
var isFormValid: Bool {
return self.isPassphraseValid && self.isConfirmPassphraseValid
}
weak var viewDelegate: KeyBackupSetupPassphraseViewModelViewDelegate?
weak var coordinatorDelegate: KeyBackupSetupPassphraseViewModelCoordinatorDelegate?
// MARK: - Setup
init(keyBackup: MXKeyBackup) {
self.passwordStrengthManager = PasswordStrengthManager()
self.keyBackup = keyBackup
let coordinatorDelegateQueue = OperationQueue()
coordinatorDelegateQueue.name = "KeyBackupSetupPassphraseViewModel.coordinatorDelegateQueue"
coordinatorDelegateQueue.maxConcurrentOperationCount = 1
self.coordinatorDelegateQueue = coordinatorDelegateQueue
}
// MARK: - Public
func process(viewAction: KeyBackupSetupPassphraseViewAction) {
switch viewAction {
case .setupPassphrase:
self.setupPassphrase()
case .skip:
self.pauseCoordinatorOperations()
self.viewDelegate?.keyBackupSetupPassphraseViewModelShowSkipAlert(self)
case.skipAlertContinue:
self.resumeCoordinatorOperations()
case.skipAlertSkip:
self.cancelCoordinatorOperations()
self.coordinatorDelegate?.keyBackupSetupPassphraseViewModelDidCancel(self)
}
}
// MARK: - Private
func setupPassphrase() {
guard let passphrase = self.passphrase else {
return
}
self.viewDelegate?.keyBackupSetupPassphraseViewModel(self, didUpdateViewState: .loading)
self.keyBackup.prepareKeyBackupVersion(withPassword: passphrase, success: { [weak self] (megolmBackupCreationInfo) in
guard let sself = self else {
return
}
sself.viewDelegate?.keyBackupSetupPassphraseViewModel(sself, didUpdateViewState: .loaded)
sself.coordinatorDelegateQueue.addOperation {
DispatchQueue.main.async {
sself.coordinatorDelegate?.keyBackupSetupPassphraseViewModel(sself, didCompleteWithMegolmBackupCreationInfo: megolmBackupCreationInfo)
}
}
}, failure: { [weak self] error in
guard let sself = self else {
return
}
sself.viewDelegate?.keyBackupSetupPassphraseViewModel(sself, didUpdateViewState: .error(error))
})
}
private func updatePassphraseStrength() {
self.passphraseStrength = self.passwordStrength(for: self.passphrase)
}
private func passwordStrength(for password: String?) -> PasswordStrength {
guard let password = password else {
return .tooGuessable
}
return self.passwordStrengthManager.passwordStrength(for: password)
}
private func pauseCoordinatorOperations() {
self.coordinatorDelegateQueue.isSuspended = true
}
private func resumeCoordinatorOperations() {
self.coordinatorDelegateQueue.isSuspended = false
}
private func cancelCoordinatorOperations() {
self.coordinatorDelegateQueue.cancelAllOperations()
}
}
@@ -0,0 +1,44 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol KeyBackupSetupPassphraseViewModelViewDelegate: class {
func keyBackupSetupPassphraseViewModel(_ viewModel: KeyBackupSetupPassphraseViewModelType, didUpdateViewState viewSate: KeyBackupSetupPassphraseViewState)
func keyBackupSetupPassphraseViewModelShowSkipAlert(_ viewModel: KeyBackupSetupPassphraseViewModelType)
}
protocol KeyBackupSetupPassphraseViewModelCoordinatorDelegate: class {
func keyBackupSetupPassphraseViewModel(_ viewModel: KeyBackupSetupPassphraseViewModelType, didCompleteWithMegolmBackupCreationInfo megolmBackupCreationInfo: MXMegolmBackupCreationInfo)
func keyBackupSetupPassphraseViewModelDidCancel(_ viewModel: KeyBackupSetupPassphraseViewModelType)
}
/// Protocol describing the view model used by `KeyBackupSetupPassphraseViewController`
protocol KeyBackupSetupPassphraseViewModelType {
var passphrase: String? { get set }
var confirmPassphrase: String? { get set }
var passphraseStrength: PasswordStrength { get }
var isPassphraseValid: Bool { get }
var isConfirmPassphraseValid: Bool { get }
var isFormValid: Bool { get }
var viewDelegate: KeyBackupSetupPassphraseViewModelViewDelegate? { get set }
var coordinatorDelegate: KeyBackupSetupPassphraseViewModelCoordinatorDelegate? { get set }
func process(viewAction: KeyBackupSetupPassphraseViewAction)
}
@@ -0,0 +1,24 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// KeyBackupSetupPassphraseViewController view state
enum KeyBackupSetupPassphraseViewState {
case loading
case loaded
case error(Error)
}
@@ -0,0 +1,131 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import Reusable
final class PasswordStrengthView: UIView, NibOwnerLoadable {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var firstStrengthView: UIView!
@IBOutlet private weak var secondStrengthView: UIView!
@IBOutlet private weak var thirdStrengthView: UIView!
@IBOutlet private weak var fourthStrengthView: UIView!
// MARK: Private
private var strengthViews: [UIView] = []
private let strengthViewDefaultColor = UIColor(rgb: 0x9E9E9E)
private var strengthViewColors: [Int: UIColor] = [
0: UIColor(rgb: 0xF56679),
1: UIColor(rgb: 0xFFC666),
2: UIColor(rgb: 0xF8E71C),
3: UIColor(rgb: 0x7AC9A1)
]
// MARK: Public
var strength: PasswordStrength = .tooGuessable {
didSet {
self.updateStrengthColors()
}
}
// MARK: - Setup
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.loadNibContent()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.loadNibContent()
}
// MARK: - Life cycle
override func awakeFromNib() {
super.awakeFromNib()
self.strengthViews = [self.firstStrengthView,
self.secondStrengthView,
self.thirdStrengthView,
self.fourthStrengthView]
for strenghView in self.strengthViews {
strenghView.layer.masksToBounds = true
}
}
override func layoutSubviews() {
super.layoutSubviews()
for strenghView in self.strengthViews {
strenghView.layer.cornerRadius = strenghView.bounds.height/2
}
}
// MARK: - Private
private func updateStrengthColors() {
let strengthViewIndex: Int
switch self.strength {
case .tooGuessable, .veryGuessable:
strengthViewIndex = 0
case .somewhatGuessable:
strengthViewIndex = 1
case .safelyUnguessable:
strengthViewIndex = 2
case .veryUnguessable:
strengthViewIndex = 3
}
self.color(until: strengthViewIndex)
}
private func color(until strengthViewIndex: Int) {
var index: Int = 0
for strenghView in self.strengthViews {
let color: UIColor
if index <= strengthViewIndex {
color = self.color(for: index)
} else {
color = self.strengthViewDefaultColor
}
strenghView.backgroundColor = color
index+=1
}
}
private func color(for index: Int) -> UIColor {
guard let color = self.strengthViewColors[index] else {
return self.strengthViewDefaultColor
}
return color
}
}
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PasswordStrengthView" customModule="Riot" customModuleProvider="target">
<connections>
<outlet property="firstStrengthView" destination="yIt-rD-VDt" id="y2A-O5-hAs"/>
<outlet property="fourthStrengthView" destination="OwU-a7-PfS" id="EPH-gr-SqH"/>
<outlet property="secondStrengthView" destination="jCg-01-X4V" id="9ms-Od-kB8"/>
<outlet property="thirdStrengthView" destination="cme-Tu-KKT" id="2tF-if-InI"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="548" height="13"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="UfG-F9-gZO">
<rect key="frame" x="0.0" y="0.0" width="548" height="13"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yIt-rD-VDt">
<rect key="frame" x="0.0" y="0.0" width="133.5" height="13"/>
<color key="backgroundColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jCg-01-X4V">
<rect key="frame" x="138.5" y="0.0" width="133" height="13"/>
<color key="backgroundColor" red="1" green="0.57637232540000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cme-Tu-KKT">
<rect key="frame" x="276.5" y="0.0" width="133.5" height="13"/>
<color key="backgroundColor" red="0.99942404029999998" green="0.98555368190000003" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OwU-a7-PfS">
<rect key="frame" x="415" y="0.0" width="133" height="13"/>
<color key="backgroundColor" red="0.0" green="0.97680455450000003" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="UfG-F9-gZO" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="7BS-57-ecz"/>
<constraint firstAttribute="trailing" secondItem="UfG-F9-gZO" secondAttribute="trailing" id="WAP-t7-Ew7"/>
<constraint firstAttribute="bottom" secondItem="UfG-F9-gZO" secondAttribute="bottom" id="ZKs-hJ-jRh"/>
<constraint firstItem="UfG-F9-gZO" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="xbQ-C6-B1J"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="36.799999999999997" y="-192.05397301349328"/>
</view>
</objects>
</document>