mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-20 16:42:44 +02:00
Secrets recover: Move files.
This commit is contained in:
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
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 SecretsRecoveryWithPassphraseCoordinator: SecretsRecoveryWithPassphraseCoordinatorType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let secretsRecoveryWithPassphraseViewController: SecretsRecoveryWithPassphraseViewController
|
||||
private var secretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphraseViewModelType
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: SecretsRecoveryWithPassphraseCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal) {
|
||||
let secretsRecoveryWithPassphraseViewModel = SecretsRecoveryWithPassphraseViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal)
|
||||
let secretsRecoveryWithPassphraseViewController = SecretsRecoveryWithPassphraseViewController.instantiate(with: secretsRecoveryWithPassphraseViewModel)
|
||||
self.secretsRecoveryWithPassphraseViewController = secretsRecoveryWithPassphraseViewController
|
||||
self.secretsRecoveryWithPassphraseViewModel = secretsRecoveryWithPassphraseViewModel
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
self.secretsRecoveryWithPassphraseViewModel.coordinatorDelegate = self
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.secretsRecoveryWithPassphraseViewController
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SecretsRecoveryWithPassphraseViewModelCoordinatorDelegate
|
||||
extension SecretsRecoveryWithPassphraseCoordinator: SecretsRecoveryWithPassphraseViewModelCoordinatorDelegate {
|
||||
func secretsRecoveryWithPassphraseViewModelWantsToRecoverByKey(_ viewModel: SecretsRecoveryWithPassphraseViewModelType) {
|
||||
self.delegate?.secretsRecoveryWithPassphraseCoordinatorDoNotKnowPassphrase(self)
|
||||
}
|
||||
|
||||
func secretsRecoveryWithPassphraseViewModelDidRecover(_ viewModel: SecretsRecoveryWithPassphraseViewModelType) { self.delegate?.secretsRecoveryWithPassphraseCoordinatorDidRecover(self)
|
||||
}
|
||||
|
||||
func secretsRecoveryWithPassphraseViewModelDidCancel(_ viewModel: SecretsRecoveryWithPassphraseViewModelType) { self.delegate?.secretsRecoveryWithPassphraseCoordinatorDidCancel(self)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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 SecretsRecoveryWithPassphraseCoordinatorDelegate: class {
|
||||
func secretsRecoveryWithPassphraseCoordinatorDidRecover(_ coordinator: SecretsRecoveryWithPassphraseCoordinatorType)
|
||||
func secretsRecoveryWithPassphraseCoordinatorDoNotKnowPassphrase(_ coordinator: SecretsRecoveryWithPassphraseCoordinatorType)
|
||||
func secretsRecoveryWithPassphraseCoordinatorDidCancel(_ coordinator: SecretsRecoveryWithPassphraseCoordinatorType)
|
||||
}
|
||||
|
||||
/// `SecretsRecoveryWithPassphraseCoordinatorType` is a protocol describing a Coordinator that handle key backup passphrase recover navigation flow.
|
||||
protocol SecretsRecoveryWithPassphraseCoordinatorType: Coordinator, Presentable {
|
||||
var delegate: SecretsRecoveryWithPassphraseCoordinatorDelegate? { get }
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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
|
||||
|
||||
/// SecretsRecoveryWithPassphraseViewController view actions exposed to view model
|
||||
enum SecretsRecoveryWithPassphraseViewAction {
|
||||
case recover
|
||||
case useRecoveryKey
|
||||
case cancel
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
<?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="KkK-aQ-7Ig">
|
||||
<device id="retina4_7" 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 Recovery With Passphrase View Controller-->
|
||||
<scene sceneID="r1I-YV-Fog">
|
||||
<objects>
|
||||
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="KkK-aQ-7Ig" customClass="SecretsRecoveryWithPassphraseViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8SG-gc-Id7">
|
||||
<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="FYl-Bb-Kpe">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dlf-fL-IPA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="423"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gw9-uS-bGl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="423"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="secrets_recovery_passphrase" translatesAutoresizingMaskIntoConstraints="NO" id="hA4-wJ-xGz">
|
||||
<rect key="frame" x="165.5" y="35" width="44" height="48"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="6ho-II-3gd"/>
|
||||
<constraint firstAttribute="height" constant="48" id="xDH-Af-ISa"/>
|
||||
</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="p2V-aL-g0y">
|
||||
<rect key="frame" x="20" y="113" width="335" 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="uly-5I-NIc">
|
||||
<rect key="frame" x="0.0" y="207" width="375" height="50"/>
|
||||
<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="I7Q-Tb-YGd">
|
||||
<rect key="frame" x="20" y="10" width="38" height="30"/>
|
||||
<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="rAd-wZ-jgA">
|
||||
<rect key="frame" x="78" y="0.0" width="243" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="iy4-UK-b6r"/>
|
||||
</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="KkK-aQ-7Ig" id="cti-9X-BOh"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ahr-Zq-UM4">
|
||||
<rect key="frame" x="321" y="3" width="44" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="2WM-IG-od1"/>
|
||||
<constraint firstAttribute="height" constant="44" id="6DP-64-vVH"/>
|
||||
</constraints>
|
||||
<state key="normal" image="reveal_password_button"/>
|
||||
<connections>
|
||||
<action selector="passphraseVisibilityButtonAction:" destination="KkK-aQ-7Ig" eventType="touchUpInside" id="QYR-na-0HN"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="I7Q-Tb-YGd" secondAttribute="bottom" constant="10" id="56l-Mf-bJF"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="rAd-wZ-jgA" secondAttribute="bottom" id="D6q-RB-bGD"/>
|
||||
<constraint firstItem="rAd-wZ-jgA" firstAttribute="top" relation="greaterThanOrEqual" secondItem="uly-5I-NIc" secondAttribute="top" id="OFF-nn-6xF"/>
|
||||
<constraint firstItem="ahr-Zq-UM4" firstAttribute="centerY" secondItem="rAd-wZ-jgA" secondAttribute="centerY" id="OUT-sb-ah0"/>
|
||||
<constraint firstItem="I7Q-Tb-YGd" firstAttribute="centerY" secondItem="rAd-wZ-jgA" secondAttribute="centerY" id="XO6-Hc-U8v"/>
|
||||
<constraint firstItem="I7Q-Tb-YGd" firstAttribute="top" secondItem="uly-5I-NIc" secondAttribute="top" constant="10" id="Yba-F0-C4v"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ahr-Zq-UM4" secondAttribute="trailing" constant="10" id="lEF-2g-3pp"/>
|
||||
<constraint firstItem="I7Q-Tb-YGd" firstAttribute="centerY" secondItem="uly-5I-NIc" secondAttribute="centerY" id="mDZ-Lf-ya1"/>
|
||||
<constraint firstItem="ahr-Zq-UM4" firstAttribute="leading" secondItem="rAd-wZ-jgA" secondAttribute="trailing" id="nGm-Xg-tUR"/>
|
||||
<constraint firstItem="I7Q-Tb-YGd" firstAttribute="leading" secondItem="uly-5I-NIc" secondAttribute="leading" constant="20" id="oKp-YW-yLc"/>
|
||||
<constraint firstItem="rAd-wZ-jgA" firstAttribute="leading" secondItem="I7Q-Tb-YGd" secondAttribute="trailing" priority="750" constant="10" id="sfe-6L-QD2"/>
|
||||
<constraint firstItem="rAd-wZ-jgA" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="I7Q-Tb-YGd" secondAttribute="trailing" constant="20" id="vyG-oS-use"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="a20-Ii-sAN">
|
||||
<rect key="frame" x="0.0" y="273" width="375" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="9rW-yL-qS1"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
|
||||
<state key="normal" title="Don’t know your recovery passphrase ? You can use your recovery key.">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</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="useRecoveryKeyButtonAction:" destination="KkK-aQ-7Ig" eventType="touchUpInside" id="fXd-03-45l"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zOv-dc-49b">
|
||||
<rect key="frame" x="0.0" y="353" width="375" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DpI-8g-yKB" customClass="RoundedButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="0.0" width="335" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="Ghb-Uq-q6w"/>
|
||||
</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="recoverButtonAction:" destination="KkK-aQ-7Ig" eventType="touchUpInside" id="o61-R0-Uwr"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="DpI-8g-yKB" firstAttribute="centerX" secondItem="zOv-dc-49b" secondAttribute="centerX" id="Hr6-9M-GiX"/>
|
||||
<constraint firstAttribute="bottom" secondItem="DpI-8g-yKB" secondAttribute="bottom" id="TTL-7C-OLb"/>
|
||||
<constraint firstItem="DpI-8g-yKB" firstAttribute="top" secondItem="zOv-dc-49b" secondAttribute="top" id="TtN-kR-msg"/>
|
||||
<constraint firstItem="DpI-8g-yKB" firstAttribute="width" secondItem="zOv-dc-49b" secondAttribute="width" priority="750" id="q7z-h9-oc7"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="zOv-dc-49b" secondAttribute="bottom" constant="20" id="3Wl-WT-6Et"/>
|
||||
<constraint firstAttribute="trailing" secondItem="uly-5I-NIc" secondAttribute="trailing" id="6VB-MQ-hIp"/>
|
||||
<constraint firstItem="p2V-aL-g0y" firstAttribute="top" secondItem="hA4-wJ-xGz" secondAttribute="bottom" constant="30" id="6eX-cP-a3F"/>
|
||||
<constraint firstAttribute="trailing" secondItem="zOv-dc-49b" secondAttribute="trailing" id="9er-kg-arg"/>
|
||||
<constraint firstAttribute="width" priority="750" constant="500" id="NAT-Cc-oHN"/>
|
||||
<constraint firstItem="a20-Ii-sAN" firstAttribute="top" secondItem="uly-5I-NIc" secondAttribute="bottom" constant="16" id="P9q-sL-AcP"/>
|
||||
<constraint firstItem="p2V-aL-g0y" firstAttribute="leading" secondItem="Gw9-uS-bGl" secondAttribute="leading" constant="20" id="VM5-6u-8kW"/>
|
||||
<constraint firstItem="uly-5I-NIc" firstAttribute="top" secondItem="p2V-aL-g0y" secondAttribute="bottom" constant="40" id="b6d-xb-RsF"/>
|
||||
<constraint firstItem="zOv-dc-49b" firstAttribute="top" secondItem="a20-Ii-sAN" secondAttribute="bottom" constant="30" id="b6e-I5-UmV"/>
|
||||
<constraint firstItem="zOv-dc-49b" firstAttribute="leading" secondItem="Gw9-uS-bGl" secondAttribute="leading" id="bdZ-LL-sEK"/>
|
||||
<constraint firstItem="uly-5I-NIc" firstAttribute="leading" secondItem="Gw9-uS-bGl" secondAttribute="leading" id="cbx-lF-FxP"/>
|
||||
<constraint firstAttribute="trailing" secondItem="p2V-aL-g0y" secondAttribute="trailing" constant="20" id="ebM-3Y-G7G"/>
|
||||
<constraint firstItem="hA4-wJ-xGz" firstAttribute="centerX" secondItem="Gw9-uS-bGl" secondAttribute="centerX" id="v1j-88-njw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="a20-Ii-sAN" secondAttribute="trailing" id="ysR-iF-6Wq"/>
|
||||
<constraint firstItem="hA4-wJ-xGz" firstAttribute="top" secondItem="Gw9-uS-bGl" secondAttribute="top" constant="35" id="zVj-yd-Zo3"/>
|
||||
<constraint firstItem="a20-Ii-sAN" firstAttribute="leading" secondItem="Gw9-uS-bGl" secondAttribute="leading" id="zZ6-IT-SKQ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="DpI-8g-yKB" secondAttribute="trailing" constant="20" id="GWL-GS-AAo"/>
|
||||
<constraint firstItem="DpI-8g-yKB" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dlf-fL-IPA" secondAttribute="leading" constant="20" id="WtX-De-uw7"/>
|
||||
<constraint firstItem="Gw9-uS-bGl" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dlf-fL-IPA" secondAttribute="leading" id="dS8-xZ-yYp"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Gw9-uS-bGl" secondAttribute="bottom" id="npc-qR-fYH"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Gw9-uS-bGl" secondAttribute="trailing" id="sjh-zi-XoD"/>
|
||||
<constraint firstItem="Gw9-uS-bGl" firstAttribute="centerX" secondItem="dlf-fL-IPA" secondAttribute="centerX" id="weL-9S-YWG"/>
|
||||
<constraint firstItem="Gw9-uS-bGl" firstAttribute="top" secondItem="dlf-fL-IPA" secondAttribute="top" id="yCz-MS-etd"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dlf-fL-IPA" firstAttribute="width" secondItem="FYl-Bb-Kpe" secondAttribute="width" id="QEO-LC-HhE"/>
|
||||
<constraint firstItem="dlf-fL-IPA" firstAttribute="top" secondItem="FYl-Bb-Kpe" secondAttribute="top" id="bMA-tC-YZy"/>
|
||||
<constraint firstAttribute="bottom" secondItem="dlf-fL-IPA" secondAttribute="bottom" id="guQ-5P-dDE"/>
|
||||
<constraint firstItem="dlf-fL-IPA" firstAttribute="leading" secondItem="FYl-Bb-Kpe" secondAttribute="leading" id="iiH-iB-Mhj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="dlf-fL-IPA" secondAttribute="trailing" id="mUQ-Pn-DID"/>
|
||||
</constraints>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="FYl-Bb-Kpe" firstAttribute="leading" secondItem="9Os-Vv-Xnb" secondAttribute="leading" id="GOs-MF-rUL"/>
|
||||
<constraint firstAttribute="bottom" secondItem="FYl-Bb-Kpe" secondAttribute="bottom" id="Phv-cj-t2w"/>
|
||||
<constraint firstItem="9Os-Vv-Xnb" firstAttribute="trailing" secondItem="FYl-Bb-Kpe" secondAttribute="trailing" id="SYj-74-mpC"/>
|
||||
<constraint firstItem="9Os-Vv-Xnb" firstAttribute="top" secondItem="FYl-Bb-Kpe" secondAttribute="top" id="dg5-In-5U7"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="9Os-Vv-Xnb"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="informationLabel" destination="p2V-aL-g0y" id="f0u-LY-N8z"/>
|
||||
<outlet property="passphraseTextField" destination="rAd-wZ-jgA" id="IG1-XG-h4B"/>
|
||||
<outlet property="passphraseTextFieldBackgroundView" destination="uly-5I-NIc" id="CR8-V3-RhB"/>
|
||||
<outlet property="passphraseTitleLabel" destination="I7Q-Tb-YGd" id="jrA-8B-fsw"/>
|
||||
<outlet property="passphraseVisibilityButton" destination="ahr-Zq-UM4" id="95l-go-Yjj"/>
|
||||
<outlet property="recoverButton" destination="DpI-8g-yKB" id="aA6-fD-whb"/>
|
||||
<outlet property="scrollView" destination="FYl-Bb-Kpe" id="jR3-VH-AdU"/>
|
||||
<outlet property="shieldImageView" destination="hA4-wJ-xGz" id="MPg-q1-UVx"/>
|
||||
<outlet property="useRecoveryKeyButton" destination="a20-Ii-sAN" id="ZMQ-PQ-jEn"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="1uq-Io-hFy" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-3772" y="-774"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="reveal_password_button" width="24" height="18"/>
|
||||
<image name="secrets_recovery_passphrase" width="44" height="48"/>
|
||||
</resources>
|
||||
</document>
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
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 SecretsRecoveryWithPassphraseViewController: UIViewController {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Outlets
|
||||
|
||||
@IBOutlet private weak var scrollView: UIScrollView!
|
||||
|
||||
@IBOutlet private weak var shieldImageView: UIImageView!
|
||||
|
||||
@IBOutlet private weak var informationLabel: UILabel!
|
||||
|
||||
@IBOutlet private weak var passphraseTitleLabel: UILabel!
|
||||
@IBOutlet private weak var passphraseTextField: UITextField!
|
||||
@IBOutlet private weak var passphraseTextFieldBackgroundView: UIView!
|
||||
|
||||
@IBOutlet private weak var passphraseVisibilityButton: UIButton!
|
||||
|
||||
@IBOutlet private weak var useRecoveryKeyButton: UIButton!
|
||||
|
||||
@IBOutlet private weak var recoverButton: RoundedButton!
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var viewModel: SecretsRecoveryWithPassphraseViewModelType!
|
||||
private var keyboardAvoider: KeyboardAvoider?
|
||||
private var theme: Theme!
|
||||
private var errorPresenter: MXKErrorPresentation!
|
||||
private var activityPresenter: ActivityIndicatorPresenter!
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
class func instantiate(with viewModel: SecretsRecoveryWithPassphraseViewModelType) -> SecretsRecoveryWithPassphraseViewController {
|
||||
let viewController = StoryboardScene.SecretsRecoveryWithPassphraseViewController.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.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 var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return self.theme.statusBarStyle
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupViews() {
|
||||
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
|
||||
self?.viewModel.process(viewAction: .cancel)
|
||||
}
|
||||
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
|
||||
|
||||
self.title = VectorL10n.secretsRecoveryWithPassphraseTitle
|
||||
|
||||
self.scrollView.keyboardDismissMode = .interactive
|
||||
|
||||
let shieldImage = Asset.Images.secretsRecoveryPassphrase.image.withRenderingMode(.alwaysTemplate)
|
||||
self.shieldImageView.image = shieldImage
|
||||
|
||||
let visibilityImage = Asset.Images.revealPasswordButton.image.withRenderingMode(.alwaysTemplate)
|
||||
self.passphraseVisibilityButton.setImage(visibilityImage, for: .normal)
|
||||
|
||||
let informationText: String
|
||||
|
||||
switch self.viewModel.recoveryGoal {
|
||||
case .default, .keyBackup:
|
||||
informationText = VectorL10n.secretsRecoveryWithPassphraseInformationDefault
|
||||
case .verifyDevice:
|
||||
informationText = VectorL10n.secretsRecoveryWithPassphraseInformationVerifyDevice
|
||||
}
|
||||
|
||||
self.informationLabel.text = informationText
|
||||
|
||||
self.passphraseTitleLabel.text = VectorL10n.secretsRecoveryWithPassphrasePassphraseTitle
|
||||
self.passphraseTextField.addTarget(self, action: #selector(passphraseTextFieldDidChange(_:)), for: .editingChanged)
|
||||
|
||||
self.useRecoveryKeyButton.vc_enableMultiLinesTitle()
|
||||
|
||||
self.recoverButton.vc_enableMultiLinesTitle()
|
||||
self.recoverButton.setTitle(VectorL10n.secretsRecoveryWithPassphraseRecoverAction, for: .normal)
|
||||
|
||||
self.updateRecoverButton()
|
||||
}
|
||||
|
||||
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.shieldImageView.tintColor = theme.textPrimaryColor
|
||||
|
||||
self.passphraseTextFieldBackgroundView.backgroundColor = theme.backgroundColor
|
||||
self.passphraseTitleLabel.textColor = theme.textPrimaryColor
|
||||
theme.applyStyle(onTextField: self.passphraseTextField)
|
||||
self.passphraseTextField.attributedPlaceholder = NSAttributedString(string: VectorL10n.secretsRecoveryWithPassphrasePassphrasePlaceholder,
|
||||
attributes: [.foregroundColor: theme.placeholderTextColor])
|
||||
|
||||
self.theme.applyStyle(onButton: self.passphraseVisibilityButton)
|
||||
|
||||
self.recoverButton.update(theme: theme)
|
||||
|
||||
let useRecoveryKeyAttributedString = NSMutableAttributedString(string: VectorL10n.secretsRecoveryWithPassphraseLostPassphraseActionPart1, attributes: [.foregroundColor: self.theme.textPrimaryColor])
|
||||
let unknownRecoveryKeyAttributedStringPart2 = NSAttributedString(string: VectorL10n.secretsRecoveryWithPassphraseLostPassphraseActionPart2, attributes: [.foregroundColor: self.theme.tintColor])
|
||||
let unknownRecoveryKeyAttributedStringPart3 = NSAttributedString(string: VectorL10n.secretsRecoveryWithPassphraseLostPassphraseActionPart3, attributes: [.foregroundColor: self.theme.textPrimaryColor])
|
||||
|
||||
useRecoveryKeyAttributedString.append(unknownRecoveryKeyAttributedStringPart2)
|
||||
useRecoveryKeyAttributedString.append(unknownRecoveryKeyAttributedStringPart3)
|
||||
|
||||
self.useRecoveryKeyButton.setAttributedTitle(useRecoveryKeyAttributedString, for: .normal)
|
||||
}
|
||||
|
||||
private func registerThemeServiceDidChangeThemeNotification() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
|
||||
}
|
||||
|
||||
@objc private func themeDidChange() {
|
||||
self.update(theme: ThemeService.shared().theme)
|
||||
}
|
||||
|
||||
private func updateRecoverButton() {
|
||||
self.recoverButton.isEnabled = self.viewModel.isFormValid
|
||||
}
|
||||
|
||||
private func render(viewState: SecretsRecoveryWithPassphraseViewState) {
|
||||
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)
|
||||
|
||||
let nsError = error as NSError
|
||||
|
||||
if nsError.domain == MXRecoveryServiceErrorDomain
|
||||
&& nsError.code == Int(MXRecoveryServiceErrorCode.badRecoveryKeyErrorCode.rawValue) {
|
||||
|
||||
self.errorPresenter.presentError(from: self,
|
||||
title: VectorL10n.secretsRecoveryWithPassphraseInvalidPassphraseTitle,
|
||||
message: VectorL10n.secretsRecoveryWithPassphraseInvalidPassphraseMessage,
|
||||
animated: true,
|
||||
handler: nil)
|
||||
} else {
|
||||
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction private func passphraseVisibilityButtonAction(_ sender: Any) {
|
||||
self.passphraseTextField.isSecureTextEntry = !self.passphraseTextField.isSecureTextEntry
|
||||
}
|
||||
|
||||
@objc private func passphraseTextFieldDidChange(_ textField: UITextField) {
|
||||
self.viewModel.passphrase = textField.text
|
||||
self.updateRecoverButton()
|
||||
}
|
||||
|
||||
@IBAction private func recoverButtonAction(_ sender: Any) {
|
||||
self.viewModel.process(viewAction: .recover)
|
||||
}
|
||||
|
||||
@IBAction private func useRecoveryKeyButtonAction(_ sender: Any) {
|
||||
self.viewModel.process(viewAction: .useRecoveryKey)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITextFieldDelegate
|
||||
extension SecretsRecoveryWithPassphraseViewController: UITextFieldDelegate {
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField.resignFirstResponder()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SecretsRecoveryWithPassphraseViewModelViewDelegate
|
||||
extension SecretsRecoveryWithPassphraseViewController: SecretsRecoveryWithPassphraseViewModelViewDelegate {
|
||||
func secretsRecoveryWithPassphraseViewModel(_ viewModel: SecretsRecoveryWithPassphraseViewModelType, didUpdateViewState viewSate: SecretsRecoveryWithPassphraseViewState) {
|
||||
self.render(viewState: viewSate)
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphraseViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let recoveryService: MXRecoveryService
|
||||
|
||||
// MARK: Public
|
||||
|
||||
let recoveryGoal: SecretsRecoveryGoal
|
||||
|
||||
var passphrase: String?
|
||||
|
||||
var isFormValid: Bool {
|
||||
return self.passphrase?.isEmpty == false
|
||||
}
|
||||
|
||||
weak var viewDelegate: SecretsRecoveryWithPassphraseViewModelViewDelegate?
|
||||
weak var coordinatorDelegate: SecretsRecoveryWithPassphraseViewModelCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal) {
|
||||
self.recoveryService = recoveryService
|
||||
self.recoveryGoal = recoveryGoal
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func process(viewAction: SecretsRecoveryWithPassphraseViewAction) {
|
||||
switch viewAction {
|
||||
case .recover:
|
||||
self.recoverWithPassphrase()
|
||||
case .cancel:
|
||||
self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelDidCancel(self)
|
||||
case .useRecoveryKey:
|
||||
self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelWantsToRecoverByKey(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func recoverWithPassphrase() {
|
||||
guard let passphrase = self.passphrase else {
|
||||
return
|
||||
}
|
||||
|
||||
self.update(viewState: .loading)
|
||||
|
||||
self.recoveryService.privateKey(fromPassphrase: passphrase, success: { [weak self] privateKey in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let secretIds: [String]?
|
||||
|
||||
if case SecretsRecoveryGoal.keyBackup = self.recoveryGoal {
|
||||
secretIds = [MXSecretId.keyBackup.takeUnretainedValue() as String]
|
||||
} else {
|
||||
secretIds = nil
|
||||
}
|
||||
|
||||
self.recoveryService.recoverSecrets(secretIds, withPrivateKey: privateKey, recoverServices: true, success: { [weak self] _ in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.update(viewState: .loaded)
|
||||
self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelDidRecover(self)
|
||||
}, failure: { [weak self] error in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.update(viewState: .error(error))
|
||||
})
|
||||
|
||||
}, failure: { [weak self] error in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.update(viewState: .error(error))
|
||||
})
|
||||
}
|
||||
|
||||
private func update(viewState: SecretsRecoveryWithPassphraseViewState) {
|
||||
self.viewDelegate?.secretsRecoveryWithPassphraseViewModel(self, didUpdateViewState: viewState)
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 SecretsRecoveryWithPassphraseViewModelViewDelegate: class {
|
||||
func secretsRecoveryWithPassphraseViewModel(_ viewModel: SecretsRecoveryWithPassphraseViewModelType, didUpdateViewState viewSate: SecretsRecoveryWithPassphraseViewState)
|
||||
}
|
||||
|
||||
protocol SecretsRecoveryWithPassphraseViewModelCoordinatorDelegate: class {
|
||||
func secretsRecoveryWithPassphraseViewModelDidRecover(_ viewModel: SecretsRecoveryWithPassphraseViewModelType)
|
||||
func secretsRecoveryWithPassphraseViewModelDidCancel(_ viewModel: SecretsRecoveryWithPassphraseViewModelType)
|
||||
func secretsRecoveryWithPassphraseViewModelWantsToRecoverByKey(_ viewModel: SecretsRecoveryWithPassphraseViewModelType)
|
||||
}
|
||||
|
||||
/// Protocol describing the view model used by `SecretsRecoveryWithPassphraseViewController`
|
||||
protocol SecretsRecoveryWithPassphraseViewModelType {
|
||||
|
||||
var passphrase: String? { get set }
|
||||
var isFormValid: Bool { get }
|
||||
var recoveryGoal: SecretsRecoveryGoal { get }
|
||||
|
||||
var viewDelegate: SecretsRecoveryWithPassphraseViewModelViewDelegate? { get set }
|
||||
var coordinatorDelegate: SecretsRecoveryWithPassphraseViewModelCoordinatorDelegate? { get set }
|
||||
|
||||
func process(viewAction: SecretsRecoveryWithPassphraseViewAction)
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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
|
||||
|
||||
/// SecretsRecoveryWithPassphraseViewController view state
|
||||
enum SecretsRecoveryWithPassphraseViewState {
|
||||
case loading
|
||||
case loaded
|
||||
case error(Error)
|
||||
}
|
||||
Reference in New Issue
Block a user