Merge branch 'master' into fix-date-time-picker-buttons-fonts-too-big-for-chinese

pull/321/head
James Sangalli 7 years ago committed by GitHub
commit aa7edc2db6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      Trust.xcodeproj/project.pbxproj
  2. 3
      Trust/Accounts/Coordinators/AccountsCoordinator.swift
  3. 21
      Trust/Assets.xcassets/create_wallet.imageset/Contents.json
  4. BIN
      Trust/Assets.xcassets/create_wallet.imageset/create_wallet@3x.png
  5. 21
      Trust/Assets.xcassets/create_wallet_import.imageset/Contents.json
  6. BIN
      Trust/Assets.xcassets/create_wallet_import.imageset/import (1).png
  7. 2
      Trust/Extensions/Date.swift
  8. 3
      Trust/Extensions/UIAlertController.swift
  9. 1
      Trust/Localization/en.lproj/Localizable.strings
  10. 1
      Trust/Localization/es.lproj/Localizable.strings
  11. 1
      Trust/Localization/zh-Hans.lproj/Localizable.strings
  12. 4
      Trust/Sell/ViewControllers/GenerateSellMagicLinkViewController.swift
  13. 3
      Trust/Settings/Models/LiveLocaleSwitcherBundle.swift
  14. 124
      Trust/UI/Views/TextView.swift
  15. 10
      Trust/Wallet/Types/ImportWalletTab.swift
  16. 476
      Trust/Wallet/ViewControllers/ImportWalletViewController.swift
  17. 36
      Trust/Wallet/ViewModels/ImportWalletTabBarViewModel.swift
  18. 34
      Trust/Wallet/ViewModels/ImportWalletViewModel.swift
  19. 138
      Trust/Wallet/Views/ImportWalletTabBar.swift
  20. 14
      TrustTests/Settings/ConfigTests.swift

@ -344,11 +344,13 @@
5E7C7A4384A8E3F22D3F8249 /* SetSellTicketsExpiryDateViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C700CD3E43689E88FBE9B /* SetSellTicketsExpiryDateViewControllerViewModel.swift */; };
5E7C7AB2ECFB589632F2A26C /* WalletFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E2DCCE0D775ECF83088 /* WalletFilter.swift */; };
5E7C7AB6950E43BD6E8D0CBE /* TokensViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B3302309706CA0F972A /* TokensViewController.swift */; };
5E7C7AE1389D3179239249F0 /* ImportWalletTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C743172FCBDCD362C03A6 /* ImportWalletTabBar.swift */; };
5E7C7B0367CFB413C6885474 /* GenerateSellMagicLinkViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7624D6F7EA55F6F167B3 /* GenerateSellMagicLinkViewControllerViewModel.swift */; };
5E7C7B3E08EEA63C5B68B9C4 /* TicketRedemptionInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C778F20D32B70D7FF2135 /* TicketRedemptionInfoViewController.swift */; };
5E7C7C0FAC500A6651E663FD /* TransferTicketsQuantitySelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */; };
5E7C7C21E5CAF122AA4F6617 /* HowDoIGetMyMoneyInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */; };
5E7C7C658D619C70F1E3DE59 /* AdvancedSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C719B717E002583B1E2E9 /* AdvancedSettingsViewModel.swift */; };
5E7C7C7142C4519873B2BB4E /* ImportWalletTabBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */; };
5E7C7C98EAF40E8110241DBD /* TicketTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */; };
5E7C7C9E89056069C8FEFA76 /* AlphaWalletSettingsSwitchRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7534FB6BF4D199643246 /* AlphaWalletSettingsSwitchRow.swift */; };
5E7C7CCA357CB7BF12E1F2B4 /* UIStackView+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73ED9226646D562B5A3C /* UIStackView+Array.swift */; };
@ -367,11 +369,13 @@
5E7C7E04D4DDD7D8881A2AB1 /* UniversalLinkCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76AF81B8DFF605558499 /* UniversalLinkCoordinator.swift */; };
5E7C7E1B18EC7F7FD6D64439 /* SellTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FF84A4377FC395772C3 /* SellTicketsViewController.swift */; };
5E7C7E2F558A1DFF078B61F9 /* TransferTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7011D8E5C9FFE0E59D55 /* TransferTicketsViewController.swift */; };
5E7C7E47C3C412A52DED7380 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AC5A210D034DBC75FB0 /* TextView.swift */; };
5E7C7E4B4054AAD41C5BE3EC /* SettingsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7564AF453BAB0BDAAA57 /* SettingsAction.swift */; };
5E7C7E5C30EFDC70DF1E00C1 /* TicketsViewControllerHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77316522DF2B256F1F92 /* TicketsViewControllerHeaderViewModel.swift */; };
5E7C7E68425E20834B898D06 /* AppLocale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B29A9E728402D144C05 /* AppLocale.swift */; };
5E7C7EAEBB435F3909DA36FB /* TransferTicketsViaWalletAddressViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76D3CFA12C2236E73E10 /* TransferTicketsViaWalletAddressViewControllerViewModel.swift */; };
5E7C7EAED92E4AE8B99217AB /* TransferTicketsQuantitySelectionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7021EE19C4B81CAAF3C0 /* TransferTicketsQuantitySelectionViewControllerTests.swift */; };
5E7C7EDA1BB781A45C1C19CD /* ImportWalletTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73D26F24C4AAE981E2F2 /* ImportWalletTab.swift */; };
5E7C7EEE563D81793CB96FA0 /* TransferTicketsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C755132D9B6F95080A1BE /* TransferTicketsCoordinator.swift */; };
5E7C7FAF2A07E7AE21BF09AF /* AlphaWalletSettingsTextRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71684B93F60206992E10 /* AlphaWalletSettingsTextRow.swift */; };
5E7C7FC0770A411DB09F8C09 /* TokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C077372C3F2A4349FA1 /* TokenViewCell.swift */; };
@ -806,10 +810,12 @@
5E7C731B6F01534683227123 /* TicketTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C73495E0C0A207152EC25 /* LockEnterPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LockEnterPasscodeViewController.swift; path = Trust/AlphaWalletLock/ViewControllers/LockEnterPasscodeViewController.swift; sourceTree = SOURCE_ROOT; };
5E7C73617E3A4C0B9A90A5F8 /* AmountTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmountTextField.swift; sourceTree = "<group>"; };
5E7C73D26F24C4AAE981E2F2 /* ImportWalletTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTab.swift; sourceTree = "<group>"; };
5E7C73DF5FBFE756097D32B1 /* EthCurrencyHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthCurrencyHelper.swift; sourceTree = "<group>"; };
5E7C73ED9226646D562B5A3C /* UIStackView+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStackView+Array.swift"; sourceTree = "<group>"; };
5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeViewController.swift; sourceTree = "<group>"; };
5E7C7419F47CC8B2996AA8F9 /* TransferTicketsQuantitySelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsQuantitySelectionViewController.swift; sourceTree = "<group>"; };
5E7C743172FCBDCD362C03A6 /* ImportWalletTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTabBar.swift; sourceTree = "<group>"; };
5E7C74A2C738BF2412D412A7 /* TicketSellInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketSellInfoViewController.swift; sourceTree = "<group>"; };
5E7C74B82783A94091A43470 /* EthTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthTokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensDataStore.swift; sourceTree = "<group>"; };
@ -857,6 +863,7 @@
5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleViewViewModel.swift; sourceTree = "<group>"; };
5E7C7A65F6033318F7C8AEB0 /* DateEntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateEntryField.swift; sourceTree = "<group>"; };
5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeCoordinator.swift; sourceTree = "<group>"; };
5E7C7AC5A210D034DBC75FB0 /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextView.swift; path = Views/TextView.swift; sourceTree = "<group>"; };
5E7C7ACB94CEE493AC37487F /* TicketRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRowView.swift; sourceTree = "<group>"; };
5E7C7ADD0FBE8708A6E98AF8 /* PromptBackupCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromptBackupCoordinator.swift; sourceTree = "<group>"; };
5E7C7AE6FAE0DF969B4F52E9 /* ContactUsBannerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactUsBannerView.swift; sourceTree = "<group>"; };
@ -872,6 +879,7 @@
5E7C7BA578BE5FB0E613A6D6 /* ChooseTicketTransferModeViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTicketTransferModeViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7C077372C3F2A4349FA1 /* TokenViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenViewCell.swift; sourceTree = "<group>"; };
5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTabBarViewModel.swift; sourceTree = "<group>"; };
5E7C7C58586099F082973073 /* WalletFilterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFilterView.swift; sourceTree = "<group>"; };
5E7C7CD7ABB18C1121D5776F /* LiveLocaleSwitcherBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLocaleSwitcherBundle.swift; sourceTree = "<group>"; };
5E7C7CFDE7DEA8C06C4100AF /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
@ -1302,6 +1310,7 @@
442FC575B6A4A50B0555E1B0 /* NumberStepper.swift */,
5E7C75918317E13AD540DCA7 /* RoundedBackground.swift */,
5E7C75B5AF76279A71395FC7 /* AddressTextField.swift */,
5E7C7AC5A210D034DBC75FB0 /* TextView.swift */,
);
path = UI;
sourceTree = "<group>";
@ -1406,6 +1415,7 @@
children = (
291A1B641F974E8600ADEC80 /* WalletEntryPoint.swift */,
29F114EF1FA6D53700114A29 /* ImportSelectionType.swift */,
5E7C73D26F24C4AAE981E2F2 /* ImportWalletTab.swift */,
);
path = Types;
sourceTree = "<group>";
@ -1619,6 +1629,7 @@
2996F1421F6C96FF005C33AE /* ImportWalletViewModel.swift */,
771AA961200D5EC700D25403 /* PassphraseViewModel.swift */,
77872D26202505C00032D687 /* EnterPasswordViewModel.swift */,
5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -2618,6 +2629,7 @@
771AA95F200D5E8800D25403 /* PassphraseView.swift */,
771AA963200D5EDB00D25403 /* WordCollectionViewCell.swift */,
771AA965200D5F1900D25403 /* WordCollectionViewCell.xib */,
5E7C743172FCBDCD362C03A6 /* ImportWalletTabBar.swift */,
);
path = Views;
sourceTree = "<group>";
@ -3612,6 +3624,10 @@
5E7C7488D5CAE24B7462815A /* LiveLocaleSwitcherBundle.swift in Sources */,
5E7C79D78AA5E774119BE49B /* TextField.swift in Sources */,
5E7C7788984F7ADCFE5B4DE0 /* AddressTextField.swift in Sources */,
5E7C7E47C3C412A52DED7380 /* TextView.swift in Sources */,
5E7C7AE1389D3179239249F0 /* ImportWalletTabBar.swift in Sources */,
5E7C7EDA1BB781A45C1C19CD /* ImportWalletTab.swift in Sources */,
5E7C7C7142C4519873B2BB4E /* ImportWalletTabBarViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -57,7 +57,8 @@ class AccountsCoordinator: Coordinator {
message: nil,
alertButtonTitles: [R.string.localizable.walletCreateButtonTitle(), R.string.localizable.walletImportButtonTitle(), R.string.localizable.cancel()],
alertButtonStyles: [.default, .default, .cancel],
viewController: navigationController) { index in
viewController: navigationController,
preferredStyle: .actionSheet) { index in
if index == 0 {
self.showCreateWallet()
} else if index == 1 {

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "create_wallet@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "import (1).png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

@ -44,6 +44,6 @@ public extension Date {
}
public func formatAsShortDateString() -> String {
return format("dd MMM")
return format("dd MMM yyyy")
}
}

@ -36,12 +36,13 @@ extension UIAlertController {
alertButtonTitles: [String],
alertButtonStyles: [UIAlertActionStyle],
viewController: UIViewController,
preferredStyle: UIAlertControllerStyle = .alert,
completion: ((Int) -> Void)?) {
let alertController = UIAlertController(
title: title,
message: message,
preferredStyle: UIAlertControllerStyle.alert)
preferredStyle: preferredStyle)
alertButtonTitles.forEach { title in
let alertStyle: UIAlertActionStyle = alertButtonStyles[alertButtonTitles.index(of: title)!]

@ -66,7 +66,6 @@
"wallet.create.button.title" = "Create Wallet";
"wallet.create.inProgress" = "Creating wallet...";
"wallet.import.button.title" = "Import Wallet";
"wallet.import.subtitle" = "Importing wallet as easy as creating";
"wallets.backup.alertSheet.title" = "Backup Keystore";
"transactions.tabbar.item.title" = "My Transactions";
"transaction.navigation.title" = "Transaction";

@ -73,7 +73,6 @@
"wallet.create.button.title" = "Create Wallet";
"wallet.create.inProgress" = "Creating wallet...";
"wallet.import.button.title" = "Import Wallet";
"wallet.import.subtitle" = "Importing wallet as easy as creating";
"wallets.backup.alertSheet.title" = "Hacer copia de seguridad del almacén de claves";
"transactions.tabbar.item.title" = "Actas";
"import.navigation.title" = "Importando cartera";

@ -112,7 +112,6 @@
"wallet.import.button.title" = "导入钱包";
"browser.home.button.title" = "主页";
"browser.reload.button.title" = "刷新";
"wallet.import.subtitle" = "导入钱包与创建一样简单";
"configureTransaction.gasPriceGwei.label.title" = "Gas费用 (Gwei)";
"enterPassword.confirmPassword.textField.placeholder" = "确认密码";
"enterPassword.navigation.title" = "备份密码";

@ -89,8 +89,8 @@ class GenerateSellMagicLinkViewController: UIViewController {
actionButton.heightAnchor.constraint(equalToConstant: 47),
cancelButton.heightAnchor.constraint(equalTo: actionButton.heightAnchor),
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 40),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -40),
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 30),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -30),
stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: 16),
stackView.bottomAnchor.constraint(equalTo: background.bottomAnchor, constant: -16),

@ -15,7 +15,8 @@ class LiveLocaleSwitcherBundle: Bundle {
}
override func url(forResource name: String?, withExtension ext: String?) -> URL? {
if let languageBundle = objc_getAssociatedObject(self, &liveLocaleSwitcherBundleKey) as? Bundle {
//We want to match "html", but exclude "nib" (for "xib"). Safe to whitelist instead of blacklist
if ext == "html", let languageBundle = objc_getAssociatedObject(self, &liveLocaleSwitcherBundleKey) as? Bundle {
return languageBundle.url(forResource: name, withExtension: ext)
} else {
return super.url(forResource: name, withExtension: ext)

@ -0,0 +1,124 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
protocol TextViewDelegate: class {
func shouldReturn(in textView: TextView) -> Bool
func doneButtonTapped(for textView: TextView)
func nextButtonTapped(for textView: TextView)
}
class TextView: UIControl {
enum InputAccessoryButtonType {
case done
case next
case none
}
let label = UILabel()
let textView = UITextView()
var value: String {
get {
return textView.text ?? ""
}
set {
textView.text = newValue
}
}
var inputAccessoryButtonType = InputAccessoryButtonType.none {
didSet {
switch inputAccessoryButtonType {
case .done:
textView.inputAccessoryView = makeToolbarWithDoneButton()
case .next:
textView.inputAccessoryView = makeToolbarWithNextButton()
case .none:
textView.inputAccessoryView = nil
}
}
}
private var isConfigured = false
weak var delegate: TextViewDelegate?
init() {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
textView.translatesAutoresizingMaskIntoConstraints = false
textView.delegate = self
textView.textContainerInset = .init(top: 10, left: 12, bottom: 10, right: 12)
addSubview(textView)
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: leadingAnchor),
textView.trailingAnchor.constraint(equalTo: trailingAnchor),
textView.topAnchor.constraint(equalTo: topAnchor),
textView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
func configureOnce() {
guard !isConfigured else { return }
isConfigured = true
label.font = Fonts.regular(size: 10)!
label.textColor = Colors.appGrayLabelColor
textView.textColor = Colors.appBackground
textView.font = Fonts.bold(size: 21)
textView.layer.borderColor = Colors.appBackground.cgColor
textView.layer.borderWidth = 1
textView.layer.cornerRadius = 20
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func makeToolbarWithDoneButton() -> UIToolbar {
//Frame needed, but actual values aren't that important
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
toolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped))
toolbar.items = [flexSpace, done]
toolbar.sizeToFit()
return toolbar
}
private func makeToolbarWithNextButton() -> UIToolbar {
//Frame needed, but actual values aren't that important
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
toolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let next = UIBarButtonItem(title: R.string.localizable.next(), style: .plain, target: self, action: #selector(nextButtonTapped))
toolbar.items = [flexSpace, next]
toolbar.sizeToFit()
return toolbar
}
@objc func doneButtonTapped() {
delegate?.doneButtonTapped(for: self)
}
@objc func nextButtonTapped() {
delegate?.nextButtonTapped(for: self)
}
}
extension TextView: UITextViewDelegate {
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
guard let delegate = delegate else { return true }
return delegate.shouldReturn(in: self)
} else {
return true
}
}
}

@ -0,0 +1,10 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
enum ImportWalletTab {
case keystore
case privateKey
case watch
}

@ -1,7 +1,5 @@
// Copyright SIX DAY LLC. All rights reserved.
import UIKit
import Eureka
import BonMot
import TrustKeystore
import QRCodeReaderViewController
@ -10,57 +8,160 @@ protocol ImportWalletViewControllerDelegate: class {
func didImportAccount(account: Wallet, in viewController: ImportWalletViewController)
}
class ImportWalletViewController: FormViewController {
class ImportWalletViewController: UIViewController {
struct ValidationError: LocalizedError {
var msg: String
var errorDescription: String? {
return msg
}
}
let keystore: Keystore
private let viewModel = ImportWalletViewModel()
struct Values {
static let segment = "segment"
static let keystore = "keystore"
static let privateKey = "privateKey"
static let password = "password"
static let watch = "watch"
static let mnemonic = "mnemonic"
}
var segmentRow: SegmentedRow<String>? {
return form.rowBy(tag: Values.segment)
}
var keystoreRow: TextAreaRow? {
return form.rowBy(tag: Values.keystore)
}
var mnemonicRow: TextAreaRow? {
return form.rowBy(tag: Values.mnemonic)
}
var privateKeyRow: TextAreaRow? {
return form.rowBy(tag: Values.privateKey)
}
//We don't actually use the rounded corner here, but it's a useful "content" view here
let roundedBackground = RoundedBackground()
let scrollView = UIScrollView()
let footerBar = UIView()
let tabBar = ImportWalletTabBar()
let keystoreJSONTextView = TextView()
let passwordTextField = TextField()
let privateKeyTextView = TextView()
let watchAddressTextField = AddressTextField()
var passwordRow: TextFloatLabelRow? {
return form.rowBy(tag: Values.password)
}
var keystoreJSONControlsStackView: UIStackView!
var privateKeyControlsStackView: UIStackView!
var watchControlsStackView: UIStackView!
var watchRow: TextFloatLabelRow? {
return form.rowBy(tag: Values.watch)
}
let importButton = UIButton(type: .system)
weak var delegate: ImportWalletViewControllerDelegate?
init(
keystore: Keystore
) {
init(keystore: Keystore) {
self.keystore = keystore
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
super.init(nibName: nil, bundle: nil)
title = viewModel.title
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(roundedBackground)
scrollView.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.addSubview(scrollView)
tabBar.delegate = self
tabBar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tabBar)
keystoreJSONTextView.label.translatesAutoresizingMaskIntoConstraints = false
keystoreJSONTextView.delegate = self
keystoreJSONTextView.translatesAutoresizingMaskIntoConstraints = false
keystoreJSONTextView.textView.returnKeyType = .next
passwordTextField.label.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.delegate = self
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.textField.returnKeyType = .done
passwordTextField.textField.isSecureTextEntry = true
privateKeyTextView.label.translatesAutoresizingMaskIntoConstraints = false
privateKeyTextView.delegate = self
privateKeyTextView.translatesAutoresizingMaskIntoConstraints = false
privateKeyTextView.textView.returnKeyType = .done
watchAddressTextField.translatesAutoresizingMaskIntoConstraints = false
watchAddressTextField.delegate = self
watchAddressTextField.textField.returnKeyType = .done
keystoreJSONControlsStackView = [
keystoreJSONTextView.label,
.spacer(height: 4),
keystoreJSONTextView,
.spacer(height: 10),
passwordTextField.label,
.spacer(height: 4),
passwordTextField,
].asStackView(axis: .vertical)
keystoreJSONControlsStackView.translatesAutoresizingMaskIntoConstraints = false
privateKeyControlsStackView = [
privateKeyTextView.label,
.spacer(height: 4),
privateKeyTextView,
].asStackView(axis: .vertical)
privateKeyControlsStackView.translatesAutoresizingMaskIntoConstraints = false
watchControlsStackView = [
watchAddressTextField.label,
.spacer(height: 4),
watchAddressTextField,
].asStackView(axis: .vertical)
watchControlsStackView.translatesAutoresizingMaskIntoConstraints = false
let stackView = [
tabBar,
.spacer(height: 10),
keystoreJSONControlsStackView,
privateKeyControlsStackView,
watchControlsStackView,
].asStackView(axis: .vertical, alignment: .center)
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
importButton.setTitle(R.string.localizable.importWalletImportButtonTitle(), for: .normal)
importButton.addTarget(self, action: #selector(importWallet), for: .touchUpInside)
let buttonsStackView = [importButton].asStackView(distribution: .fillEqually, contentHuggingPriority: .required)
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
footerBar.translatesAutoresizingMaskIntoConstraints = false
footerBar.backgroundColor = Colors.appHighlightGreen
roundedBackground.addSubview(footerBar)
let buttonsHeight = CGFloat(60)
footerBar.addSubview(buttonsStackView)
let xMargin = CGFloat(7)
let heightThatFitsPrivateKeyNicely = CGFloat(100)
NSLayoutConstraint.activate([
keystoreJSONTextView.heightAnchor.constraint(equalToConstant: heightThatFitsPrivateKeyNicely),
privateKeyTextView.heightAnchor.constraint(equalToConstant: heightThatFitsPrivateKeyNicely),
tabBar.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
tabBar.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
keystoreJSONControlsStackView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: xMargin),
keystoreJSONControlsStackView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -xMargin),
privateKeyControlsStackView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: xMargin),
privateKeyControlsStackView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -xMargin),
watchControlsStackView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: xMargin),
watchControlsStackView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -xMargin),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
buttonsStackView.leadingAnchor.constraint(equalTo: footerBar.leadingAnchor),
buttonsStackView.trailingAnchor.constraint(equalTo: footerBar.trailingAnchor),
buttonsStackView.topAnchor.constraint(equalTo: footerBar.topAnchor),
buttonsStackView.heightAnchor.constraint(equalToConstant: buttonsHeight),
footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
footerBar.heightAnchor.constraint(equalToConstant: buttonsHeight),
footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: footerBar.topAnchor),
] + roundedBackground.createConstraintsWithContainer(view: view))
configure()
showKeystoreControlsOnly()
navigationItem.rightBarButtonItems = [
UIBarButtonItem(image: R.image.import_options(), style: .done, target: self, action: #selector(importOptions)),
UIBarButtonItem(image: R.image.qr_code_icon(), style: .done, target: self, action: #selector(openReader)),
@ -71,126 +172,97 @@ class ImportWalletViewController: FormViewController {
self.demo()
}
}
}
let recipientRightView = FieldAppereance.addressFieldRightView(
pasteAction: { [unowned self] in self.pasteAddressAction() },
qrAction: { [unowned self] in self.openReader() }
)
form
+++ Section {
var header = HeaderFooterView<InfoHeaderView>(.class)
header.height = { 90 }
header.onSetupView = { (view, section) -> Void in
view.label.attributedText = R.string.localizable.walletImportSubtitle().styled(
with:
.color(UIColor(hex: "6e6e72")),
.font(Fonts.regular(size: 16)!),
.lineHeightMultiple(1.25)
)
view.logoImageView.image = R.image.create_wallet_import()
}
$0.header = header
}
<<< SegmentedRow<String>(Values.segment) {
$0.options = [
//ImportSelectionType.mnemonic.title,
ImportSelectionType.keystore.title,
ImportSelectionType.privateKey.title,
ImportSelectionType.watch.title,
]
$0.value = ImportSelectionType.keystore.title
}
func configure() {
view.backgroundColor = viewModel.backgroundColor
<<< AppFormAppearance.textArea(tag: Values.mnemonic) {
$0.placeholder = R.string.localizable.mnemonic()
$0.textAreaHeight = .fixed(cellHeight: 140)
$0.add(rule: RuleRequired())
keystoreJSONTextView.configureOnce()
keystoreJSONTextView.label.textAlignment = .center
keystoreJSONTextView.label.text = viewModel.keystoreJSONLabel
$0.hidden = Eureka.Condition.function([Values.segment], { _ in
return self.segmentRow?.value != ImportSelectionType.mnemonic.title
})
}
passwordTextField.configureOnce()
passwordTextField.label.textAlignment = .center
passwordTextField.label.text = viewModel.passwordLabel
<<< AppFormAppearance.textArea(tag: Values.keystore) {
$0.placeholder = R.string.localizable.keystoreJSON()
$0.textAreaHeight = .fixed(cellHeight: 140)
$0.add(rule: RuleRequired())
privateKeyTextView.configureOnce()
privateKeyTextView.label.textAlignment = .center
privateKeyTextView.label.text = viewModel.privateKeyLabel
$0.hidden = Eureka.Condition.function([Values.segment], { _ in
return self.segmentRow?.value != ImportSelectionType.keystore.title
})
}
watchAddressTextField.configureOnce()
watchAddressTextField.label.textAlignment = .center
watchAddressTextField.label.text = viewModel.watchAddressLabel
<<< AppFormAppearance.textArea(tag: Values.privateKey) {
$0.placeholder = R.string.localizable.privateKey()
$0.textAreaHeight = .fixed(cellHeight: 140)
$0.add(rule: RuleRequired())
$0.add(rule: PrivateKeyRule())
$0.hidden = Eureka.Condition.function([Values.segment], { _ in
return self.segmentRow?.value != ImportSelectionType.privateKey.title
})
}
importButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
importButton.backgroundColor = viewModel.buttonBackgroundColor
importButton.titleLabel?.font = viewModel.buttonFont
}
<<< AppFormAppearance.textFieldFloat(tag: Values.watch) {
$0.add(rule: RuleRequired())
$0.add(rule: EthereumAddressRule())
$0.hidden = Eureka.Condition.function([Values.segment], { _ in
return self.segmentRow?.value != ImportSelectionType.watch.title
})
}.cellUpdate { cell, _ in
cell.textField.placeholder = R.string.localizable.ethereumAddress()
cell.textField.rightView = recipientRightView
cell.textField.rightViewMode = .always
}
func didImport(account: Wallet) {
delegate?.didImportAccount(account: account, in: self)
}
<<< AppFormAppearance.textFieldFloat(tag: Values.password) {
$0.validationOptions = .validatesOnDemand
$0.hidden = Eureka.Condition.function([Values.segment], { _ in
return self.segmentRow?.value != ImportSelectionType.keystore.title
})
}.cellUpdate { cell, _ in
cell.textField.isSecureTextEntry = true
cell.textField.textAlignment = .left
cell.textField.placeholder = R.string.localizable.password()
}
///Returns true only if valid
private func validate() -> Bool {
switch tabBar.tab {
case .keystore:
return validateKeystore()
case .privateKey:
return validatePrivateKey()
case .watch:
return validateWatch()
default:
return true
}
}
+++ Section("")
///Returns true only if valid
private func validateKeystore() -> Bool {
if keystoreJSONTextView.value.isEmpty {
displayError(title: viewModel.keystoreJSONLabel, error: ValidationError(msg: R.string.localizable.warningFieldRequired()))
return false
}
if passwordTextField.value.isEmpty {
displayError(title: viewModel.passwordLabel, error: ValidationError(msg: R.string.localizable.warningFieldRequired()))
return false
}
return true
}
<<< ButtonRow(R.string.localizable.importWalletImportButtonTitle()) {
$0.title = $0.tag
}.onCellSelection { [unowned self] _, _ in
self.importWallet()
}
///Returns true only if valid
private func validatePrivateKey() -> Bool {
if let validationError = PrivateKeyRule().isValid(value: passwordTextField.value) {
displayError(error: ValidationError(msg: validationError.msg))
return false
}
return true
}
func didImport(account: Wallet) {
delegate?.didImportAccount(account: account, in: self)
///Returns true only if valid
private func validateWatch() -> Bool {
if let validationError = EthereumAddressRule().isValid(value: watchAddressTextField.value) {
displayError(error: ValidationError(msg: validationError.msg))
return false
}
return true
}
func importWallet() {
let validatedError = keystoreRow?.section?.form?.validate()
guard let errors = validatedError, errors.isEmpty else { return }
@objc func importWallet() {
guard validate() else { return }
let keystoreInput = keystoreRow?.value?.trimmed ?? ""
let privateKeyInput = privateKeyRow?.value?.trimmed ?? ""
let password = passwordRow?.value ?? ""
let watchInput = watchRow?.value?.trimmed ?? ""
let mnemonicInput = mnemonicRow?.value?.trimmed ?? ""
let words = mnemonicInput.components(separatedBy: " ").map { $0.trimmed }
let keystoreInput = keystoreJSONTextView.value.trimmed
let privateKeyInput = privateKeyTextView.value.trimmed
let password = passwordTextField.value.trimmed
let watchInput = watchAddressTextField.value.trimmed
displayLoading(text: R.string.localizable.importWalletImportingIndicatorLabelTitle(), animated: false)
let type = ImportSelectionType(title: segmentRow?.value)
let importType: ImportType = {
switch type {
switch tabBar.tab {
case .keystore:
return .keystore(string: keystoreInput, password: password)
case .privateKey:
return .privateKey(privateKey: privateKeyInput)
case .mnemonic:
return .mnemonic(words: words, password: password)
case .watch:
let address = Address(string: watchInput)! // Address validated by form view.
return .watch(address: address)
@ -246,40 +318,60 @@ class ImportWalletViewController: FormViewController {
}
func setValueForCurrentField(string: String) {
let type = ImportSelectionType(title: segmentRow?.value)
switch type {
switch tabBar.tab {
case .keystore:
keystoreRow?.value = string
keystoreRow?.reload()
keystoreJSONTextView.value = string
case .privateKey:
privateKeyRow?.value = string
privateKeyRow?.reload()
privateKeyTextView.value = string
case .watch:
watchRow?.value = string
watchRow?.reload()
case .mnemonic:
mnemonicRow?.value = string
mnemonicRow?.reload()
watchAddressTextField.value = string
default:
return
}
}
@objc func pasteAddressAction() {
let value = UIPasteboard.general.string?.trimmed
watchRow?.value = value
watchRow?.reload()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func showKeystoreControlsOnly() {
keystoreJSONControlsStackView.isHidden = false
privateKeyControlsStackView.isHidden = true
watchControlsStackView.isHidden = true
}
private func showPrivateKeyControlsOnly() {
keystoreJSONControlsStackView.isHidden = true
privateKeyControlsStackView.isHidden = false
watchControlsStackView.isHidden = true
}
private func showWatchControlsOnly() {
keystoreJSONControlsStackView.isHidden = true
privateKeyControlsStackView.isHidden = true
watchControlsStackView.isHidden = false
}
private func moveFocusToTextEntryField(after textInput: UIView) {
switch textInput {
case keystoreJSONTextView.textView:
passwordTextField.textField.becomeFirstResponder()
case passwordTextField.textField:
view.endEditing(true)
case privateKeyTextView.textView:
view.endEditing(true)
case watchAddressTextField.textField:
view.endEditing(true)
default:
break
}
}
}
extension ImportWalletViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
if controller.documentPickerMode == UIDocumentPickerMode.import {
let text = try? String(contentsOfFile: url.path)
keystoreRow?.value = text
keystoreRow?.reload()
guard controller.documentPickerMode == UIDocumentPickerMode.import else { return }
let text = try? String(contentsOfFile: url.path)
if let text = text {
keystoreJSONTextView.value = text
}
}
}
@ -295,3 +387,71 @@ extension ImportWalletViewController: QRCodeReaderDelegate {
reader.dismiss(animated: true)
}
}
extension ImportWalletViewController: TextFieldDelegate {
func shouldReturn(in textField: TextField) -> Bool {
moveFocusToTextEntryField(after: textField.textField)
return false
}
func doneButtonTapped(for textField: TextField) {
view.endEditing(true)
}
func nextButtonTapped(for textField: TextField) {
moveFocusToTextEntryField(after: textField.textField)
}
}
extension ImportWalletViewController: TextViewDelegate {
func shouldReturn(in textView: TextView) -> Bool {
moveFocusToTextEntryField(after: textView.textView)
return false
}
func doneButtonTapped(for textView: TextView) {
view.endEditing(true)
}
func nextButtonTapped(for textView: TextView) {
moveFocusToTextEntryField(after: textView.textView)
}
}
extension ImportWalletViewController: AddressTextFieldDelegate {
func displayError(error: Error, for textField: AddressTextField) {
displayError(error: error)
}
func openQRCodeReader(for textField: AddressTextField) {
openReader()
}
func didPaste(in textField: AddressTextField) {
view.endEditing(true)
}
func shouldReturn(in textField: AddressTextField) -> Bool {
moveFocusToTextEntryField(after: textField.textField)
return false
}
func shouldChange(in range: NSRange, to string: String, in textField: AddressTextField) -> Bool {
return true
}
}
extension ImportWalletViewController: ImportWalletTabBarDelegate {
func didPressImportWalletTab(tab: ImportWalletTab, in tabBar: ImportWalletTabBar) {
switch tab {
case .keystore:
showKeystoreControlsOnly()
case .privateKey:
showPrivateKeyControlsOnly()
case .watch:
showWatchControlsOnly()
default:
break
}
}
}

@ -0,0 +1,36 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
struct ImportWalletTabBarViewModel {
var currentTab: ImportWalletTab
init(tab: ImportWalletTab) {
currentTab = tab
}
var backgroundColor: UIColor {
return Colors.appBackground
}
func titleColor(for tab: ImportWalletTab) -> UIColor {
if currentTab == tab {
return Colors.appBackground
} else {
return Colors.appWhite
}
}
var font: UIFont {
return Fonts.regular(size: 14)!
}
var barUnhighlightedColor: UIColor {
return UIColor(red: 122, green: 197, blue: 225)
}
var barHighlightedColor: UIColor {
return Colors.appWhite
}
}

@ -1,10 +1,42 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import UIKit
struct ImportWalletViewModel {
var backgroundColor: UIColor {
return Colors.appBackground
}
var title: String {
return R.string.localizable.importNavigationTitle()
}
var buttonTitleColor: UIColor {
return Colors.appWhite
}
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
var keystoreJSONLabel: String {
return R.string.localizable.keystoreJSON().uppercased()
}
var passwordLabel: String {
return R.string.localizable.password().uppercased()
}
var privateKeyLabel: String {
return R.string.localizable.privateKey().uppercased()
}
var watchAddressLabel: String {
return R.string.localizable.ethereumAddress().uppercased()
}
}

@ -0,0 +1,138 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
protocol ImportWalletTabBarDelegate: class {
func didPressImportWalletTab(tab: ImportWalletTab, in tabBar: ImportWalletTabBar)
}
class ImportWalletTabBar: UIView {
let keystoreButton = UIButton(type: .system)
let privateKeyButton = UIButton(type: .system)
let watchButton = UIButton(type: .system)
let tabHighlightView = UIView()
var tab: ImportWalletTab = .keystore {
didSet {
viewModel.currentTab = tab
delegate?.didPressImportWalletTab(tab: tab, in: self)
configure()
}
}
var highlightBarHorizontalConstraints: [NSLayoutConstraint]?
weak var delegate: ImportWalletTabBarDelegate?
lazy var viewModel = ImportWalletTabBarViewModel(tab: tab)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = viewModel.backgroundColor
keystoreButton.setTitle(ImportSelectionType.keystore.title.uppercased(), for: .normal)
keystoreButton.titleLabel?.font = viewModel.font
keystoreButton.addTarget(self, action: #selector(showKeystoreTab), for: .touchUpInside)
privateKeyButton.setTitle(ImportSelectionType.privateKey.title.uppercased(), for: .normal)
privateKeyButton.titleLabel?.font = viewModel.font
privateKeyButton.addTarget(self, action: #selector(showPrivateKeyTab), for: .touchUpInside)
watchButton.setTitle(ImportSelectionType.watch.title.uppercased(), for: .normal)
watchButton.titleLabel?.font = viewModel.font
watchButton.addTarget(self, action: #selector(showWatchTab), for: .touchUpInside)
let fullWidthBar = UIView()
fullWidthBar.translatesAutoresizingMaskIntoConstraints = false
fullWidthBar.backgroundColor = .clear
fullWidthBar.isUserInteractionEnabled = false
addSubview(fullWidthBar)
tabHighlightView.translatesAutoresizingMaskIntoConstraints = false
tabHighlightView.backgroundColor = viewModel.barHighlightedColor
fullWidthBar.addSubview(tabHighlightView)
let buttonsStackView = [keystoreButton, privateKeyButton, watchButton].asStackView(spacing: 20)
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(buttonsStackView)
let barHeightConstraint = fullWidthBar.heightAnchor.constraint(equalToConstant: 44)
barHeightConstraint.priority = .defaultHigh
let stackViewLeadingConstraint = buttonsStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 17)
stackViewLeadingConstraint.priority = .defaultHigh
let stackViewTrailingConstraint = buttonsStackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, constant: -17)
stackViewTrailingConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
stackViewLeadingConstraint,
stackViewTrailingConstraint,
buttonsStackView.topAnchor.constraint(equalTo: topAnchor),
buttonsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
keystoreButton.widthAnchor.constraint(equalTo: privateKeyButton.widthAnchor),
keystoreButton.widthAnchor.constraint(equalTo: watchButton.widthAnchor),
fullWidthBar.leadingAnchor.constraint(equalTo: leadingAnchor),
fullWidthBar.trailingAnchor.constraint(equalTo: trailingAnchor),
barHeightConstraint,
fullWidthBar.topAnchor.constraint(equalTo: topAnchor),
fullWidthBar.bottomAnchor.constraint(equalTo: bottomAnchor),
tabHighlightView.topAnchor.constraint(equalTo: fullWidthBar.topAnchor),
tabHighlightView.bottomAnchor.constraint(equalTo: fullWidthBar.bottomAnchor, constant: 20),
])
configure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func showKeystoreTab() {
tab = .keystore
}
@objc func showPrivateKeyTab() {
tab = .privateKey
}
@objc func showWatchTab() {
tab = .watch
}
private func configure() {
configureButtonColors()
configureHighlightedBar()
}
private func configureButtonColors() {
keystoreButton.setTitleColor(viewModel.titleColor(for: .keystore), for: .normal)
privateKeyButton.setTitleColor(viewModel.titleColor(for: .privateKey), for: .normal)
watchButton.setTitleColor(viewModel.titleColor(for: .watch), for: .normal)
}
private func configureHighlightedBar() {
tabHighlightView.cornerRadius = 14
var button: UIButton
switch tab {
case .keystore:
button = keystoreButton
case .privateKey:
button = privateKeyButton
case .watch:
button = watchButton
}
if let previousConstraints = highlightBarHorizontalConstraints {
NSLayoutConstraint.deactivate(previousConstraints)
}
highlightBarHorizontalConstraints = [
tabHighlightView.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: -10),
tabHighlightView.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: 10),
]
if let constraints = highlightBarHorizontalConstraints {
NSLayoutConstraint.activate(constraints)
}
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
}

@ -51,5 +51,17 @@ class ConfigTests: XCTestCase {
//Must change this back to system, otherwise other tests will break either immediately or the next run
config.locale = AppLocale.system.id
}
}
func testNibsAccessAfterSwitchingLocale() {
var config: Config = .make()
config.locale = AppLocale.english.id
config.locale = AppLocale.simplifiedChinese.id
let controller = AccountsViewController(keystore: FakeKeystore(), balanceCoordinator: FakeGetBalanceCoordinator())
let _ = controller.view
XCTAssertNoThrow(controller.tableView.dequeueReusableCell(withIdentifier: R.nib.accountViewCell.name, for: .init(row: 0, section: 0)))
//Must change this back to system, otherwise other tests will break either immediately or the next run
config.locale = AppLocale.system.id
}
}

Loading…
Cancel
Save