-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
GH-3418 created basic export and import of data
- Loading branch information
Showing
22 changed files
with
1,804 additions
and
12 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
154 changes: 154 additions & 0 deletions
154
Multisig/Features/Data Export/CreateExportPasswordViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// | ||
// CreateExportPasswordViewController.swift | ||
// Multisig | ||
// | ||
// Created by Dmitrii Bespalov on 03.06.24. | ||
// Copyright © 2024 Core Contributors GmbH. All rights reserved. | ||
// | ||
|
||
|
||
import UIKit | ||
|
||
class CreateExportPasswordViewController: UIViewController { | ||
|
||
var prompt: String = "" | ||
var placeholder: String = "" | ||
var plainTextPassword: String? | ||
var completion: (String) -> Void = { _ in } | ||
var validateValue: (String) -> Error? = { _ in nil } | ||
|
||
@IBOutlet weak var textField: GNOTextField! | ||
@IBOutlet weak var descriptionLabel: UILabel! | ||
@IBOutlet weak var continueButton: UIButton! | ||
@IBOutlet weak var scrollView: UIScrollView! | ||
@IBOutlet weak var buttonBottomConstraint: NSLayoutConstraint! | ||
|
||
private var debounceTimer: Timer! | ||
private let debounceDuration: TimeInterval = 0.250 | ||
private var isValid: Bool = false | ||
|
||
private var keyboardBehavior: KeyboardAvoidingBehavior! | ||
|
||
var trackingEvent: TrackingEvent? | ||
var trackingParameters: [String: Any]? = nil | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
|
||
keyboardBehavior = KeyboardAvoidingBehavior(scrollView: scrollView) | ||
|
||
textField.setPlaceholder(placeholder) | ||
textField.textField.isSecureTextEntry = true | ||
textField.textField.delegate = self | ||
textField.textField.clearButtonMode = .always | ||
|
||
if let value = plainTextPassword { | ||
textField.textField.text = value | ||
} | ||
|
||
descriptionLabel.text = prompt | ||
descriptionLabel.setStyle(.body) | ||
|
||
continueButton.setText("Continue", .filled) | ||
|
||
validateText() | ||
|
||
NotificationCenter.default.addObserver(self, | ||
selector: #selector(willShowKeyboard(_:)), | ||
name: UIResponder.keyboardWillShowNotification, | ||
object: nil) | ||
NotificationCenter.default.addObserver(self, | ||
selector: #selector(willHideKeyboard(_:)), | ||
name: UIResponder.keyboardWillHideNotification, | ||
object: nil) | ||
|
||
} | ||
|
||
override func viewWillAppear(_ animated: Bool) { | ||
super.viewWillAppear(animated) | ||
keyboardBehavior.start() | ||
} | ||
|
||
override func viewDidAppear(_ animated: Bool) { | ||
super.viewDidAppear(animated) | ||
if let trackingEvent = trackingEvent { | ||
Tracker.trackEvent(trackingEvent, parameters: trackingParameters) | ||
} | ||
} | ||
|
||
override func viewWillDisappear(_ animated: Bool) { | ||
super.viewWillDisappear(animated) | ||
keyboardBehavior.stop() | ||
} | ||
|
||
@objc func willShowKeyboard(_ notification: NSNotification) { | ||
guard | ||
let frameEnd = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue, | ||
let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber | ||
else { | ||
return | ||
} | ||
|
||
UIView.animate(withDuration: animationDuration.doubleValue) { [weak self] in | ||
self?.buttonBottomConstraint?.constant = 16 + frameEnd.cgRectValue.height | ||
self?.view?.layoutIfNeeded() | ||
} | ||
} | ||
|
||
@objc func willHideKeyboard(_ notification: NSNotification) { | ||
guard | ||
let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber | ||
else { | ||
return | ||
} | ||
UIView.animate(withDuration: animationDuration.doubleValue) { [weak self] in | ||
self?.buttonBottomConstraint?.constant = 16 | ||
self?.view?.layoutIfNeeded() | ||
} | ||
} | ||
|
||
private func validateText() { | ||
isValid = false | ||
continueButton.isEnabled = false | ||
textField.setError(nil) | ||
guard let text = textField.textField.text?.trimmingCharacters(in: .whitespacesAndNewlines), | ||
!text.isEmpty else { | ||
self.plainTextPassword = nil | ||
return | ||
} | ||
if let error = validateValue(text) { | ||
textField.setError(error) | ||
return | ||
} | ||
self.plainTextPassword = text | ||
continueButton.isEnabled = true | ||
isValid = true | ||
} | ||
|
||
@IBAction func didTapContinue(_ sender: Any) { | ||
completion(plainTextPassword!) | ||
} | ||
} | ||
|
||
extension CreateExportPasswordViewController: UITextFieldDelegate { | ||
|
||
func textFieldDidBeginEditing(_ textField: UITextField) { | ||
keyboardBehavior.activeTextField = textField | ||
} | ||
|
||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { | ||
debounceTimer?.invalidate() | ||
debounceTimer = Timer.scheduledTimer(withTimeInterval: debounceDuration, repeats: false, block: { [weak self] _ in | ||
self?.validateText() | ||
}) | ||
return true | ||
} | ||
|
||
func textFieldShouldReturn(_ textField: UITextField) -> Bool { | ||
if isValid { | ||
didTapContinue(textField) | ||
textField.resignFirstResponder() | ||
} | ||
return true | ||
} | ||
} |
117 changes: 117 additions & 0 deletions
117
Multisig/Features/Data Export/CreateExportPasswordViewController.xib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" 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="22685"/> | ||
<capability name="Named colors" minToolsVersion="9.0"/> | ||
<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" customClass="CreateExportPasswordViewController" customModule="Multisig" customModuleProvider="target"> | ||
<connections> | ||
<outlet property="buttonBottomConstraint" destination="BLR-Ri-2c1" id="4Mc-DW-AHm"/> | ||
<outlet property="continueButton" destination="gXy-yX-Ihv" id="syz-ft-J4k"/> | ||
<outlet property="descriptionLabel" destination="qCR-Ls-3Sv" id="jfH-3z-Fne"/> | ||
<outlet property="scrollView" destination="HvJ-08-RJo" id="Tzi-uc-zzt"/> | ||
<outlet property="textField" destination="YAC-g2-UKn" id="96R-Fu-Bt2"/> | ||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/> | ||
</connections> | ||
</placeholder> | ||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> | ||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT"> | ||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> | ||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||
<subviews> | ||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HvJ-08-RJo"> | ||
<rect key="frame" x="0.0" y="48" width="414" height="814"/> | ||
<subviews> | ||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jVT-TE-2xN" userLabel="ContentView"> | ||
<rect key="frame" x="0.0" y="0.0" width="414" height="814"/> | ||
<subviews> | ||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="1Gx-rA-E0l"> | ||
<rect key="frame" x="16" y="16" width="382" height="105"/> | ||
<subviews> | ||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="LsW-zt-ujG"> | ||
<rect key="frame" x="0.0" y="0.0" width="382" height="105"/> | ||
<subviews> | ||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Choose a password for protecting the exported data." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qCR-Ls-3Sv"> | ||
<rect key="frame" x="0.0" y="0.0" width="382" height="41"/> | ||
<fontDescription key="fontDescription" type="system" pointSize="17"/> | ||
<nil key="textColor"/> | ||
<nil key="highlightedColor"/> | ||
</label> | ||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YAC-g2-UKn" customClass="GNOTextField" customModule="Multisig" customModuleProvider="target"> | ||
<rect key="frame" x="0.0" y="49" width="382" height="56"/> | ||
<color key="backgroundColor" systemColor="systemBackgroundColor"/> | ||
<constraints> | ||
<constraint firstAttribute="height" constant="56" placeholder="YES" id="OVz-U6-IPi"/> | ||
</constraints> | ||
</view> | ||
</subviews> | ||
<constraints> | ||
<constraint firstItem="YAC-g2-UKn" firstAttribute="width" secondItem="LsW-zt-ujG" secondAttribute="width" id="Zri-CE-aSn"/> | ||
</constraints> | ||
</stackView> | ||
</subviews> | ||
</stackView> | ||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gXy-yX-Ihv"> | ||
<rect key="frame" x="16" y="742" width="382" height="56"/> | ||
<constraints> | ||
<constraint firstAttribute="height" constant="56" id="2Bc-ES-1VX"/> | ||
</constraints> | ||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/> | ||
<state key="normal" title="Continue"> | ||
<color key="titleColor" name="labelPrimary"/> | ||
</state> | ||
<connections> | ||
<action selector="didTapContinue:" destination="-1" eventType="touchUpInside" id="6Nr-QZ-zb8"/> | ||
</connections> | ||
</button> | ||
</subviews> | ||
<color key="backgroundColor" systemColor="systemBackgroundColor"/> | ||
<constraints> | ||
<constraint firstItem="gXy-yX-Ihv" firstAttribute="top" relation="greaterThanOrEqual" secondItem="1Gx-rA-E0l" secondAttribute="bottom" constant="16" id="0Rn-m7-otO"/> | ||
<constraint firstItem="1Gx-rA-E0l" firstAttribute="top" secondItem="jVT-TE-2xN" secondAttribute="top" constant="16" id="2BR-bH-HVW"/> | ||
<constraint firstAttribute="trailing" secondItem="1Gx-rA-E0l" secondAttribute="trailing" constant="16" id="8p3-ZY-H2z"/> | ||
<constraint firstAttribute="bottom" secondItem="gXy-yX-Ihv" secondAttribute="bottom" constant="16" id="BLR-Ri-2c1"/> | ||
<constraint firstItem="gXy-yX-Ihv" firstAttribute="leading" secondItem="jVT-TE-2xN" secondAttribute="leading" constant="16" id="E93-hD-oTa"/> | ||
<constraint firstItem="1Gx-rA-E0l" firstAttribute="leading" secondItem="jVT-TE-2xN" secondAttribute="leading" constant="16" id="dYE-y4-oh4"/> | ||
<constraint firstAttribute="trailing" secondItem="gXy-yX-Ihv" secondAttribute="trailing" constant="16" id="kzJ-kx-0AE"/> | ||
</constraints> | ||
</view> | ||
</subviews> | ||
<constraints> | ||
<constraint firstItem="jVT-TE-2xN" firstAttribute="top" secondItem="EPz-Ac-Ohw" secondAttribute="top" id="KZx-r4-vvR"/> | ||
<constraint firstItem="jVT-TE-2xN" firstAttribute="trailing" secondItem="EPz-Ac-Ohw" secondAttribute="trailing" id="fMW-SC-z0V"/> | ||
<constraint firstItem="jVT-TE-2xN" firstAttribute="bottom" secondItem="EPz-Ac-Ohw" secondAttribute="bottom" id="oib-vQ-b9X"/> | ||
<constraint firstItem="jVT-TE-2xN" firstAttribute="width" secondItem="JzW-9w-9wW" secondAttribute="width" id="ryj-hi-tFb"/> | ||
<constraint firstItem="jVT-TE-2xN" firstAttribute="leading" secondItem="EPz-Ac-Ohw" secondAttribute="leading" id="vp8-07-NqA"/> | ||
</constraints> | ||
<viewLayoutGuide key="contentLayoutGuide" id="EPz-Ac-Ohw"/> | ||
<viewLayoutGuide key="frameLayoutGuide" id="JzW-9w-9wW"/> | ||
</scrollView> | ||
</subviews> | ||
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/> | ||
<color key="backgroundColor" systemColor="systemBackgroundColor"/> | ||
<constraints> | ||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="jVT-TE-2xN" secondAttribute="bottom" id="7p2-jh-lGh"/> | ||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="HvJ-08-RJo" secondAttribute="bottom" id="Zl6-HL-ncb"/> | ||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="HvJ-08-RJo" secondAttribute="trailing" id="d9t-ZP-Ls8"/> | ||
<constraint firstItem="HvJ-08-RJo" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="hNc-2i-JxC"/> | ||
<constraint firstItem="HvJ-08-RJo" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="mnZ-df-Aqk"/> | ||
</constraints> | ||
<point key="canvasLocation" x="132" y="131"/> | ||
</view> | ||
</objects> | ||
<resources> | ||
<namedColor name="labelPrimary"> | ||
<color red="0.070588235294117646" green="0.074509803921568626" blue="0.070588235294117646" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> | ||
</namedColor> | ||
<systemColor name="systemBackgroundColor"> | ||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> | ||
</systemColor> | ||
</resources> | ||
</document> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// | ||
// ErrorViewController.swift | ||
// Multisig | ||
// | ||
// Created by Dmitrii Bespalov on 06.06.24. | ||
// Copyright © 2024 Core Contributors GmbH. All rights reserved. | ||
// | ||
|
||
import UIKit | ||
|
||
class ErrorViewController: UIViewController { | ||
|
||
@IBOutlet weak var imageView: UIImageView! | ||
@IBOutlet weak var titleLabel: UILabel! | ||
@IBOutlet weak var descriptionLabel: UILabel! | ||
@IBOutlet weak var textView: UITextView! | ||
@IBOutlet weak var button: UIButton! | ||
|
||
var titleText = "Operation failed" | ||
var bodyText = "Error details are below:" | ||
var errorText = "" | ||
var buttonTitle = "Done" | ||
var imageName = "square.and.arrow.up.trianglebadge.exclamationmark" | ||
|
||
var completion: () -> Void = {} | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
imageView.image = UIImage(systemName: imageName) | ||
titleLabel.text = titleText | ||
descriptionLabel.text = bodyText | ||
textView.text = errorText | ||
button.setText(buttonTitle, .filled) | ||
|
||
titleLabel.setStyle(.headline) | ||
descriptionLabel.setStyle(.body) | ||
textView.setStyle(.bodyMedium) | ||
} | ||
|
||
@IBAction func done(_ sender: Any) { | ||
completion() | ||
} | ||
|
||
} |
Oops, something went wrong.