Refactor DeviceVerification prefix classes to KeyVerification where needed. And move key verification related classes into the same module KeyVerification.

This commit is contained in:
SBiOSoftWhare
2020-03-18 17:39:26 +01:00
parent a8f28e0ec0
commit d6296b3150
91 changed files with 819 additions and 803 deletions
@@ -0,0 +1,79 @@
/*
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
import Reusable
struct UserVerificationSessionStatusViewData {
let deviceId: String
let sessionName: String
let isTrusted: Bool
}
final class UserVerificationSessionStatusCell: UITableViewCell, NibReusable, Themable {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var statusImageView: UIImageView!
@IBOutlet private weak var sessionNameLabel: UILabel!
@IBOutlet private weak var statusTextLabel: UILabel!
// MARK: Private
private var viewData: UserVerificationSessionStatusViewData?
private var theme: Theme?
// MARK: - Public
func fill(viewData: UserVerificationSessionStatusViewData) {
self.viewData = viewData
let statusText: String
let statusImage: UIImage
if viewData.isTrusted {
statusImage = Asset.Images.encryptionTrusted.image
statusText = VectorL10n.userVerificationSessionsListSessionTrusted
} else {
statusImage = Asset.Images.encryptionWarning.image
statusText = VectorL10n.userVerificationSessionsListSessionUntrusted
}
self.statusImageView.image = statusImage
self.statusTextLabel.text = statusText
self.sessionNameLabel.text = viewData.sessionName
self.updateStatusTextColor()
}
func update(theme: Theme) {
self.theme = theme
self.backgroundColor = theme.headerBackgroundColor
self.sessionNameLabel.textColor = theme.textPrimaryColor
self.updateStatusTextColor()
}
// MARK: - Private
private func updateStatusTextColor() {
guard let viewData = self.viewData, let theme = self.theme else {
return
}
self.statusTextLabel.textColor = viewData.isTrusted ? theme.tintColor : theme.warningColor
}
}
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="37" id="KGk-i7-Jjw" customClass="UserVerificationSessionStatusCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="307" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="307" height="36.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="V5p-pi-DJj">
<rect key="frame" x="0.0" y="0.0" width="307" height="36.5"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_warning" translatesAutoresizingMaskIntoConstraints="NO" id="4Ja-9R-o9i" customClass="encryption_warning">
<rect key="frame" x="20" y="11.5" width="14" height="14"/>
<constraints>
<constraint firstAttribute="width" secondItem="4Ja-9R-o9i" secondAttribute="height" multiplier="1:1" id="Ivd-S1-mib"/>
<constraint firstAttribute="width" constant="14" id="n0o-Xb-nnn"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Riot iOS" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MkZ-9n-kCS">
<rect key="frame" x="44" y="10" width="174.5" height="16.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="752" text="Trusted" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wsp-qe-DLy">
<rect key="frame" x="228.5" y="8" width="58.5" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.01176470588" green="0.70196078429999997" blue="0.50588235290000005" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="MkZ-9n-kCS" firstAttribute="leading" secondItem="4Ja-9R-o9i" secondAttribute="trailing" constant="10" id="29O-OV-c6O"/>
<constraint firstItem="4Ja-9R-o9i" firstAttribute="leading" secondItem="V5p-pi-DJj" secondAttribute="leading" constant="20" id="Gcu-EC-b72"/>
<constraint firstItem="wsp-qe-DLy" firstAttribute="centerY" secondItem="MkZ-9n-kCS" secondAttribute="centerY" id="JQR-uR-OSm"/>
<constraint firstAttribute="bottom" secondItem="MkZ-9n-kCS" secondAttribute="bottom" constant="10" id="Ohk-Zx-SaA"/>
<constraint firstAttribute="trailing" secondItem="wsp-qe-DLy" secondAttribute="trailing" constant="20" id="Onn-ew-tSg"/>
<constraint firstItem="4Ja-9R-o9i" firstAttribute="centerY" secondItem="MkZ-9n-kCS" secondAttribute="centerY" id="U88-nG-bpa"/>
<constraint firstItem="wsp-qe-DLy" firstAttribute="leading" secondItem="MkZ-9n-kCS" secondAttribute="trailing" constant="10" id="VdB-e0-Ru9"/>
<constraint firstItem="MkZ-9n-kCS" firstAttribute="top" secondItem="V5p-pi-DJj" secondAttribute="top" constant="10" id="lx8-Ae-MX8"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="V5p-pi-DJj" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="3rQ-mA-tnY"/>
<constraint firstItem="V5p-pi-DJj" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="PDK-oM-MhJ"/>
<constraint firstAttribute="bottom" secondItem="V5p-pi-DJj" secondAttribute="bottom" id="dj0-s9-48N"/>
<constraint firstAttribute="trailing" secondItem="V5p-pi-DJj" secondAttribute="trailing" id="lp6-TZ-ycQ"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="sessionNameLabel" destination="MkZ-9n-kCS" id="atH-sc-sJx"/>
<outlet property="statusImageView" destination="4Ja-9R-o9i" id="n6o-LV-b3O"/>
<outlet property="statusTextLabel" destination="wsp-qe-DLy" id="4dV-vY-pmR"/>
</connections>
<point key="canvasLocation" x="-72" y="26"/>
</tableViewCell>
</objects>
<resources>
<image name="encryption_warning" width="16" height="16"/>
</resources>
</document>
@@ -0,0 +1,70 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var userVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType
private let userVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: UserVerificationSessionsStatusCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, userId: String) {
self.session = session
let userVerificationSessionsStatusViewModel = UserVerificationSessionsStatusViewModel(session: self.session, userId: userId)
let userVerificationSessionsStatusViewController = UserVerificationSessionsStatusViewController.instantiate(with: userVerificationSessionsStatusViewModel)
self.userVerificationSessionsStatusViewModel = userVerificationSessionsStatusViewModel
self.userVerificationSessionsStatusViewController = userVerificationSessionsStatusViewController
}
// MARK: - Public methods
func start() {
self.userVerificationSessionsStatusViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.userVerificationSessionsStatusViewController
}
}
// MARK: - UserVerificationSessionsStatusViewModelCoordinatorDelegate
extension UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusViewModelCoordinatorDelegate {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String) {
self.delegate?.userVerificationSessionsStatusCoordinator(self, didSelectDeviceWithId: deviceId, for: userId)
}
func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType) {
self.delegate?.userVerificationSessionsStatusCoordinatorDidClose(self)
}
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
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 UserVerificationSessionsStatusCoordinatorDelegate: class {
func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType)
func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String)
}
/// `UserVerificationSessionsStatusCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol UserVerificationSessionsStatusCoordinatorType: Coordinator, Presentable {
var delegate: UserVerificationSessionsStatusCoordinatorDelegate? { get }
}
@@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
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
/// UserVerificationSessionsStatusViewController view actions exposed to view model
enum UserVerificationSessionsStatusViewAction {
case loadData
case selectSession(deviceId: String)
case close
}
@@ -0,0 +1,119 @@
<?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="le3-e8-C1h">
<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>
<!--User Verification Sessions Status View Controller-->
<scene sceneID="G1j-dD-ArC">
<objects>
<viewController id="le3-e8-C1h" customClass="UserVerificationSessionsStatusViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="IXd-J2-Vex">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kfr-Ho-krK">
<rect key="frame" x="0.0" y="20" width="375" height="108"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NBJ-A1-IP0">
<rect key="frame" x="325" y="20" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="jda-sE-4Uz"/>
<constraint firstAttribute="height" constant="30" id="rPb-uQ-Tyt"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<state key="normal" image="close_button"/>
<connections>
<action selector="closeButtonAction:" destination="le3-e8-C1h" eventType="touchUpInside" id="4JW-Rl-hIk"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_warning" translatesAutoresizingMaskIntoConstraints="NO" id="rZH-h1-WTw">
<rect key="frame" x="20" y="23" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" secondItem="rZH-h1-WTw" secondAttribute="height" multiplier="1:1" id="RgU-aV-cmS"/>
<constraint firstAttribute="height" constant="24" id="v0F-Dp-CmF"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Warning" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fcs-Vl-AtB">
<rect key="frame" x="54" y="23" width="261" height="24"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Messages with this user in this room are end-to-end encrypted and cant be read by third parties." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vfc-7p-LLs">
<rect key="frame" x="20" y="62" width="335" height="36"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="NBJ-A1-IP0" firstAttribute="leading" secondItem="fcs-Vl-AtB" secondAttribute="trailing" constant="10" id="0cd-68-5tR"/>
<constraint firstItem="fcs-Vl-AtB" firstAttribute="leading" secondItem="rZH-h1-WTw" secondAttribute="trailing" constant="10" id="75Z-TB-Hhj"/>
<constraint firstItem="fcs-Vl-AtB" firstAttribute="centerY" secondItem="NBJ-A1-IP0" secondAttribute="centerY" id="DVH-2n-tXo"/>
<constraint firstItem="Vfc-7p-LLs" firstAttribute="trailing" secondItem="NBJ-A1-IP0" secondAttribute="trailing" id="KQg-I1-5FP"/>
<constraint firstItem="rZH-h1-WTw" firstAttribute="centerY" secondItem="fcs-Vl-AtB" secondAttribute="centerY" id="N8y-sA-Am7"/>
<constraint firstItem="NBJ-A1-IP0" firstAttribute="top" secondItem="Kfr-Ho-krK" secondAttribute="top" constant="20" id="VUJ-Jj-UjR"/>
<constraint firstAttribute="trailing" secondItem="NBJ-A1-IP0" secondAttribute="trailing" constant="20" id="ZH0-2n-XKy"/>
<constraint firstItem="rZH-h1-WTw" firstAttribute="leading" secondItem="Kfr-Ho-krK" secondAttribute="leading" constant="20" id="gPw-ja-veq"/>
<constraint firstItem="Vfc-7p-LLs" firstAttribute="top" secondItem="fcs-Vl-AtB" secondAttribute="bottom" constant="15" id="qh5-nR-1EN"/>
<constraint firstAttribute="bottom" secondItem="Vfc-7p-LLs" secondAttribute="bottom" constant="10" id="t5D-ht-cSc"/>
<constraint firstItem="Vfc-7p-LLs" firstAttribute="leading" secondItem="rZH-h1-WTw" secondAttribute="leading" id="zAt-yf-7HQ"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sessions" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uMA-Dt-xTz">
<rect key="frame" x="20" y="133" width="335" height="24"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="drr-Gd-5wA">
<rect key="frame" x="0.0" y="167" width="375" height="500"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="dataSource" destination="le3-e8-C1h" id="Xmr-jN-S8s"/>
<outlet property="delegate" destination="le3-e8-C1h" id="Y5J-nA-Ynp"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="drr-Gd-5wA" firstAttribute="leading" secondItem="IXd-J2-Vex" secondAttribute="leading" id="52r-4B-JdK"/>
<constraint firstItem="Kfr-Ho-krK" firstAttribute="trailing" secondItem="Eud-3V-Vqw" secondAttribute="trailing" id="9Zg-EV-cHL"/>
<constraint firstItem="Eud-3V-Vqw" firstAttribute="trailing" secondItem="uMA-Dt-xTz" secondAttribute="trailing" constant="20" id="Amb-5F-Ors"/>
<constraint firstAttribute="trailing" secondItem="drr-Gd-5wA" secondAttribute="trailing" id="Awf-hM-uzJ"/>
<constraint firstItem="drr-Gd-5wA" firstAttribute="top" secondItem="uMA-Dt-xTz" secondAttribute="bottom" constant="10" id="H1a-sI-AbP"/>
<constraint firstAttribute="bottom" secondItem="drr-Gd-5wA" secondAttribute="bottom" id="Nhh-Ps-Upr"/>
<constraint firstItem="Kfr-Ho-krK" firstAttribute="top" secondItem="Eud-3V-Vqw" secondAttribute="top" id="Wxs-Uw-xoA"/>
<constraint firstItem="uMA-Dt-xTz" firstAttribute="leading" secondItem="Eud-3V-Vqw" secondAttribute="leading" constant="20" id="an1-BC-BP0"/>
<constraint firstItem="uMA-Dt-xTz" firstAttribute="top" secondItem="Kfr-Ho-krK" secondAttribute="bottom" constant="5" id="wok-Oq-W0z"/>
<constraint firstItem="Eud-3V-Vqw" firstAttribute="leading" secondItem="Kfr-Ho-krK" secondAttribute="leading" id="xjg-JA-ZUT"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Eud-3V-Vqw"/>
</view>
<connections>
<outlet property="badgeImageImageView" destination="rZH-h1-WTw" id="M2a-7i-OoW"/>
<outlet property="closeButton" destination="NBJ-A1-IP0" id="AYv-b7-fFp"/>
<outlet property="informationLabel" destination="Vfc-7p-LLs" id="em9-jE-1wo"/>
<outlet property="sessionsTableViewTitle" destination="uMA-Dt-xTz" id="vFU-UV-jMO"/>
<outlet property="tableView" destination="drr-Gd-5wA" id="Cs0-pF-wwB"/>
<outlet property="titleLabel" destination="fcs-Vl-AtB" id="iGp-kn-QfJ"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dBA-Zm-evE" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-463.19999999999999" y="-80.50974512743629"/>
</scene>
</scenes>
<resources>
<image name="close_button" width="16" height="16"/>
<image name="encryption_warning" width="16" height="16"/>
</resources>
</document>
@@ -0,0 +1,234 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
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 UserVerificationSessionsStatusViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let estimatedRowHeight: CGFloat = 40.0
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var badgeImageImageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var closeButton: UIButton!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var sessionsTableViewTitle: UILabel!
@IBOutlet private weak var tableView: UITableView!
// MARK: Private
private var viewModel: UserVerificationSessionsStatusViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityIndicatorPresenter: ActivityIndicatorPresenter!
private var sessionsStatusViewData: [UserVerificationSessionStatusViewData] = []
private var userEncryptionTrustLevel: UserEncryptionTrustLevel = .unknown
// MARK: - Setup
class func instantiate(with viewModel: UserVerificationSessionsStatusViewModelType) -> UserVerificationSessionsStatusViewController {
let viewController = StoryboardScene.UserVerificationSessionsStatusViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.setupViews()
self.vc_removeBackTitle()
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let selectedIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedIndexPath, animated: animated)
}
}
// 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.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal)
self.titleLabel.textColor = theme.textPrimaryColor
self.informationLabel.textColor = theme.textPrimaryColor
self.sessionsTableViewTitle.textColor = theme.textPrimaryColor
}
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() {
self.closeButton.layer.masksToBounds = true
self.setupTableView()
self.updateTitleViews()
self.sessionsTableViewTitle.text = VectorL10n.userVerificationSessionsListTableTitle
self.informationLabel.text = VectorL10n.userVerificationSessionsListInformation
}
private func setupTableView() {
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = Constants.estimatedRowHeight
self.tableView.separatorStyle = .none
self.tableView.tableFooterView = UIView()
self.tableView.alwaysBounceVertical = false
self.tableView.register(cellType: UserVerificationSessionStatusCell.self)
}
private func render(viewState: UserVerificationSessionsStatusViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(userTrustLevel: let userTrustLevel, sessionsStatusViewData: let sessionsStatusViewData):
self.renderLoaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.tableView.isUserInteractionEnabled = false
self.activityIndicatorPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData]) {
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
self.tableView.isUserInteractionEnabled = true
self.userEncryptionTrustLevel = userTrustLevel
self.sessionsStatusViewData = sessionsStatusViewData
self.updateTitleViews()
self.tableView.reloadData()
}
private func render(error: Error) {
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
self.tableView.isUserInteractionEnabled = true
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func updateTitleViews() {
let badgeImage: UIImage
let title: String
switch self.userEncryptionTrustLevel {
case .trusted:
badgeImage = Asset.Images.encryptionTrusted.image
title = VectorL10n.userVerificationSessionsListUserTrustLevelTrustedTitle
case .warning:
badgeImage = Asset.Images.encryptionWarning.image
title = VectorL10n.userVerificationSessionsListUserTrustLevelWarningTitle
default:
badgeImage = Asset.Images.encryptionNormal.image
title = VectorL10n.userVerificationSessionsListUserTrustLevelUnknownTitle
}
self.badgeImageImageView.image = badgeImage
self.titleLabel.text = title
}
// MARK: - Actions
@IBAction private func closeButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .close)
}
}
// MARK: - UITableViewDataSource
extension UserVerificationSessionsStatusViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sessionsStatusViewData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: UserVerificationSessionStatusCell.self)
let viewData = self.sessionsStatusViewData[indexPath.row]
cell.update(theme: self.theme)
cell.fill(viewData: viewData)
return cell
}
}
// MARK: - UITableViewDelegate
extension UserVerificationSessionsStatusViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewData = self.sessionsStatusViewData[indexPath.row]
self.viewModel.process(viewAction: .selectSession(deviceId: viewData.deviceId))
}
}
// MARK: - UserVerificationSessionsStatusViewModelViewDelegate
extension UserVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewModelViewDelegate {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState) {
self.render(viewState: viewSate)
}
}
@@ -0,0 +1,148 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum UserVerificationSessionsStatusViewModelError: Error {
case unknown
}
final class UserVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let userId: String
private var currentOperation: MXHTTPOperation?
private var userTrustLevel: UserEncryptionTrustLevel
// MARK: Public
weak var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate?
weak var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, userId: String) {
self.session = session
self.userId = userId
self.userTrustLevel = .unknown
}
deinit {
self.currentOperation?.cancel()
}
// MARK: - Public
func process(viewAction: UserVerificationSessionsStatusViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .selectSession(deviceId: let deviceId):
self.coordinatorDelegate?.userVerificationSessionsStatusViewModel(self, didSelectDeviceWithId: deviceId, for: self.userId)
case .close:
self.coordinatorDelegate?.userVerificationSessionsStatusViewModelDidClose(self)
}
}
// MARK: - Private
private func loadData() {
let sessionsStatusViewData = self.getSessionStatusViewDataListFromCache(for: self.userId)
self.update(viewState: .loaded(userTrustLevel: self.userTrustLevel, sessionsStatusViewData: sessionsStatusViewData))
self.fetchSessionStatus()
}
private func update(viewState: UserVerificationSessionsStatusViewState) {
self.viewDelegate?.userVerificationSessionsStatusViewModel(self, didUpdateViewState: viewState)
}
private func fetchSessionStatus() {
self.update(viewState: .loading)
self.currentOperation = self.getSessionStatusViewDataList(for: self.userId) { result in
switch result {
case .success(let sessionsStatusViewData):
let isUserTrusted = sessionsStatusViewData.contains(where: { sessionsStatusViewData -> Bool in
return sessionsStatusViewData.isTrusted == false
}) == false
let userTrustLevel: UserEncryptionTrustLevel = isUserTrusted ? .trusted : .warning
self.update(viewState: .loaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData))
case .failure(let error):
self.update(viewState: .error(error))
}
}
}
private func getSessionStatusViewDataListFromCache(for userId: String) -> [UserVerificationSessionStatusViewData] {
let deviceInfoList = self.getDevicesFromCache(for: self.userId)
return self.sessionStatusViewDataList(from: deviceInfoList)
}
private func getDevicesFromCache(for userId: String) -> [MXDeviceInfo] {
guard let deviceInfoMap = self.session.crypto.devices(forUser: self.userId) else {
return []
}
return Array(deviceInfoMap.values)
}
@discardableResult
private func getSessionStatusViewDataList(for userId: String, completion: @escaping (Result<[UserVerificationSessionStatusViewData], Error>) -> Void) -> MXHTTPOperation? {
let httpOperation: MXHTTPOperation?
httpOperation = self.session.crypto.downloadKeys([self.userId], forceDownload: false, success: { ( usersDeviceMap: MXUsersDevicesMap<MXDeviceInfo>?, usersCrossSigningMap: [String : MXCrossSigningInfo]?) in
let sessionsViewData: [UserVerificationSessionStatusViewData]
if let usersDeviceMap = usersDeviceMap, let userDeviceInfoMap = Array(usersDeviceMap.map.values).first {
let deviceInfoList = Array(userDeviceInfoMap.values)
sessionsViewData = self.sessionStatusViewDataList(from: deviceInfoList)
} else {
sessionsViewData = []
}
completion(.success(sessionsViewData))
}, failure: { error in
let finalError = error ?? UserVerificationSessionsStatusViewModelError.unknown
completion(.failure(finalError))
})
return httpOperation
}
private func sessionStatusViewData(from deviceInfo: MXDeviceInfo) -> UserVerificationSessionStatusViewData {
return UserVerificationSessionStatusViewData(deviceId: deviceInfo.deviceId, sessionName: deviceInfo.displayName ?? "", isTrusted: deviceInfo.trustLevel.isVerified)
}
private func sessionStatusViewDataList(from deviceInfoList: [MXDeviceInfo]) -> [UserVerificationSessionStatusViewData] {
return deviceInfoList.map { (deviceInfo) -> UserVerificationSessionStatusViewData in
return self.sessionStatusViewData(from: deviceInfo)
}
}
}
@@ -0,0 +1,37 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
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 UserVerificationSessionsStatusViewModelViewDelegate: class {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState)
}
protocol UserVerificationSessionsStatusViewModelCoordinatorDelegate: class {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String)
func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType)
}
/// Protocol describing the view model used by `UserVerificationSessionsStatusViewController`
protocol UserVerificationSessionsStatusViewModelType {
var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate? { get set }
var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate? { get set }
func process(viewAction: UserVerificationSessionsStatusViewAction)
}
@@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
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
/// UserVerificationSessionsStatusViewController view state
enum UserVerificationSessionsStatusViewState {
case loading
case loaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData])
case error(Error)
}