Add accounts empty state

This commit is contained in:
Ivan Grachyov 2021-12-10 21:24:11 +03:00
parent 0b97e2800d
commit d267d2c8dc
7 changed files with 354 additions and 5 deletions

View File

@ -63,5 +63,11 @@ struct Strings {
static let secretWordsGiveFullAccess = "Secret words give full access to your funds."
static let privateKeyGivesFullAccess = "Private key gives full access to your funds."
static let toShowAccountKey = "To show account key"
static let loading = "Loading"
static let failedToLoad = "Failed to load"
static let tryAgain = "Try again"
static let noData = "There is no data yet"
static let refresh = "Refresh"
static let tokenaryIsEmpty = "Tokenary is empty"
}

View File

@ -0,0 +1,19 @@
// Copyright © 2021 Tokenary. All rights reserved.
import UIKit
struct Images {
static var noData: UIImage { systemName("wind") }
static var failedToLoad: UIImage { systemName("xmark.octagon") }
static var preferences: UIImage { systemName("gearshape") }
private static func named(_ name: String) -> UIImage {
return UIImage(named: name)!
}
private static func systemName(_ systemName: String, configuration: UIImage.Configuration? = nil) -> UIImage {
return UIImage(systemName: systemName, withConfiguration: configuration)!
}
}

View File

@ -0,0 +1,19 @@
// Copyright © 2021 Tokenary. All rights reserved.
import UIKit
func loadNib<View: UIView>(_ type: View.Type) -> View {
return Bundle.main.loadNibNamed(String(describing: type), owner: nil, options: nil)![0] as! View
}
extension UIView {
func addSubviewConstrainedToFrame(_ subview: UIView) {
addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
let firstConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": subview])
let secondConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": subview])
addConstraints(firstConstraints + secondConstraints)
}
}

View File

@ -0,0 +1,167 @@
// Copyright © 2021 Tokenary. All rights reserved.
import UIKit
import BlockiesSwift
enum DataState: CaseIterable {
case hasData, loading, failedToLoad, noData, unknown
}
protocol DataStateContainer: AnyObject {
var dataState: DataState { get set }
func configureDataState(_ dataState: DataState, description: String?, image: UIImage?, buttonTitle: String?, actionHandler: (() -> Void)?)
}
class DataStateView: UIView {
private class Configuration {
let description: String?
let image: UIImage?
let buttonTitle: String?
let actionHandler: (() -> Void)?
init(description: String? = nil, image: UIImage? = nil, buttonTitle: String? = nil, actionHandler: (() -> Void)? = nil) {
self.description = description
self.image = image
self.buttonTitle = buttonTitle
self.actionHandler = actionHandler
}
static func defaultForDataState(_ dataState: DataState) -> Configuration {
let configuration: Configuration
switch dataState {
case .hasData, .loading, .unknown:
configuration = Configuration()
case .failedToLoad:
configuration = Configuration(description: Strings.failedToLoad, image: Images.failedToLoad, buttonTitle: Strings.tryAgain)
case .noData:
configuration = Configuration(description: Strings.noData, image: Images.noData, buttonTitle: Strings.refresh)
}
return configuration
}
}
fileprivate static let tag = Int.max
fileprivate static var new: DataStateView {
let view = loadNib(DataStateView.self)
view.tag = tag
view.isHidden = true
view.observeKeyboard()
return view
}
fileprivate var shouldMoveWithKeyboard = true
fileprivate var currentState = DataState.unknown {
didSet { updateForCurrentState() }
}
private var configurations = [DataState: Configuration]()
@IBOutlet private weak var centerYConstraint: NSLayoutConstraint!
@IBOutlet private weak var activityIndicator: UIActivityIndicatorView!
@IBOutlet private weak var imageView: UIImageView!
@IBOutlet private weak var descriptionLabel: UILabel!
@IBOutlet private weak var button: UIButton!
@IBOutlet private weak var activityIndicatorDescriptionLabel: UILabel! {
didSet {
activityIndicatorDescriptionLabel.text = Strings.loading.uppercased()
}
}
@IBAction private func didTapButton(_ sender: Any) {
configurations[currentState]?.actionHandler?()
}
fileprivate func configureDataState(_ dataState: DataState, description: String? = nil, image: UIImage? = nil, buttonTitle: String? = nil, actionHandler: (() -> Void)? = nil) {
let newConfiguration = Configuration(description: description, image: image, buttonTitle: buttonTitle, actionHandler: actionHandler)
configurations[dataState] = newConfiguration
}
private func updateForCurrentState() {
isHidden = currentState == .unknown || currentState == .hasData
let configuration = configurations[currentState]
let defaultConfiguration = Configuration.defaultForDataState(currentState)
imageView.image = configuration?.image ?? defaultConfiguration.image
descriptionLabel.text = configuration?.description ?? defaultConfiguration.description
button.setTitle(configuration?.buttonTitle ?? defaultConfiguration.buttonTitle, for: .normal)
let isLoading = currentState == .loading
activityIndicator.isHidden = !isLoading
activityIndicatorDescriptionLabel.isHidden = !isLoading
imageView.isHidden = isLoading
descriptionLabel.isHidden = isLoading
button.isHidden = isLoading || configuration?.actionHandler == nil
if isLoading {
activityIndicator.startAnimating()
} else if activityIndicator.isAnimating {
activityIndicator.stopAnimating()
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return button.frame.insetBy(dx: -30, dy: -30).contains(point)
}
}
extension DataStateView: KeyboardObserver {
func keyboardWill(show: Bool, height: CGFloat, animtaionOptions: UIView.AnimationOptions, duration: Double) {
guard shouldMoveWithKeyboard else { return }
let centerOffset: CGFloat = show ? -105 : -50
UIView.animate(withDuration: duration,
delay: 0,
options: animtaionOptions,
animations: { [weak self] in
self?.centerYConstraint.constant = centerOffset
self?.layoutIfNeeded()
},
completion: nil
)
}
}
extension DataStateContainer where Self: UIViewController {
var dataState: DataState {
get {
return dataStateView.currentState
}
set {
dataStateView.currentState = newValue
}
}
func dataStateShouldMoveWithKeyboard(_ shouldMove: Bool) {
dataStateView.shouldMoveWithKeyboard = shouldMove
}
func setDataStateViewTransparent(_ isTransparent: Bool) {
dataStateView.backgroundColor = isTransparent ? .clear : .systemGroupedBackground
}
func configureDataState(_ dataState: DataState, description: String? = nil, image: UIImage? = nil, buttonTitle: String? = nil, actionHandler: (() -> Void)? = nil) {
dataStateView.configureDataState(dataState, description: description, image: image, buttonTitle: buttonTitle, actionHandler: actionHandler)
}
private var dataStateView: DataStateView {
if let subview = view.viewWithTag(DataStateView.tag) as? DataStateView { return subview }
let dataStateView = DataStateView.new
view.addSubview(dataStateView)
dataStateView.translatesAutoresizingMaskIntoConstraints = false
let firstConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": dataStateView])
let secondConstraint = NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": dataStateView])
view.addConstraints(firstConstraint + secondConstraint)
view.bringSubviewToFront(dataStateView)
return dataStateView
}
}

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.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"/>
<view contentMode="scaleToFill" id="lqS-Qo-2hd" customClass="DataStateView" customModule="Tokenary_iOS" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="0rZ-Sk-fve">
<rect key="frame" x="197" y="388" width="20" height="20"/>
</activityIndicatorView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LOADING" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ImM-Ma-L3J">
<rect key="frame" x="180.5" y="416" width="53.5" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="wind" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="OeR-Db-lcJ">
<rect key="frame" x="118.5" y="271" width="177" height="174.5"/>
<color key="tintColor" systemColor="tertiaryLabelColor"/>
<constraints>
<constraint firstAttribute="width" secondItem="OeR-Db-lcJ" secondAttribute="height" multiplier="1:1" id="g5A-B4-JDZ"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="200" id="qey-Ch-daS"/>
</constraints>
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="default" weight="thin"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Failed to load" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Iym-80-uYR">
<rect key="frame" x="20" y="454.5" width="374" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5ZO-df-Isv">
<rect key="frame" x="137" y="527" width="140" height="52"/>
<constraints>
<constraint firstAttribute="height" constant="52" id="5fo-UF-Qwu"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="140" id="JBF-DF-ChM"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="15"/>
<state key="normal" title="Retry"/>
<buttonConfiguration key="configuration" style="filled" title="Retry">
<fontDescription key="titleFontDescription" type="system" weight="semibold" pointSize="15"/>
</buttonConfiguration>
<connections>
<action selector="didTapButton:" destination="lqS-Qo-2hd" eventType="touchUpInside" id="wYk-pp-YyB"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="lUb-JT-l59"/>
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
<constraints>
<constraint firstItem="OeR-Db-lcJ" firstAttribute="top" relation="greaterThanOrEqual" secondItem="lqS-Qo-2hd" secondAttribute="top" constant="150" id="0ao-To-70w"/>
<constraint firstItem="OeR-Db-lcJ" firstAttribute="centerX" secondItem="lqS-Qo-2hd" secondAttribute="centerX" id="11T-RK-uzC"/>
<constraint firstItem="Iym-80-uYR" firstAttribute="top" secondItem="OeR-Db-lcJ" secondAttribute="bottom" constant="8" id="3dj-vb-QgB"/>
<constraint firstItem="0rZ-Sk-fve" firstAttribute="centerX" secondItem="lqS-Qo-2hd" secondAttribute="centerX" id="3yi-dM-5bk"/>
<constraint firstItem="0rZ-Sk-fve" firstAttribute="top" secondItem="OeR-Db-lcJ" secondAttribute="bottom" constant="-58.5" id="8Pu-Nt-RW8"/>
<constraint firstItem="ImM-Ma-L3J" firstAttribute="centerX" secondItem="0rZ-Sk-fve" secondAttribute="centerX" id="ENE-tI-BjB"/>
<constraint firstItem="Iym-80-uYR" firstAttribute="width" secondItem="lqS-Qo-2hd" secondAttribute="width" constant="-40" id="KtH-zG-zmO"/>
<constraint firstItem="ImM-Ma-L3J" firstAttribute="top" secondItem="0rZ-Sk-fve" secondAttribute="bottom" constant="8" id="OTT-Nq-oAt"/>
<constraint firstItem="5ZO-df-Isv" firstAttribute="centerX" secondItem="lqS-Qo-2hd" secondAttribute="centerX" id="X1o-2s-b8d"/>
<constraint firstItem="OeR-Db-lcJ" firstAttribute="width" secondItem="lqS-Qo-2hd" secondAttribute="width" multiplier="3:7" priority="750" id="YTt-wi-2MM"/>
<constraint firstItem="Iym-80-uYR" firstAttribute="centerX" secondItem="lqS-Qo-2hd" secondAttribute="centerX" id="e6D-zs-nVx"/>
<constraint firstItem="5ZO-df-Isv" firstAttribute="top" secondItem="Iym-80-uYR" secondAttribute="bottom" constant="52" id="i2r-xi-nPw"/>
<constraint firstItem="0rZ-Sk-fve" firstAttribute="centerY" secondItem="lqS-Qo-2hd" secondAttribute="centerY" constant="-50" id="yXH-dL-f1g"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="activityIndicator" destination="0rZ-Sk-fve" id="vxk-57-49J"/>
<outlet property="activityIndicatorDescriptionLabel" destination="ImM-Ma-L3J" id="JXP-oJ-MHf"/>
<outlet property="button" destination="5ZO-df-Isv" id="ktY-5G-IeA"/>
<outlet property="centerYConstraint" destination="yXH-dL-f1g" id="CPn-cG-i1P"/>
<outlet property="descriptionLabel" destination="Iym-80-uYR" id="pef-lb-qeN"/>
<outlet property="imageView" destination="OeR-Db-lcJ" id="hu0-Fa-45B"/>
</connections>
<point key="canvasLocation" x="139" y="-492"/>
</view>
</objects>
<resources>
<image name="wind" catalog="system" width="128" height="103"/>
<systemColor name="secondaryLabelColor">
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGroupedBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="tertiaryLabelColor">
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View File

@ -2,7 +2,7 @@
import UIKit
class AccountsListViewController: UIViewController {
class AccountsListViewController: UIViewController, DataStateContainer {
private let walletsManager = WalletsManager.shared
private let keychain = Keychain.shared
@ -25,8 +25,27 @@ class AccountsListViewController: UIViewController {
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addAccount))
let preferencesItem = UIBarButtonItem(image: UIImage(systemName: "gearshape"), style: UIBarButtonItem.Style.plain, target: self, action: #selector(preferencesButtonTapped))
let preferencesItem = UIBarButtonItem(image: Images.preferences, style: UIBarButtonItem.Style.plain, target: self, action: #selector(preferencesButtonTapped))
navigationItem.rightBarButtonItems = [addItem, preferencesItem]
configureDataState(.noData, description: Strings.tokenaryIsEmpty, buttonTitle: Strings.addAccount) { [weak self] in
self?.addAccount()
}
dataStateShouldMoveWithKeyboard(false)
updateDataState()
}
private func updateDataState() {
let isEmpty = wallets.isEmpty
dataState = isEmpty ? .noData : .hasData
let canScroll = !isEmpty
if tableView.isScrollEnabled != canScroll {
tableView.isScrollEnabled = canScroll
}
}
private func reloadData() {
updateDataState()
tableView.reloadData()
}
@objc private func preferencesButtonTapped() {
@ -76,7 +95,7 @@ class AccountsListViewController: UIViewController {
private func createNewAccountAndShowSecretWords() {
guard let wallet = try? walletsManager.createWallet() else { return }
tableView.reloadData()
reloadData()
showKey(wallet: wallet, mnemonic: true)
}
@ -104,7 +123,7 @@ class AccountsListViewController: UIViewController {
let importAccountViewController = instantiate(ImportViewController.self, from: .main)
importAccountViewController.completion = { [weak self] success in
if success {
self?.tableView.reloadData()
self?.reloadData()
}
}
present(importAccountViewController.inNavigationController, animated: true)
@ -169,7 +188,7 @@ class AccountsListViewController: UIViewController {
private func removeWallet(_ wallet: TokenaryWallet) {
try? walletsManager.delete(wallet: wallet)
tableView.reloadData()
reloadData()
}
private func didTapExportAccount(_ wallet: TokenaryWallet) {

View File

@ -69,6 +69,11 @@
2C96D39827623EC600687301 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96D39727623EC600687301 /* URL.swift */; };
2C96D39927623ECE00687301 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96D39727623EC600687301 /* URL.swift */; };
2C96D39C2763ADE100687301 /* LocalAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96D39B2763ADE100687301 /* LocalAuthentication.swift */; };
2C96D3A22763C65B00687301 /* KeyboardObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96D3A12763C65B00687301 /* KeyboardObserver.swift */; };
2C96D3A42763C6A800687301 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96D3A32763C6A800687301 /* UIView.swift */; };
2C96D3A62763CCA000687301 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96D3A52763CCA000687301 /* Images.swift */; };
2C96D3A92763D13400687301 /* DataStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96D3A72763D13400687301 /* DataStateView.swift */; };
2C96D3AA2763D13400687301 /* DataStateView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C96D3A82763D13400687301 /* DataStateView.xib */; };
2C9F0B6526BDC9AF008FA3D6 /* EthereumNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9F0B6426BDC9AF008FA3D6 /* EthereumNetwork.swift */; };
2C9F0B6826BDCB2E008FA3D6 /* EthereumChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9F0B6726BDCB2E008FA3D6 /* EthereumChain.swift */; };
2CAA412526C7CD93009F3535 /* ReviewRequester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAA412426C7CD93009F3535 /* ReviewRequester.swift */; };
@ -246,6 +251,11 @@
2C96D3952762380400687301 /* ButtonWithExtendedArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonWithExtendedArea.swift; sourceTree = "<group>"; };
2C96D39727623EC600687301 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
2C96D39B2763ADE100687301 /* LocalAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthentication.swift; sourceTree = "<group>"; };
2C96D3A12763C65B00687301 /* KeyboardObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KeyboardObserver.swift; path = "../../../Wildberries/wbx-ios/WBX-iOS/Library/Tools/KeyboardObserver.swift"; sourceTree = "<group>"; };
2C96D3A32763C6A800687301 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
2C96D3A52763CCA000687301 /* Images.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = "<group>"; };
2C96D3A72763D13400687301 /* DataStateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStateView.swift; sourceTree = "<group>"; };
2C96D3A82763D13400687301 /* DataStateView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DataStateView.xib; sourceTree = "<group>"; };
2C9F0B6426BDC9AF008FA3D6 /* EthereumNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumNetwork.swift; sourceTree = "<group>"; };
2C9F0B6726BDCB2E008FA3D6 /* EthereumChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumChain.swift; sourceTree = "<group>"; };
2CAA412426C7CD93009F3535 /* ReviewRequester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewRequester.swift; sourceTree = "<group>"; };
@ -501,6 +511,7 @@
2C8E88A0275F99D4003EB8DB /* Content */ = {
isa = PBXGroup;
children = (
2C96D3A52763CCA000687301 /* Images.swift */,
2C8E889E275F9967003EB8DB /* Storyboard.swift */,
);
path = Content;
@ -519,6 +530,9 @@
2C96D394276237F600687301 /* Library */ = {
isa = PBXGroup;
children = (
2C96D3A72763D13400687301 /* DataStateView.swift */,
2C96D3A82763D13400687301 /* DataStateView.xib */,
2C96D3A12763C65B00687301 /* KeyboardObserver.swift */,
2C96D3952762380400687301 /* ButtonWithExtendedArea.swift */,
2C96D39B2763ADE100687301 /* LocalAuthentication.swift */,
);
@ -530,6 +544,7 @@
children = (
2CC6EF0C275E64810040CC62 /* UIViewController.swift */,
2C8E88A3275FB7B9003EB8DB /* UIApplication.swift */,
2C96D3A32763C6A800687301 /* UIView.swift */,
2C96D391276232A300687301 /* UITableView.swift */,
);
path = Extensions;
@ -856,6 +871,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2C96D3AA2763D13400687301 /* DataStateView.xib in Resources */,
2C5FF97E26C84F7C00B32ACC /* LaunchScreen.storyboard in Resources */,
2C96D3902762317300687301 /* AccountTableViewCell.xib in Resources */,
2C5FF97B26C84F7C00B32ACC /* Assets.xcassets in Resources */,
@ -1080,7 +1096,9 @@
2CF255A6275A48BB00AE54B9 /* GasService.swift in Sources */,
2C96D392276232A300687301 /* UITableView.swift in Sources */,
2CF255A5275A48BB00AE54B9 /* ReviewRequester.swift in Sources */,
2C96D3A22763C65B00687301 /* KeyboardObserver.swift in Sources */,
2CF255B6275A746000AE54B9 /* AccountsListViewController.swift in Sources */,
2C96D3A42763C6A800687301 /* UIView.swift in Sources */,
2CF25597275A46D300AE54B9 /* Defaults.swift in Sources */,
2CF255A2275A47DD00AE54B9 /* String.swift in Sources */,
2CF2559D275A479800AE54B9 /* TokenaryWallet.swift in Sources */,
@ -1097,12 +1115,14 @@
2CF255B4275A744000AE54B9 /* PasswordViewController.swift in Sources */,
2C8E889F275F9967003EB8DB /* Storyboard.swift in Sources */,
2C96D39C2763ADE100687301 /* LocalAuthentication.swift in Sources */,
2C96D3A62763CCA000687301 /* Images.swift in Sources */,
2CF255AA275A48BB00AE54B9 /* SessionStorage.swift in Sources */,
2C96D39827623EC600687301 /* URL.swift in Sources */,
2CF255AD275A48CF00AE54B9 /* EthereumChain.swift in Sources */,
2CF2559C275A477F00AE54B9 /* ApprovalSubject.swift in Sources */,
2CF255B1275A4A1800AE54B9 /* ResponseToExtension.swift in Sources */,
2CF2559B275A46E700AE54B9 /* AuthenticationReason.swift in Sources */,
2C96D3A92763D13400687301 /* DataStateView.swift in Sources */,
2CF255A3275A47DD00AE54B9 /* UserDefaults.swift in Sources */,
2CF255B8275A748300AE54B9 /* ApproveTransactionViewController.swift in Sources */,
2C8E88A4275FB7B9003EB8DB /* UIApplication.swift in Sources */,