diff --git a/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents b/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents index ebf5ff422..18bd22603 100644 --- a/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents +++ b/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -53,6 +53,7 @@ + diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 3e3a7519a..19764b297 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -100,7 +100,7 @@ struct AppAssembly: MainThreadAssembly { SecretWalletsFactory( visibleWalletsService: r.resolve(VisibleWalletsService.self)!, accountService: r.resolve(AccountService.self)!, - SecureStore: r.resolve(SecureStore.self)! + container: container ) }.inObjectScope(.container) @@ -116,7 +116,18 @@ struct AppAssembly: MainThreadAssembly { secretWalletsManager: r.resolve(SecretWalletsManagerProtocol.self)! ) }.inObjectScope(.container) - + + container.register(SecretWalletsViewModel.self) { r in + SecretWalletsViewModel(secretWalletsManager: r.resolve(SecretWalletsManagerProtocol.self)!) + }.inObjectScope(.container) + + container.register(SecretWalletsAlertMenuView.self) { r in + SecretWalletsAlertMenuView( + dialogService: r.resolve(DialogService.self)!, + secretWalletsViewModel: r.resolve(SecretWalletsViewModel.self)! + ) + }.inObjectScope(.transient) + // MARK: IncreaseFeeService container.register(IncreaseFeeService.self) { r in AdamantIncreaseFeeService( diff --git a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift index cbd57b6ff..d068d72d9 100644 --- a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift +++ b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift @@ -19,6 +19,7 @@ extension CoinTransaction { @NSManaged public var amount: NSDecimalNumber? @NSManaged public var transactionId: String @NSManaged public var coinId: String? + @NSManaged public var uniqueId: String? @NSManaged public var senderId: String? @NSManaged public var recipientId: String? @NSManaged public var date: NSDate? diff --git a/Adamant/Modules/Account/AccountFactory.swift b/Adamant/Modules/Account/AccountFactory.swift index b23d5211c..ea7805f6f 100644 --- a/Adamant/Modules/Account/AccountFactory.swift +++ b/Adamant/Modules/Account/AccountFactory.swift @@ -27,7 +27,9 @@ struct AccountFactory { languageService: assembler.resolve(LanguageStorageProtocol.self)!, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, - visibleWalletsService: assembler.resolve(VisibleWalletsService.self)! + visibleWalletsService: assembler.resolve(VisibleWalletsService.self)!, + secretWalletsAlertService: assembler.resolve(SecretWalletsAlertMenuView.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } } diff --git a/Adamant/Modules/Account/AccountHeader.xib b/Adamant/Modules/Account/AccountHeader.xib index e0c1f2f3f..b9f6afbb5 100644 --- a/Adamant/Modules/Account/AccountHeader.xib +++ b/Adamant/Modules/Account/AccountHeader.xib @@ -1,9 +1,9 @@ - - + + - + @@ -53,13 +53,22 @@ + + + + + + + + + @@ -69,13 +78,15 @@ + - + - + + diff --git a/Adamant/Modules/Account/AccountHeaderView.swift b/Adamant/Modules/Account/AccountHeaderView.swift index b41afe2ad..7545236ac 100644 --- a/Adamant/Modules/Account/AccountHeaderView.swift +++ b/Adamant/Modules/Account/AccountHeaderView.swift @@ -11,6 +11,7 @@ import UIKit @MainActor protocol AccountHeaderViewDelegate: AnyObject { func addressLabelTapped(from: UIView) + func walletsButtonTapped(from: UIView) } final class AccountHeaderView: UIView { @@ -19,10 +20,135 @@ final class AccountHeaderView: UIView { @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var addressButton: UIButton! @IBOutlet weak var walletViewContainer: UIView! - + @IBOutlet weak var secretWalletsImageView: UIImageView! + + private var circularBackgroundView: UIView? + weak var delegate: AccountHeaderViewDelegate? - + + override func awakeFromNib() { + super.awakeFromNib() + + setWalletIcon(.regular, badgeCount: 0) + addPersistentOutline() + + setupGestureRecognizers() + } + @IBAction func addressButtonTapped(_ sender: UIButton) { delegate?.addressLabelTapped(from: sender) } + + @objc private func walletsButtonTapped() { + animateOutline() + delegate?.walletsButtonTapped(from: secretWalletsImageView) + } + + override func layoutSubviews() { + super.layoutSubviews() + guard let bgView = circularBackgroundView else { return } + + let iconFrame = secretWalletsImageView.frame + let bgWidth = iconFrame.width * 2 + let bgHeight = iconFrame.height * 2 + bgView.frame = CGRect(x: iconFrame.midX - bgWidth / 2, + y: iconFrame.midY - bgHeight / 2, + width: bgWidth, + height: bgHeight) + bgView.layer.cornerRadius = bgWidth / 2 + } +} + +extension AccountHeaderView { + func setWalletIcon(_ icon: WalletIcon, badgeCount: Int) { + secretWalletsImageView.tintColor = .adamant.secondary + secretWalletsImageView.image = .asset(named: icon.rawValue)?.withRenderingMode(.alwaysTemplate) ?? .init() + updateWalletBadge(count: badgeCount) + } +} + +private extension AccountHeaderView { + func setupGestureRecognizers() { + secretWalletsImageView.isUserInteractionEnabled = true + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(walletsButtonTapped)) + secretWalletsImageView.addGestureRecognizer(tapGesture) + + guard self.circularBackgroundView != nil else { return } + circularBackgroundView!.isUserInteractionEnabled = true + let bgTapGesture = UITapGestureRecognizer(target: self, action: #selector(walletsButtonTapped)) + circularBackgroundView!.addGestureRecognizer(bgTapGesture) + } + + func updateWalletBadge(count: Int) { + guard let bgView = circularBackgroundView else { return } + + bgView.viewWithTag(99)?.removeFromSuperview() + + guard count > 0 else { return } + + let badgeLabel = UILabel() + badgeLabel.tag = 99 + badgeLabel.text = "\(count)" + badgeLabel.font = .systemFont(ofSize: 14, weight: .medium) + badgeLabel.textAlignment = .center + badgeLabel.textColor = UIColor { traitCollection in + return traitCollection.userInterfaceStyle == .light ? .white : .black + } + badgeLabel.backgroundColor = .adamant.secondary + + let badgeSize: CGFloat = 24 + badgeLabel.layer.cornerRadius = badgeSize / 2 + badgeLabel.layer.masksToBounds = true + + badgeLabel.translatesAutoresizingMaskIntoConstraints = false + bgView.addSubview(badgeLabel) + + badgeLabel.snp.makeConstraints { make in + make.width.height.equalTo(badgeSize) + make.trailing.equalTo(bgView.snp.trailing) + make.bottom.equalTo(bgView.snp.bottom) + } + } + + func addPersistentOutline() { + let bgView = UIView() + bgView.backgroundColor = UIColor { traitCollection in + return traitCollection.userInterfaceStyle == .light + ? .adamant.secondBackgroundColor + : .adamant.background + } + + secretWalletsImageView.superview?.insertSubview(bgView, belowSubview: secretWalletsImageView) + self.circularBackgroundView = bgView + } + + func animateOutline() { + guard let bgView = circularBackgroundView else { return } + + let originalColor = bgView.backgroundColor + let highlightColor = UIColor.systemGray5 + + UIView.animate(withDuration: 0.1, animations: { + bgView.backgroundColor = highlightColor + bgView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) + self.secretWalletsImageView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) + }, completion: { _ in + UIView.animate(withDuration: 0.1, animations: { + bgView.backgroundColor = originalColor + bgView.transform = .identity + self.secretWalletsImageView.transform = .identity + }) + }) + } +} + +extension AccountHeaderView { + enum WalletIcon: String { + case regular = "secret_wallets_regular" + case secret = "secret_wallets_active" + + func image() -> UIImage? { + return UIImage(named: self.rawValue) + } + } } diff --git a/Adamant/Modules/Account/AccountViewController/AccountViewController+Form.swift b/Adamant/Modules/Account/AccountViewController/AccountViewController+Form.swift index 3c1ee13cd..cb5d4d17d 100644 --- a/Adamant/Modules/Account/AccountViewController/AccountViewController+Form.swift +++ b/Adamant/Modules/Account/AccountViewController/AccountViewController+Form.swift @@ -35,11 +35,11 @@ extension AccountViewController { } enum Rows { - case balance, sendTokens // Wallet - case security, nodes, coinsNodes, theme, currency, language, about, visibleWallets, contribute, storage // Application - case voteForDelegates, generateQr, generatePk, logout // Actions - case stayIn, biometry, notifications // Security - + case balance, sendTokens // Wallet + case security, nodes, coinsNodes, theme, currency, language, about, visibleWallets, secretWallets, contribute, storage // Application + case voteForDelegates, generateQr, generatePk, logout // Actions + case stayIn, biometry, notifications // Security + var tag: String { switch self { case .balance: return "blnc" @@ -57,6 +57,7 @@ extension AccountViewController { case .biometry: return "biometry" case .notifications: return "notifications" case .visibleWallets: return "visibleWallets" + case .secretWallets: return "secretWallets" case .contribute: return "contribute" case .coinsNodes: return "coinsNodes" case .language: return "language" @@ -81,6 +82,7 @@ extension AccountViewController { case .biometry: return SecurityViewController.Rows.biometry.localized case .notifications: return SecurityViewController.Rows.notificationsMode.localized case .visibleWallets: return .localized("VisibleWallets.Title", comment: "Visible Wallets page: scene title") + case .secretWallets: return .localized("SecretWallets.Row.Title", comment: "Secret wallets page: row title") case .contribute: return .localized("AccountTab.Row.Contribute", comment: "Account tab: 'Contribute' row") case .coinsNodes: return .adamant.coinsNodesList.title case .language: return .localized("AccountTab.Row.Language", comment: "Account tab: 'Language' row") @@ -107,6 +109,7 @@ extension AccountViewController { case .biometry: image = nil // Determined by localAuth service case .notifications: image = .asset(named: "row_Notifications.png") case .visibleWallets: image = .asset(named: "row_balance") + case .secretWallets: image = .asset(named: "secret_wallets_active") case .contribute: image = .asset(named: "row_contribute") case .language: image = .asset(named: "row_language") case .storage: image = .asset(named: "row_storage") diff --git a/Adamant/Modules/Account/AccountViewController/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController/AccountViewController.swift index 2c4622c39..eb0065fdd 100644 --- a/Adamant/Modules/Account/AccountViewController/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController/AccountViewController.swift @@ -48,8 +48,10 @@ final class AccountViewController: FormViewController { private let languageService: LanguageStorageProtocol private let apiServiceCompose: ApiServiceComposeProtocol private let visibleWalletsService: VisibleWalletsService - private lazy var viewModel: AccountWalletsViewModel = .init(walletsStoreService: walletStoreServiceProvider) - + private lazy var walletsViewModel: AccountWalletsViewModel = .init(walletsStoreService: walletStoreServiceProvider) + private let secretWalletsAlertService: SecretWalletsAlertMenuView + private let secretWalletsViewModel: SecretWalletsViewModel + let accountService: AccountService let dialogService: DialogService let localAuth: LocalAuthentication @@ -63,7 +65,7 @@ final class AccountViewController: FormViewController { private var pagingViewController: PagingViewController! private var notificationsSet: Set = [] - + // MARK: StayIn var showLoggedInOptions: Bool { @@ -92,7 +94,7 @@ final class AccountViewController: FormViewController { private var walletViewControllers: [WalletViewController] = [] private lazy var currentSelectedWallet: AccountWalletCellState? = { - viewModel.state.wallets.first(where: { $0.model.index == 0 }) + walletsViewModel.state.wallets.first(where: { $0.model.index == 0 }) }() private var initiated = false @@ -112,7 +114,9 @@ final class AccountViewController: FormViewController { languageService: LanguageStorageProtocol, walletServiceCompose: WalletServiceCompose, apiServiceCompose: ApiServiceComposeProtocol, - visibleWalletsService: VisibleWalletsService + visibleWalletsService: VisibleWalletsService, + secretWalletsAlertService: SecretWalletsAlertMenuView, + secretWalletsViewModel: SecretWalletsViewModel ) { self.walletStoreServiceProvider = walletStoreServiceProvider self.accountService = accountService @@ -126,7 +130,9 @@ final class AccountViewController: FormViewController { self.languageService = languageService self.apiServiceCompose = apiServiceCompose self.visibleWalletsService = visibleWalletsService - + self.secretWalletsAlertService = secretWalletsAlertService + self.secretWalletsViewModel = secretWalletsViewModel + super.init(nibName: nil, bundle: nil) } @@ -169,7 +175,23 @@ final class AccountViewController: FormViewController { accountHeaderView = header accountHeaderView.delegate = self - + + secretWalletsViewModel.$state + .removeDuplicates() + .map { $0.currentActiveIndex } + .receive(on: DispatchQueue.main) + .sink { [weak self] index in + guard let self = self else { return } + self.setupWalletsVC() + self.pagingViewController.reloadData() + if let pagingItemIndex = currentSelectedWallet?.identifier { + self.pagingViewController.select(index: pagingItemIndex, animated: false) + } + guard index >= 0 else { return } + self.accountHeaderView.setWalletIcon(index == 0 ? .regular : .secret, badgeCount: index) + } + .store(in: ¬ificationsSet) + updateAccountInfo() tableView.tableHeaderView = header @@ -201,6 +223,74 @@ final class AccountViewController: FormViewController { pagingViewController.borderColor = UIColor.clear + setupSections() + + // MARK: Notification Center + addObservers() + + setColors() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: animated) + + if let indexPath = tableView.indexPathForSelectedRow { + tableView.deselectRow(at: indexPath, animated: animated) + } + + for vc in pagingViewController.pageViewController.children { + vc.viewWillAppear(animated) + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.setNavigationBarHidden(false, animated: animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !initiated { + initiated = true + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + if UIScreen.main.traitCollection.userInterfaceIdiom == .pad { + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0) + } + + if UIScreen.main.traitCollection.userInterfaceIdiom == .pad, !initiated { + layoutTableHeaderView() + if !initiated { + initiated = true + } + } + + pagingViewController?.indicatorColor = UIColor.adamant.primary + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: TableView configuration + + override func insertAnimation(forSections sections: [Section]) -> UITableView.RowAnimation { + return .fade + } + + override func deleteAnimation(forSections sections: [Section]) -> UITableView.RowAnimation { + return .fade + } + + // MARK: Other + + private func setupSections() { // MARK: Rows&Sections // MARK: Application @@ -236,7 +326,23 @@ final class AccountViewController: FormViewController { } appSection.append(visibleWalletsRow) - + + // Secret Wallets + let secretWalletsRow = LabelRow { + $0.tag = Rows.secretWallets.tag + $0.title = Rows.secretWallets.localized + $0.cell.imageView?.image = Rows.secretWallets.image + $0.cell.selectionStyle = .gray + }.cellUpdate { (cell, row) in + cell.accessoryType = .disclosureIndicator + row.title = Rows.secretWallets.localized + }.onCellSelection { [weak self] (cell, _) in + guard let self = self else { return } + self.secretWalletsAlertService.presentSecretWalletsActionSheet(from: cell) + } + + appSection.append(secretWalletsRow) + // Node list let nodesRow = LabelRow { $0.title = Rows.nodes.localized @@ -668,72 +774,8 @@ final class AccountViewController: FormViewController { form.append(appSection) form.allRows.forEach { $0.baseCell.imageView?.tintColor = UIColor.adamant.tableRowIcons } - - // MARK: Notification Center - addObservers() - - setColors() } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - navigationController?.setNavigationBarHidden(true, animated: animated) - - if let indexPath = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: indexPath, animated: animated) - } - - for vc in pagingViewController.pageViewController.children { - vc.viewWillAppear(animated) - } - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - navigationController?.setNavigationBarHidden(false, animated: animated) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !initiated { - initiated = true - } - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - if UIScreen.main.traitCollection.userInterfaceIdiom == .pad { - tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0) - } - - if UIScreen.main.traitCollection.userInterfaceIdiom == .pad, !initiated { - layoutTableHeaderView() - if !initiated { - initiated = true - } - } - - pagingViewController?.indicatorColor = UIColor.adamant.primary - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - // MARK: TableView configuration - - override func insertAnimation(forSections sections: [Section]) -> UITableView.RowAnimation { - return .fade - } - - override func deleteAnimation(forSections sections: [Section]) -> UITableView.RowAnimation { - return .fade - } - - // MARK: Other - + func addObservers() { NotificationCenter.default.addObserver( forName: Notification.Name.AdamantAccountService.userLoggedIn, @@ -852,8 +894,7 @@ final class AccountViewController: FormViewController { .sink { [weak self] _ in guard let self = self else { return } self.setupWalletsVC() - self.viewModel.updateState() - + self.walletsViewModel.updateState() self.updatePagingItemHeight() self.pagingViewController.reloadData() @@ -867,7 +908,7 @@ final class AccountViewController: FormViewController { private func updateUI() { let appSection = form.sectionBy(tag: Sections.application.tag) appSection?.header?.title = Sections.application.localized - + let walletSection = form.sectionBy(tag: Sections.wallet.tag) walletSection?.header?.title = Sections.wallet.localized @@ -943,10 +984,10 @@ final class AccountViewController: FormViewController { func layoutTableHeaderView() { guard let view = tableView.tableHeaderView else { return } var frame = view.frame - + frame.size.height = 300 view.frame = frame - + self.tableView.tableHeaderView = view } @@ -989,6 +1030,10 @@ final class AccountViewController: FormViewController { // MARK: - AccountHeaderViewDelegate extension AccountViewController: AccountHeaderViewDelegate { + func walletsButtonTapped(from sender: UIView) { + secretWalletsAlertService.presentSecretWalletsActionSheet(from: sender) + } + func addressLabelTapped(from: UIView) { guard let address = accountService.account?.address else { return @@ -996,6 +1041,7 @@ extension AccountViewController: AccountHeaderViewDelegate { let encodedAddress = AdamantUriTools.encode(request: AdamantUri.address(address: address, params: nil)) dialogService.presentShareAlertFor( + title: nil, stringForPasteboard: address, stringForShare: encodedAddress, stringForQR: encodedAddress, @@ -1054,12 +1100,12 @@ extension AccountViewController: PagingViewControllerDataSource, PagingViewContr walletViewControllers[index].viewController } } - + nonisolated func pagingViewController(_: PagingViewController, pagingItemAt index: Int) -> PagingItem { MainActor.assertIsolated() return DispatchQueue.onMainThreadSyncSafe { - return viewModel.state.wallets[safe: index] ?? AccountWalletCellState.default + return walletsViewModel.state.wallets[safe: index] ?? AccountWalletCellState.default } } @@ -1084,7 +1130,7 @@ extension AccountViewController: PagingViewControllerDataSource, PagingViewContr else { return } - + updateHeaderSize(with: second, animated: true) } } @@ -1094,7 +1140,7 @@ extension AccountViewController: PagingViewControllerDataSource, PagingViewContr didSelectItem pagingItem: PagingItem ) { Task { @MainActor in - currentSelectedWallet = viewModel.state.wallets.first(where: { wallet in + currentSelectedWallet = walletsViewModel.state.wallets.first(where: { wallet in wallet.model.index == pagingItem.identifier }) } diff --git a/Adamant/Modules/Account/AccountViewController/AccountWallets/AccountWalletsViewModel.swift b/Adamant/Modules/Account/AccountViewController/AccountWallets/AccountWalletsViewModel.swift index 43f1016ce..1f3d1213e 100644 --- a/Adamant/Modules/Account/AccountViewController/AccountWallets/AccountWalletsViewModel.swift +++ b/Adamant/Modules/Account/AccountViewController/AccountWallets/AccountWalletsViewModel.swift @@ -15,8 +15,9 @@ final class AccountWalletsViewModel { var state: AccountWalletsState = .default private let walletsStoreService: WalletStoreServiceProviderProtocol - private var subscriptions = Set() - + private var walletSubscriptions: Set = [] + private var currentWalletPublisherSubscription: Set = [] + init(walletsStoreService: WalletStoreServiceProviderProtocol) { self.walletsStoreService = walletsStoreService setup() @@ -27,8 +28,18 @@ extension AccountWalletsViewModel { fileprivate func setup() { addObservers() } - - fileprivate func addObservers() { + + func addObservers() { + walletsStoreService.currentWalletPublisher + .sink( + receiveValue: { [weak self] _ in + self?.updateState() + } + ) + .store(in: ¤tWalletPublisherSubscription) + } + + func addWalletObservers() { for wallet in walletsStoreService.sorted(includeInvisible: false) { updateInfo(for: wallet) wallet.core.walletUpdatePublisher @@ -37,11 +48,11 @@ extension AccountWalletsViewModel { self?.updateInfo(for: wallet) } ) - .store(in: &subscriptions) + .store(in: &walletSubscriptions) } } - - fileprivate func updateInfo(for wallet: WalletService) { + + func updateInfo(for wallet: WalletService) { let coreService = wallet.core let tokenID = coreService.tokenUniqueID if let index = state.wallets.firstIndex(where: { $0.model.coinID == tokenID }) { @@ -72,8 +83,8 @@ extension AccountWalletsViewModel { extension AccountWalletsViewModel { func updateState() { - subscriptions.removeAll() + walletSubscriptions.removeAll() state.wallets.removeAll() - setup() + addWalletObservers() } } diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 427872f93..8ef90e924 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1366,6 +1366,7 @@ extension ChatListViewController { guard !partner.isSystem else { self.dialogService.presentShareAlertFor( + title: nil, string: address, types: [ .copyToPasteboard, @@ -1427,6 +1428,7 @@ extension ChatListViewController { guard let self = self else { return } self.dialogService.presentShareAlertFor( + title: title, string: address, types: [ .copyToPasteboard, diff --git a/Adamant/Modules/Delegates/DelegateDetailsViewController.swift b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift index 5f191964b..aec91b7f8 100644 --- a/Adamant/Modules/Delegates/DelegateDetailsViewController.swift +++ b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift @@ -194,6 +194,7 @@ extension DelegateDetailsViewController: UITableViewDelegate, UITableViewDataSou } dialogService.presentShareAlertFor( + title: nil, string: value, types: [.copyToPasteboard, .share], excludedActivityTypes: nil, diff --git a/Adamant/Modules/SecretWallets/SecretWalletsAlertMenuView.swift b/Adamant/Modules/SecretWallets/SecretWalletsAlertMenuView.swift new file mode 100644 index 000000000..03d1dbe39 --- /dev/null +++ b/Adamant/Modules/SecretWallets/SecretWalletsAlertMenuView.swift @@ -0,0 +1,123 @@ +// +// SecretWalletsAlertService.swift +// Adamant +// +// Created by Dmitrij Meidus on 01.03.25. +// Copyright © 2025 Adamant. All rights reserved. +// + +import UIKit + +@MainActor +final class SecretWalletsAlertMenuView { + private let dialogService: DialogService + private let secretWalletsViewModel: SecretWalletsViewModel + + init( + dialogService: DialogService, + secretWalletsViewModel: SecretWalletsViewModel + ) { + self.dialogService = dialogService + self.secretWalletsViewModel = secretWalletsViewModel + } + + func presentSecretWalletsActionSheet(from sourceView: UIView) { + var actions = [AdamantAlertAction]() + let state = secretWalletsViewModel.state + + for (index, wallet) in state.wallets.enumerated() { + let isSelected = index == state.currentActiveIndex + let walletName = isSelected ? "[ " + wallet.name + " ]" : wallet.name + + let action = AdamantAlertAction( + title: walletName, + style: .default + ) { [weak self] in + self?.secretWalletsViewModel.pickWallet(at: index) + } + actions.append(action) + } + + if state.wallets.count < 6 { + let enableWalletAction = AdamantAlertAction( + title: String.localized("SecretWallets.Menu.AddSecretWallet", comment: "Secret wallet menu: add secret wallet"), + style: .default + ) { [weak self] in + self?.showEnableSecretWalletAlert() + } + actions.append(enableWalletAction) + } + + let infoAction = AdamantAlertAction( + title: String.localized("SecretWallets.Menu.TellMeMore", comment: "Secret wallet menu: tell me more"), + style: .default + ) { [weak self] in + self?.showSecretWalletInfoAlert() + } + actions.append(infoAction) + + let cancelAction = AdamantAlertAction( + title: "Cancel", + style: .cancel, + handler: nil + ) + actions.append(cancelAction) + + let source: UIAlertController.SourceView = .view(sourceView) + dialogService.showAlert( + title: String.localized("SecretWallets.Menu.Title", comment: "Secret wallet menu: title"), + message: nil, + style: .actionSheet, + actions: actions, + from: source + ) + } + + private func showEnableSecretWalletAlert() { + let passwordAlert = UIAlertController( + title: String.localized("SecretWallets.Menu.AddSecretWallet.Title", comment: "Add secret wallet title"), + message: nil, + preferredStyle: .alert + ) + + passwordAlert.addTextField { textField in + textField.isSecureTextEntry = true + textField.placeholder = "Password" + } + + let cancelAction = UIAlertAction(title: String.localized("Cancel", comment: "Cancel adding password"), style: .cancel) + passwordAlert.addAction(cancelAction) + + let enableAction = UIAlertAction(title: String.localized("SecretWallets.Menu.AddSecretWallet.Add", comment: "Confirm adding secret password"), style: .default) { [weak self] _ in + guard + let self = self, + let password = passwordAlert.textFields?.first?.text, + !password.isEmpty + else { + return + } + self.secretWalletsViewModel.createSecretWallet(password: password) + } + passwordAlert.addAction(enableAction) + + dialogService.present(passwordAlert, animated: true, completion: nil) + } + + private func showSecretWalletInfoAlert() { + let infoAlert = UIAlertController( + title: String.localized("SecretWallets.Menu.TellMeMore.Title", comment: "Tell me more about secret wallets"), + message: String.localized("SecretWallets.Menu.TellMeMore.Subtitle", comment: "Subtitle for tell me more alert"), + preferredStyle: .alert + ) + infoAlert.addAction(.init(title: String.localized("Cancel"), style: .cancel)) + + let learnMoreAction = UIAlertAction(title: String.localized("SecretWallets.Menu.TellMeMore.LearnMore", comment: "Learn more about secret wallets") , style: .default) { _ in + if let url = URL(string: "http://news.adamant.im/") { + UIApplication.shared.open(url) + } + } + infoAlert.addAction(learnMoreAction) + + dialogService.present(infoAlert, animated: true, completion: nil) + } +} diff --git a/Adamant/Modules/SecretWallets/SecretWalletsState.swift b/Adamant/Modules/SecretWallets/SecretWalletsState.swift new file mode 100644 index 000000000..0091b550f --- /dev/null +++ b/Adamant/Modules/SecretWallets/SecretWalletsState.swift @@ -0,0 +1,34 @@ +// +// SecretWalletsState.swift +// Adamant +// +// Created by Dmitrij Meidus on 12.03.25. +// Copyright © 2025 Adamant. All rights reserved. +// + +import Foundation + +struct SecretWalletsState: Equatable { + var wallets: [WalletItem] + var currentActiveIndex: Int + + var currentWallet: WalletItem? { + guard currentActiveIndex >= 0 else { return nil } + return wallets[currentActiveIndex] + } + + static let `default` = Self( + wallets: [WalletItem( + name: String.localized( + "SecretWallets.Menu.Regular", + comment: "Secret wallet menu: regular wallet" + ) + )], + currentActiveIndex: 0 + ) +} + +struct WalletItem: Equatable, Identifiable { + let id = UUID() + let name: String +} diff --git a/Adamant/Modules/SecretWallets/SecretWalletsViewModel.swift b/Adamant/Modules/SecretWallets/SecretWalletsViewModel.swift new file mode 100644 index 000000000..28db16dc9 --- /dev/null +++ b/Adamant/Modules/SecretWallets/SecretWalletsViewModel.swift @@ -0,0 +1,130 @@ +// +// SecretWalletsViewModel.swift +// Adamant +// +// Created by Dmitrij Meidus on 28.02.25. +// Copyright © 2025 Adamant. All rights reserved. +// + +import Foundation +import CommonKit +import Combine + +@MainActor +final class SecretWalletsViewModel: ObservableObject { + @Published private(set) var state: SecretWalletsState = .default + + private let secretWalletsManager: SecretWalletsManagerProtocol + private var subscriptions = Set() + + init(secretWalletsManager: SecretWalletsManagerProtocol) { + self.secretWalletsManager = secretWalletsManager + + setup() + } + + func pickWallet(at index: Int) { + if index == 0 { + secretWalletsManager.activateDefaultWallet() + } else { + secretWalletsManager.activateSecretWallet(at: index - 1) + } + state.currentActiveIndex = index + } + + func createSecretWallet(password: String) { + let index = state.wallets.count + + secretWalletsManager.createSecretWallet(withPassword: password) + secretWalletsManager.activateSecretWallet(at: index - 1) + + state.wallets.append(WalletItem(name: String.localized("SecretWallets.Menu.Secret\(index)", comment: "Secret wallet menu: regular wallet"))) + self.state.currentActiveIndex = index + } +} + +private extension SecretWalletsViewModel { + func setup() { + NotificationCenter.default.notifications(named: .AdamantAccountService.userLoggedOut) + .sink { [weak self] _ in await self?.removeAllSecretWallets() } + .store(in: &subscriptions) + } + + func removeAllSecretWallets() { + secretWalletsManager.removeAllSecretWallets() + secretWalletsManager.activateDefaultWallet() + state.currentActiveIndex = 0 + state.wallets.removeLast(state.wallets.count - 1) + } +} + +// MARK: Naming generation +extension SecretWalletsViewModel { + func getCurrentWalletName(regularWithEmoji: Bool = true) -> String { + guard !regularWithEmoji else { + return state.wallets[state.currentActiveIndex].name + } + + if state.currentActiveIndex == 0 { + return String.localized("SecretWallets.Menu.Regular.WithoutEmoji", comment: "Regular Wallet") + } else { + return state.wallets[state.currentActiveIndex].name + } + } + + // Used in transferViewControllerBase + func getNameFor(walletCore: WalletCoreProtocol, regularWithEmoji: Bool = true) -> String { + let regular = regularWithEmoji ? String.localized("SecretWallets.Menu.Regular", comment: "Regular Wallet") : String.localized("SecretWallets.Menu.Regular.WithoutEmoji", comment: "Regular Wallet") + var name = state.currentWallet?.name ?? regular + + for wallet in secretWalletsManager.getRegularWallet().sorted(includeInvisible: false) where wallet.core.wallet?.address == walletCore.wallet?.address { + name = regular + break + } + + return name + } + + // Used in wallet list, transactions list + func getCurrentWalletEmoji() -> String { + guard state.currentActiveIndex != 0 else { + return "" + } + return String.localized("SecretWallets.Menu.Secret\(state.currentActiveIndex).Emoji") + } + + // Used in walletViewControllerBase + func getCurrentWalletCoinName(withCoinName name: String) -> String { + if state.wallets.count == 1 { + return String.localizedStringWithFormat( + String.localized( + "SecretWallets.Coin.Regular", + comment: "Regular Wallet" + ), + name + ) + } + + if state.currentActiveIndex != 0 { + return String.localizedStringWithFormat( + String.localized( + "SecretWallets.Coin.Secret\(state.currentActiveIndex)", + comment: "Secret Wallet" + ), + name + ) + } + + return String.localizedStringWithFormat( + String.localized( + "SecretWallets.Coin.SecretRegular", + comment: "Regular Wallet" + ), + name + ) + } +} + +private extension SecretWalletsViewModel { + +} diff --git a/Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift b/Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift index b96c0e4c5..3c75e8580 100644 --- a/Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift +++ b/Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift @@ -13,12 +13,16 @@ struct PKGeneratorState { var keys: [KeyInfo] var buttonDescription: AttributedString var isLoading: Bool - + var isSecretWalletsEnabled: Bool + var secretWalletPassword: String + static let `default` = Self( passphrase: .empty, keys: .init(), buttonDescription: .init(), - isLoading: false + isLoading: false, + isSecretWalletsEnabled: false, + secretWalletPassword: "" ) } diff --git a/Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift b/Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift index d08c0be7c..901b104c8 100644 --- a/Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift +++ b/Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift @@ -31,8 +31,15 @@ struct PKGeneratorView: View { } } -extension PKGeneratorView { - fileprivate var loadingBackground: some View { +private extension PKGeneratorView { + private var inactiveBaseColor: Color { + Color(UIColor.gray.withAlphaComponent(0.5)) + } + private var activeBaseColor: Color { + Color(UIColor.adamant.primary) + } + + var loadingBackground: some View { HStack { Spacer() @@ -60,20 +67,33 @@ extension PKGeneratorView { placeholder: .adamant.qrGenerator.passphrasePlaceholder, text: $viewModel.state.passphrase ) - - Button(action: { viewModel.generateKeys() }) { + + Toggle(isOn: $viewModel.state.isSecretWalletsEnabled) { + Text(String.adamant.qrGenerator.toggleTitle) + .foregroundColor(viewModel.state.isSecretWalletsEnabled ? activeBaseColor : inactiveBaseColor) + } + .toggleStyle(SwitchToggleStyle(tint: Color(uiColor: .adamant.active))) + + if viewModel.state.isSecretWalletsEnabled { + AdamantSecureField( + placeholder: .adamant.qrGenerator.passwordPlaceholder, + text: $viewModel.state.secretWalletPassword + ) + } + + Button(action: { viewModel.generateKeys() }, label: { Text(String.adamant.pkGenerator.generateButton) .foregroundStyle(Color(uiColor: .adamant.primary)) .padding(.horizontal, 30) .background(loadingBackground) .expanded(axes: .horizontal) - } + }) }.listRowBackground(Color(uiColor: .adamant.cellColor)) } } - - fileprivate func keyView(_ keyInfo: PKGeneratorState.KeyInfo) -> some View { - NavigationButton(action: { viewModel.onTap(key: keyInfo.key) }) { + + func keyView(_ keyInfo: PKGeneratorState.KeyInfo) -> some View { + NavigationButton(action: { viewModel.onTap(key: keyInfo.key) }, content: { HStack { Image(uiImage: keyInfo.icon) .renderingMode(.template) @@ -93,6 +113,6 @@ extension PKGeneratorView { Text(keyInfo.key).lineLimit(1) .foregroundStyle(Color(uiColor: .adamant.secondary)) } - } + }) } } diff --git a/Adamant/Modules/Settings/PKGenerator/PKGeneratorViewModel.swift b/Adamant/Modules/Settings/PKGenerator/PKGeneratorViewModel.swift index 9afe49397..c67f7d082 100644 --- a/Adamant/Modules/Settings/PKGenerator/PKGeneratorViewModel.swift +++ b/Adamant/Modules/Settings/PKGenerator/PKGeneratorViewModel.swift @@ -63,6 +63,7 @@ final class PKGeneratorViewModel: ObservableObject { func onTap(key: String) { dialogService.presentShareAlertFor( + title: nil, string: key, types: [ .copyToPasteboard, @@ -84,14 +85,16 @@ final class PKGeneratorViewModel: ObservableObject { guard !state.isLoading else { return } withAnimation { state.isLoading = true } let passphrase = state.passphrase.lowercased() - + let password = state.secretWalletPassword + Task { defer { withAnimation { state.isLoading = false } } do { let keys = try await Task.detached { [walletServiceCompose] in - try generatePrivateKeys( + try await generatePrivateKeys( passphrase: passphrase, + password: password, walletServiceCompose: walletServiceCompose ) }.value @@ -134,22 +137,43 @@ extension PKGeneratorViewModel { private func generatePrivateKeys( passphrase: String, + password: String, walletServiceCompose: WalletServiceCompose -) throws -> [PKGeneratorState.KeyInfo] { - guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) - else { throw AdamantError(message: .adamant.qrGenerator.wrongPassphraseError) } - - return walletServiceCompose.getWallets().compactMap { - guard - let generator = $0.core as? PrivateKeyGenerator, - let key = generator.generatePrivateKeyFor(passphrase: passphrase) - else { return nil } - - return PKGeneratorState.KeyInfo( - title: generator.rowTitle, - description: .adamant.pkGenerator.keyFormat(generator.keyFormat.rawValue), - icon: generator.rowImage ?? .init(), - key: key - ) +) async throws -> [PKGeneratorState.KeyInfo] { + guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) else { + throw AdamantError(message: .adamant.qrGenerator.wrongPassphraseError) + } + + let wallets = walletServiceCompose.getWallets() + + return await withTaskGroup(of: PKGeneratorState.KeyInfo?.self, returning: [PKGeneratorState.KeyInfo].self) { group in + for wallet in wallets { + group.addTask { + guard let generator = wallet.core as? PrivateKeyGenerator else { + return nil + } + + let key = await generator.generatePrivateKeyFor(passphrase: passphrase, password: password) + guard let key = key else { + return nil + } + + return PKGeneratorState.KeyInfo( + title: generator.rowTitle, + description: .adamant.pkGenerator.keyFormat(generator.keyFormat.rawValue), + icon: generator.rowImage ?? .init(), + key: key + ) + } + } + + var result: [PKGeneratorState.KeyInfo] = [] + for await keyInfo in group { + if let keyInfo = keyInfo { + result.append(keyInfo) + } + } + + return result } } diff --git a/Adamant/Modules/Settings/PrivateKeyGenerator.swift b/Adamant/Modules/Settings/PrivateKeyGenerator.swift index b57bf8bbc..f821b2a94 100644 --- a/Adamant/Modules/Settings/PrivateKeyGenerator.swift +++ b/Adamant/Modules/Settings/PrivateKeyGenerator.swift @@ -17,6 +17,6 @@ protocol PrivateKeyGenerator { var rowTitle: String { get } var rowImage: UIImage? { get } var keyFormat: KeyFormat { get } - - func generatePrivateKeyFor(passphrase: String) -> String? + + func generatePrivateKeyFor(passphrase: String, password: String) async -> String? } diff --git a/Adamant/Modules/Settings/QRGeneratorViewController.swift b/Adamant/Modules/Settings/QRGeneratorViewController.swift index 82cb6af42..01f9af379 100644 --- a/Adamant/Modules/Settings/QRGeneratorViewController.swift +++ b/Adamant/Modules/Settings/QRGeneratorViewController.swift @@ -24,6 +24,9 @@ extension String.adamant { static var passphrasePlaceholder: String { String.localized("QrGeneratorScene.Passphrase.Placeholder", comment: "QRGenerator: Passphrase textview placeholder") } + static var passwordPlaceholder: String { + String.localized("QrGeneratorScene.Password.Placeholder", comment: "QRGenerator: Password textview placeholder") + } static var wrongPassphraseError: String { String.localized("QrGeneratorScene.Error.InvalidPassphrase", comment: "QRGenerator: user typed in invalid passphrase") } @@ -33,6 +36,9 @@ extension String.adamant { comment: "QRGenerator: Bad Internal generator error message format. Using %@ for error description" ) } + static var toggleTitle: String { + String.localized("QrGeneratorScene.Toggle.Title", comment: "QRGenerator: Toggle button title") + } } } diff --git a/Adamant/Modules/Settings/SettingsFactory.swift b/Adamant/Modules/Settings/SettingsFactory.swift index 32abc40ec..6ad75dec0 100644 --- a/Adamant/Modules/Settings/SettingsFactory.swift +++ b/Adamant/Modules/Settings/SettingsFactory.swift @@ -44,7 +44,7 @@ struct SettingsFactory { VisibleWalletsViewController( visibleWalletsService: assembler.resolve(VisibleWalletsService.self)!, accountService: assembler.resolve(AccountService.self)!, - walletsStoreService: assembler.resolve(WalletStoreServiceProviderProtocol.self)! + walletsStoreService: assembler.resolve(WalletStoreServiceProviderProtocol.self)!, secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } } diff --git a/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift index 5ced16131..63274cebb 100644 --- a/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift +++ b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift @@ -54,6 +54,7 @@ final class VisibleWalletsViewController: KeyboardObservingViewController { var visibleWalletsService: VisibleWalletsService var walletsStoreService: WalletStoreServiceProviderProtocol var accountService: AccountService + var secretWalletsViewModel: SecretWalletsViewModel // MARK: - Properties @@ -75,11 +76,13 @@ final class VisibleWalletsViewController: KeyboardObservingViewController { init( visibleWalletsService: VisibleWalletsService, accountService: AccountService, - walletsStoreService: WalletStoreServiceProviderProtocol + walletsStoreService: WalletStoreServiceProviderProtocol, + secretWalletsViewModel: SecretWalletsViewModel ) { self.visibleWalletsService = visibleWalletsService self.accountService = accountService self.walletsStoreService = walletsStoreService + self.secretWalletsViewModel = secretWalletsViewModel super.init(nibName: nil, bundle: nil) } @@ -151,7 +154,7 @@ final class VisibleWalletsViewController: KeyboardObservingViewController { } private func setupView() { - navigationItem.title = String.adamant.visibleWallets.title + navigationItem.title = String.adamant.visibleWallets.title + " \(secretWalletsViewModel.getCurrentWalletEmoji())" navigationItem.searchController = searchController navigationItem.rightBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .search, target: self, action: #selector(activateSearch)) diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 5ad9a7e19..5a861b55a 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -42,7 +42,8 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { screensFactory: ScreensFactory, addressBookService: AddressBookService, walletService: WalletService, - reachabilityMonitor: ReachabilityMonitor + reachabilityMonitor: ReachabilityMonitor, + secretWalletsViewModel: SecretWalletsViewModel ) { self.accountService = accountService self.transfersProvider = transfersProvider @@ -54,7 +55,8 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { walletService: walletService, dialogService: dialogService, reachabilityMonitor: reachabilityMonitor, - screensFactory: screensFactory + screensFactory: screensFactory, + secretWalletsViewModel: secretWalletsViewModel ) } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index 17248b4fb..2860a8dc2 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -23,6 +23,7 @@ struct AdmWalletFactory: WalletFactory { accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)!, service: service ) } @@ -37,7 +38,8 @@ struct AdmWalletFactory: WalletFactory { screensFactory: screensFactory, addressBookService: assembler.resolve(AddressBookService.self)!, walletService: service, - reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)! + reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } @@ -53,7 +55,9 @@ struct AdmWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + secretWalletManager: assembler.resolve(SecretWalletsManagerProtocol.self)!, + secretWalletViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 354ff07da..5508fdfa9 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -117,6 +117,7 @@ final class AdmWalletService: NSObject, WalletCoreProtocol, WalletStaticCoreProt private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUniqueID, + coinAddress: wallet?.address ?? "", coreDataStack: coreDataStack, blockchainType: richMessageType ) @@ -225,8 +226,8 @@ final class AdmWalletService: NSObject, WalletCoreProtocol, WalletStaticCoreProt func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo { .init(sentDate: nil, status: .notInitiated) } - - func initWallet(withPassphrase: String, withPassword: String) async throws -> WalletAccount { + + func initWallet(withPassphrase: String, withPassword: String, storeInKVS: Bool) async throws -> WalletAccount { throw InternalAPIError.unknownError } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift index c9c1dfa83..a147881fc 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift @@ -12,10 +12,6 @@ import SafariServices import UIKit extension String.adamant.wallets { - static var adamant: String { - String.localized("AccountTab.Wallets.adamant_wallet", comment: "Account tab: Adamant wallet") - } - static var sendAdm: String { String.localized("AccountTab.Row.SendAdm", comment: "Account tab: 'Send ADM tokens' button") } @@ -56,6 +52,10 @@ extension String.adamant.wallets { } final class AdmWalletViewController: WalletViewControllerBase { + override var walletName: String { + String.localized("AccountTab.Wallets.adamant", comment: "Account tab: Adamant wallet") + } + // MARK: - Rows & Sections enum Rows { case stakeAdm, buyTokens, freeTokens @@ -256,6 +256,7 @@ final class AdmWalletViewController: WalletViewControllerBase { { let encodedAddress = AdamantUriTools.encode(request: AdamantUri.address(address: address, params: nil)) self?.dialogService.presentShareAlertFor( + title: self?.makeTitle(), stringForPasteboard: address, stringForShare: encodedAddress, stringForQR: encodedAddress, @@ -278,9 +279,15 @@ final class AdmWalletViewController: WalletViewControllerBase { } return addressRow } - - override func setTitle() { - walletTitleLabel.text = String.adamant.wallets.adamant + + override func makeTitle() -> String { + String.localizedStringWithFormat( + String.localized( + "SecretWallets.Coin.Regular", + comment: "Regular Wallet" + ), + walletName + ) } func updateRows() { diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift index 0884c81e3..69a6bba58 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift @@ -23,7 +23,8 @@ final class BtcTransactionsViewController: TransactionsListViewControllerBase { dialogService: DialogService, reachabilityMonitor: ReachabilityMonitor, screensFactory: ScreensFactory, - addressBook: AddressBookService + addressBook: AddressBookService, + secretWalletsViewModel: SecretWalletsViewModel ) { self.addressBook = addressBook @@ -31,7 +32,8 @@ final class BtcTransactionsViewController: TransactionsListViewControllerBase { walletService: walletService, dialogService: dialogService, reachabilityMonitor: reachabilityMonitor, - screensFactory: screensFactory + screensFactory: screensFactory, + secretWalletsViewModel: secretWalletsViewModel ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index 226fbee86..f94f15a78 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -23,6 +23,7 @@ struct BtcWalletFactory: WalletFactory { accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)!, service: service ) } @@ -33,7 +34,8 @@ struct BtcWalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, screensFactory: screensFactory, - addressBook: assembler.resolve(AddressBookService.self)! + addressBook: assembler.resolve(AddressBookService.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } @@ -49,7 +51,9 @@ struct BtcWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + secretWalletManager: assembler.resolve(SecretWalletsManagerProtocol.self)!, + secretWalletViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 068a38e76..2c84b4d70 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -202,6 +202,7 @@ final class BtcWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, @unc private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUniqueID, + coinAddress: wallet?.address ?? "", coreDataStack: coreDataStack, blockchainType: richMessageType ) @@ -451,8 +452,8 @@ extension BtcWalletService { setState(.initiationFailed(reason: reason)) btcWallet = nil } - - func initWallet(withPassphrase passphrase: String, withPassword password: String) async throws -> WalletAccount { + + func initWallet(withPassphrase passphrase: String, withPassword password: String, storeInKVS: Bool) async throws -> WalletAccount { guard let adamant = accountService.account else { throw WalletServiceError.notLogged } @@ -475,8 +476,7 @@ extension BtcWalletService { addressConverter: addressConverter ) self.btcWallet = eWallet - let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) - + NotificationCenter.default.post( name: walletUpdatedNotification, object: self, @@ -489,8 +489,18 @@ extension BtcWalletService { self.enabled = true NotificationCenter.default.post(name: self.serviceEnabledChanged, object: self) } - + + self.setState(.upToDate) + + Task { + await self.update() + self.addTransactionObserver() + } + + guard storeInKVS else { return eWallet } + // MARK: 4. Save address into KVS + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) let service = self do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) @@ -500,25 +510,14 @@ extension BtcWalletService { } throw WalletServiceError.accountNotFound } - - service.setState(.upToDate) - - Task { - service.update() - } - + return eWallet } catch let error as WalletServiceError { switch error { case .walletNotInitiated: /// The ADM Wallet is not initialized. Check the balance of the current wallet /// and save the wallet address to kvs when dropshipping ADM - service.setState(.upToDate) - - Task { - await service.update() - } - + if let kvsAddressModel { service.save(kvsAddressModel) { result in service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) @@ -528,7 +527,6 @@ extension BtcWalletService { return eWallet default: - service.setState(.upToDate) throw error } } @@ -556,8 +554,6 @@ extension BtcWalletService: SwinjectDependentService { btcTransactionFactory = container.resolve(BitcoinKitTransactionFactoryProtocol.self) vibroService = container.resolve(VibroService.self) coreDataStack = container.resolve(CoreDataStack.self) - - addTransactionObserver() } } @@ -805,11 +801,11 @@ extension BtcWalletService: PrivateKeyGenerator { } var keyFormat: KeyFormat { .WIF } - - func generatePrivateKeyFor(passphrase: String) -> String? { + + func generatePrivateKeyFor(passphrase: String, password: String = "") -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), - let privateKeyData = passphrase.data(using: .utf8)?.sha256() + let privateKeyData = makeBinarySeed(withMnemonicSentence: passphrase, withSalt: password) else { return nil } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletViewController.swift index af0cc024f..b4fb7a6bf 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletViewController.swift @@ -10,17 +10,16 @@ import CommonKit import UIKit extension String.adamant { - static var bitcoin: String { - String.localized("AccountTab.Wallets.bitcoin_wallet", comment: "Account tab: Bitcoin wallet") - } - static var sendBtc: String { String.localized("AccountTab.Row.SendBtc", comment: "Account tab: 'Send BTC tokens' button") } } final class BtcWalletViewController: WalletViewControllerBase { - + override var walletName: String { + String.localized("AccountTab.Wallets.bitcoin", comment: "Account tab: Bitcoin wallet") + } + override func sendRowLocalizedLabel() -> NSAttributedString { return NSAttributedString(string: String.adamant.sendBtc) } @@ -28,8 +27,4 @@ final class BtcWalletViewController: WalletViewControllerBase { override func encodeForQr(address: String) -> String? { return "bitcoin:\(address)" } - - override func setTitle() { - walletTitleLabel.text = String.adamant.bitcoin - } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index 0506e4b40..948aee14c 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -23,6 +23,7 @@ struct DashWalletFactory: WalletFactory { accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)!, service: service ) } @@ -32,7 +33,8 @@ struct DashWalletFactory: WalletFactory { walletService: service, dialogService: assembler.resolve(DialogService.self)!, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - screensFactory: screensFactory + screensFactory: screensFactory, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } @@ -48,7 +50,9 @@ struct DashWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + secretWalletManager: assembler.resolve(SecretWalletsManagerProtocol.self)!, + secretWalletViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index dada9cae1..94a993489 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -149,6 +149,7 @@ final class DashWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, @un private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUniqueID, + coinAddress: wallet?.address ?? "", coreDataStack: coreDataStack, blockchainType: richMessageType ) @@ -304,7 +305,7 @@ extension DashWalletService { } @MainActor - func initWallet(withPassphrase passphrase: String, withPassword password: String) async throws -> WalletAccount { + func initWallet(withPassphrase passphrase: String, withPassword password: String, storeInKVS: Bool) async throws -> WalletAccount { guard let adamant = accountService.account else { throw WalletServiceError.notLogged } @@ -329,8 +330,7 @@ extension DashWalletService { ) self.dashWallet = eWallet - let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) - + NotificationCenter.default.post( name: walletUpdatedNotification, object: self, @@ -343,8 +343,18 @@ extension DashWalletService { self.enabled = true NotificationCenter.default.post(name: self.serviceEnabledChanged, object: self) } - + + self.setState(.upToDate) + + Task { + await self.update() + self.addTransactionObserver() + } + + guard storeInKVS else { return eWallet } + // MARK: 4. Save address into KVS + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) let service = self @@ -353,12 +363,7 @@ extension DashWalletService { service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } - - service.setState(.upToDate) - - Task { - service.update() - } + return eWallet } catch let error as WalletServiceError { let service = self @@ -366,23 +371,16 @@ extension DashWalletService { case .walletNotInitiated: /// The ADM Wallet is not initialized. Check the balance of the current wallet /// and save the wallet address to kvs when dropshipping ADM - service.setState(.upToDate) - - Task { - await service.update() - } - + if let kvsAddressModel { service.save(kvsAddressModel) { result in service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } - - service.setState(.upToDate) + return eWallet default: - service.setState(.upToDate) throw error } } @@ -411,8 +409,6 @@ extension DashWalletService: SwinjectDependentService { dashApiService = container.resolve(DashApiService.self) vibroService = container.resolve(VibroService.self) coreDataStack = container.resolve(CoreDataStack.self) - - addTransactionObserver() } } @@ -628,9 +624,9 @@ extension DashWalletService: PrivateKeyGenerator { } var keyFormat: KeyFormat { .WIF } - - func generatePrivateKeyFor(passphrase: String) -> String? { - guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), let privateKeyData = passphrase.data(using: .utf8)?.sha256() else { + + func generatePrivateKeyFor(passphrase: String, password: String) -> String? { + guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), let privateKeyData = makeBinarySeed(withMnemonicSentence: passphrase, withSalt: password) else { return nil } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletViewController.swift b/Adamant/Modules/Wallets/Dash/DashWalletViewController.swift index bc898a821..006ee0544 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletViewController.swift @@ -11,16 +11,16 @@ import Foundation import UIKit extension String.adamant { - static var dash: String { - String.localized("AccountTab.Wallets.dash_wallet", comment: "Account tab: Dash wallet") - } - static var sendDash: String { String.localized("AccountTab.Row.SendDash", comment: "Account tab: 'Send Dash tokens' button") } } final class DashWalletViewController: WalletViewControllerBase { + override var walletName: String { + String.localized("AccountTab.Wallets.dash", comment: "Account tab: Dash wallet") + } + override func sendRowLocalizedLabel() -> NSAttributedString { return NSAttributedString(string: String.adamant.sendDash) } @@ -28,8 +28,4 @@ final class DashWalletViewController: WalletViewControllerBase { override func encodeForQr(address: String) -> String? { return "dash:\(address)" } - - override func setTitle() { - walletTitleLabel.text = String.adamant.dash - } } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index 3effd2316..968a08b41 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -23,6 +23,7 @@ struct DogeWalletFactory: WalletFactory { accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)!, service: service ) } @@ -32,7 +33,8 @@ struct DogeWalletFactory: WalletFactory { walletService: service, dialogService: assembler.resolve(DialogService.self)!, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - screensFactory: screensFactory + screensFactory: screensFactory, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } @@ -48,7 +50,9 @@ struct DogeWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + secretWalletManager: assembler.resolve(SecretWalletsManagerProtocol.self)!, + secretWalletViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 6b34a78ca..f7f48054d 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -170,6 +170,7 @@ final class DogeWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, @un private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUniqueID, + coinAddress: wallet?.address ?? "", coreDataStack: coreDataStack, blockchainType: richMessageType ) @@ -322,8 +323,8 @@ extension DogeWalletService { setState(.initiationFailed(reason: reason)) dogeWallet = nil } - - func initWallet(withPassphrase passphrase: String, withPassword password: String) async throws -> WalletAccount { + + func initWallet(withPassphrase passphrase: String, withPassword password: String, storeInKVS: Bool) async throws -> WalletAccount { guard let adamant = accountService.account else { throw WalletServiceError.notLogged } @@ -347,8 +348,7 @@ extension DogeWalletService { addressConverter: addressConverter ) self.dogeWallet = eWallet - let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) - + NotificationCenter.default.post( name: walletUpdatedNotification, object: self, @@ -361,8 +361,18 @@ extension DogeWalletService { self.enabled = true NotificationCenter.default.post(name: self.serviceEnabledChanged, object: self) } - + + self.setState(.upToDate) + + Task { + await self.update() + self.addTransactionObserver() + } + + guard storeInKVS else { return eWallet } + // MARK: 4. Save address into KVS + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) let service = self do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) @@ -371,36 +381,23 @@ extension DogeWalletService { service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } - - service.setState(.upToDate) - - Task { - await service.update() - } - + return eWallet } catch let error as WalletServiceError { switch error { case .walletNotInitiated: /// The ADM Wallet is not initialized. Check the balance of the current wallet /// and save the wallet address to kvs when dropshipping ADM - service.setState(.upToDate) - - Task { - await service.update() - } - + if let kvsAddressModel { service.save(kvsAddressModel) { result in service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } - - service.setState(.upToDate) + return eWallet default: - service.setState(.upToDate) throw error } } @@ -428,8 +425,6 @@ extension DogeWalletService: SwinjectDependentService { vibroService = container.resolve(VibroService.self) coreDataStack = container.resolve(CoreDataStack.self) chatsProvider = container.resolve(ChatsProvider.self) - - addTransactionObserver() } } @@ -747,9 +742,9 @@ extension DogeWalletService: PrivateKeyGenerator { } var keyFormat: KeyFormat { .WIF } - - func generatePrivateKeyFor(passphrase: String) -> String? { - guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), let privateKeyData = passphrase.data(using: .utf8)?.sha256() else { + + func generatePrivateKeyFor(passphrase: String, password: String) -> String? { + guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), let privateKeyData = makeBinarySeed(withMnemonicSentence: passphrase, withSalt: password) else { return nil } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletViewController.swift b/Adamant/Modules/Wallets/Doge/DogeWalletViewController.swift index 244999e0e..aa9e92bef 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletViewController.swift @@ -10,16 +10,16 @@ import CommonKit import UIKit extension String.adamant { - static var doge: String { - String.localized("AccountTab.Wallets.doge_wallet", comment: "Account tab: Doge wallet") - } - static var sendDoge: String { String.localized("AccountTab.Row.SendDoge", comment: "Account tab: 'Send DOGE tokens' button") } } final class DogeWalletViewController: WalletViewControllerBase { + override var walletName: String { + String.localized("AccountTab.Wallets.doge", comment: "Account tab: Doge wallet") + } + override func sendRowLocalizedLabel() -> NSAttributedString { return NSAttributedString(string: String.adamant.sendDoge) } @@ -27,8 +27,4 @@ final class DogeWalletViewController: WalletViewControllerBase { override func encodeForQr(address: String) -> String? { return "doge:\(address)" } - - override func setTitle() { - walletTitleLabel.text = String.adamant.doge - } } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index a8ba4b4f7..16c23e258 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -23,6 +23,7 @@ struct ERC20WalletFactory: WalletFactory { accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)!, service: service ) } @@ -32,7 +33,8 @@ struct ERC20WalletFactory: WalletFactory { walletService: service, dialogService: assembler.resolve(DialogService.self)!, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - screensFactory: screensFactory + screensFactory: screensFactory, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } @@ -48,7 +50,9 @@ struct ERC20WalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + secretWalletManager: assembler.resolve(SecretWalletsManagerProtocol.self)!, + secretWalletViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 19cdc4d34..14b95494e 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -200,6 +200,7 @@ final class ERC20WalletService: WalletCoreProtocol, ERC20GasAlgorithmComputable, private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUniqueID, + coinAddress: wallet?.address ?? "", coreDataStack: coreDataStack, blockchainType: dynamicRichMessageType ) @@ -392,8 +393,8 @@ final class ERC20WalletService: WalletCoreProtocol, ERC20GasAlgorithmComputable, // MARK: - WalletInitiatedWithPassphrase extension ERC20WalletService { - func initWallet(withPassphrase passphrase: String, withPassword password: String) async throws -> WalletAccount { - + func initWallet(withPassphrase passphrase: String, withPassword password: String, storeInKVS: Bool) async throws -> WalletAccount { + // MARK: 1. Prepare setState(.notInitiated) @@ -402,7 +403,7 @@ extension ERC20WalletService { NotificationCenter.default.post(name: serviceEnabledChanged, object: self) } - let keystore = try await ethBIP32Service.keyStore(passphrase: passphrase) + let keystore = try await ethBIP32Service.keyStore(passphrase: passphrase, withPassword: password) guard let ethAddress = keystore.addresses?.first else { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: nil) @@ -432,7 +433,8 @@ extension ERC20WalletService { self.setState(.upToDate, silent: true) Task { - await update() + await self.update() + self.addTransactionObserver() } return eWallet } @@ -455,8 +457,6 @@ extension ERC20WalletService: SwinjectDependentService { vibroService = container.resolve(VibroService.self) coreDataStack = container.resolve(CoreDataStack.self) ethBIP32Service = container.resolve(EthBIP32ServiceProtocol.self) - - addTransactionObserver() } } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift index af3546960..40d581ff9 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift @@ -12,10 +12,6 @@ import UIKit extension String.adamant.wallets { enum erc20 { - static func tokenWallet(_ token: String) -> String { - return String(format: .localized("AccountTab.Wallets.erc20_wallet", comment: "Account tab: Ethereum wallet"), token) - } - static func sendToken(_ token: String) -> String { return String(format: .localized("AccountTab.Row.SendToken", comment: "Account tab: 'Send ERC20 tokens' button"), token) } @@ -23,6 +19,11 @@ extension String.adamant.wallets { } final class ERC20WalletViewController: WalletViewControllerBase { + override var walletName: String { + guard let tokenName = service?.core.tokenName else { return "" } + return String(format: .localized("AccountTab.Wallets.erc20", comment: "Account tab: Ethereum wallet"), tokenName) + } + override func sendRowLocalizedLabel() -> NSAttributedString { let networkSymbol = ERC20WalletService.tokenNetworkSymbol let tokenSymbol = String.adamant.wallets.erc20.sendToken(service?.core.tokenSymbol ?? "") @@ -48,8 +49,4 @@ final class ERC20WalletViewController: WalletViewControllerBase { override func encodeForQr(address: String) -> String? { return "ethereum:\(address)" } - - override func setTitle() { - walletTitleLabel.text = String.adamant.wallets.erc20.tokenWallet(service?.core.tokenName ?? "") - } } diff --git a/Adamant/Modules/Wallets/EthBIP32Service/EthBIP32Service.swift b/Adamant/Modules/Wallets/EthBIP32Service/EthBIP32Service.swift index 0973a6ad4..be18ee5ac 100644 --- a/Adamant/Modules/Wallets/EthBIP32Service/EthBIP32Service.swift +++ b/Adamant/Modules/Wallets/EthBIP32Service/EthBIP32Service.swift @@ -8,37 +8,31 @@ import Web3Core protocol EthBIP32ServiceProtocol { - func keyStore(passphrase: String) async throws -> BIP32Keystore + func keyStore(passphrase: String, withPassword password: String) async throws -> BIP32Keystore } actor EthBIP32Service: EthBIP32ServiceProtocol { - private var passphrase: String? - private var keystore: BIP32Keystore? - private var ethApiService: EthApiServiceProtocol - + private var keystores: [String: BIP32Keystore] = [:] + init(ethApiService: EthApiServiceProtocol) { self.ethApiService = ethApiService } - func keyStore(passphrase: String) async throws -> BIP32Keystore { - if let keystore = self.keystore, passphrase == self.passphrase { + + func keyStore(passphrase: String, withPassword password: String) async throws -> BIP32Keystore { + if let keystore = self.keystores[passphrase + password] { return keystore } do { - guard - let store = try BIP32Keystore( - mnemonics: passphrase, - password: EthWalletService.walletPassword, - mnemonicsPassword: "", - language: .english, - prefixPath: EthWalletService.walletPath - ) - else { + guard let store = try BIP32Keystore(mnemonics: passphrase, + password: EthWalletService.walletPassword, + mnemonicsPassword: password, + language: .english, + prefixPath: EthWalletService.walletPath) else { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: nil) } - self.passphrase = passphrase - self.keystore = store await ethApiService.setKeystoreManager(.init([store])) + keystores[passphrase + password] = store return store } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index d8381f1bd..e95b8b2a0 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -23,6 +23,7 @@ struct EthWalletFactory: WalletFactory { accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)!, service: service ) } @@ -32,7 +33,8 @@ struct EthWalletFactory: WalletFactory { walletService: service, dialogService: assembler.resolve(DialogService.self)!, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - screensFactory: screensFactory + screensFactory: screensFactory, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } @@ -48,7 +50,9 @@ struct EthWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + secretWalletManager: assembler.resolve(SecretWalletsManagerProtocol.self)!, + secretWalletViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 3b26d6fdc..c14f92427 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -55,13 +55,13 @@ extension Web3Error { return .internalError(message: message, error: nil) case .transactionSerializationError, - .dataError, - .walletError, - .unknownError, - .rpcError, - .revert, - .revertCustom, - .typeError: + .dataError, + .walletError, + .unknownError, + .rpcError, + .revert, + .revertCustom, + .typeError: return .internalError(message: "Unknown error", error: nil) case .valueError(let desc): return .internalError(message: "Unknown error \(String(describing: desc))", error: nil) @@ -180,7 +180,7 @@ final class EthWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, ERC2 @ObservableValue private(set) var historyTransactions: [TransactionDetails] = [] @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - + var transactionsPublisher: AnyObservable<[TransactionDetails]> { $historyTransactions.eraseToAnyPublisher() } @@ -201,6 +201,7 @@ final class EthWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, ERC2 private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUniqueID, + coinAddress: wallet?.address ?? "", coreDataStack: coreDataStack, blockchainType: richMessageType ) @@ -415,7 +416,7 @@ final class EthWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, ERC2 // MARK: - WalletInitiatedWithPassphrase extension EthWalletService { - func initWallet(withPassphrase passphrase: String, withPassword password: String) async throws -> WalletAccount { + func initWallet(withPassphrase passphrase: String, withPassword password: String, storeInKVS: Bool) async throws -> WalletAccount { guard let adamant = accountService?.account else { throw WalletServiceError.notLogged } @@ -429,8 +430,8 @@ extension EthWalletService { } // MARK: 2. Create keys and addresses - - let store = try await ethBIP32Service.keyStore(passphrase: passphrase) + + let store = try await ethBIP32Service.keyStore(passphrase: passphrase, withPassword: password) walletStorage = .init(keystore: store, unicId: tokenUniqueID) let eWallet = walletStorage?.getWallet() @@ -441,8 +442,7 @@ extension EthWalletService { // MARK: 3. Update ethWallet = eWallet - let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) - + NotificationCenter.default.post( name: walletUpdatedNotification, object: self, @@ -455,8 +455,18 @@ extension EthWalletService { enabled = true NotificationCenter.default.post(name: serviceEnabledChanged, object: self) } - + + self.setState(.upToDate) + + Task { + await self.update() + self.addTransactionObserver() + } + + guard storeInKVS else { return eWallet } + // MARK: 4. Save into KVS + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) let service = self do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) @@ -465,24 +475,13 @@ extension EthWalletService { service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } - - service.setState(.upToDate) - - Task { - await service.update() - } - + return eWallet } catch let error as WalletServiceError { switch error { case .walletNotInitiated: /// The ADM Wallet is not initialized. Check the balance of the current wallet /// and save the wallet address to kvs when dropshipping ADM - service.setState(.upToDate) - - Task { - await service.update() - } if let kvsAddressModel { service.save(kvsAddressModel) { result in @@ -493,7 +492,6 @@ extension EthWalletService { return eWallet default: - service.setState(.upToDate) throw error } } @@ -555,8 +553,6 @@ extension EthWalletService: SwinjectDependentService { vibroService = container.resolve(VibroService.self) coreDataStack = container.resolve(CoreDataStack.self) ethBIP32Service = container.resolve(EthBIP32ServiceProtocol.self) - - addTransactionObserver() } } @@ -855,22 +851,14 @@ extension EthWalletService: PrivateKeyGenerator { var keyFormat: KeyFormat { .HEX } - func generatePrivateKeyFor(passphrase: String) -> String? { + func generatePrivateKeyFor(passphrase: String, password: String) async -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) else { return nil } - - guard - let keystore = try? BIP32Keystore( - mnemonics: passphrase, - password: EthWalletService.walletPassword, - mnemonicsPassword: "", - language: .english, - prefixPath: EthWalletService.walletPath - ), - let account = keystore.addresses?.first, - let privateKeyData = try? keystore.UNSAFE_getPrivateKeyData(password: EthWalletService.walletPassword, account: account) - else { + + guard let keystore = try? await ethBIP32Service.keyStore(passphrase: passphrase, withPassword: password), + let account = keystore.addresses?.first, + let privateKeyData = try? keystore.UNSAFE_getPrivateKeyData(password: EthWalletService.walletPassword, account: account) else { return nil } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletViewController.swift index c345010ff..3556c04e8 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletViewController.swift @@ -10,16 +10,15 @@ import CommonKit import UIKit extension String.adamant.wallets { - static var ethereum: String { - String.localized("AccountTab.Wallets.ethereum_wallet", comment: "Account tab: Ethereum wallet") - } - static var sendEth: String { String.localized("AccountTab.Row.SendEth", comment: "Account tab: 'Send ETH tokens' button") } } final class EthWalletViewController: WalletViewControllerBase { + override var walletName: String { + String.localized("AccountTab.Wallets.ethereum", comment: "Account tab: Ethereum wallet") + } override func sendRowLocalizedLabel() -> NSAttributedString { return NSAttributedString(string: String.adamant.wallets.sendEth) } @@ -27,8 +26,4 @@ final class EthWalletViewController: WalletViewControllerBase { override func encodeForQr(address: String) -> String? { return "ethereum:\(address)" } - - override func setTitle() { - walletTitleLabel.text = String.adamant.wallets.ethereum - } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift index 80a98c0c1..937c5dd92 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift @@ -24,6 +24,7 @@ struct KlyWalletFactory: WalletFactory { accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)!, service: service ) } @@ -33,7 +34,8 @@ struct KlyWalletFactory: WalletFactory { walletService: service, dialogService: assembler.resolve(DialogService.self)!, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - screensFactory: screensFactory + screensFactory: screensFactory, + secretWalletsViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } @@ -49,7 +51,9 @@ struct KlyWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + secretWalletManager: assembler.resolve(SecretWalletsManagerProtocol.self)!, + secretWalletViewModel: assembler.resolve(SecretWalletsViewModel.self)! ) } diff --git a/Adamant/Modules/Wallets/Klayr/KlyWalletViewController.swift b/Adamant/Modules/Wallets/Klayr/KlyWalletViewController.swift index 83026364f..9ecfb634d 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWalletViewController.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWalletViewController.swift @@ -10,16 +10,16 @@ import CommonKit import UIKit extension String.adamant { - static var kly: String { - String.localized("AccountTab.Wallets.kly_wallet", comment: "Account tab: Klayr wallet") - } - static var sendKly: String { String.localized("AccountTab.Row.SendKly", comment: "Account tab: 'Send KLY tokens' button") } } final class KlyWalletViewController: WalletViewControllerBase { + override var walletName: String { + String.localized("AccountTab.Wallets.kly", comment: "Account tab: Klayr wallet") + } + override func sendRowLocalizedLabel() -> NSAttributedString { return NSAttributedString(string: String.adamant.sendKly) } @@ -27,8 +27,4 @@ final class KlyWalletViewController: WalletViewControllerBase { override func encodeForQr(address: String) -> String? { return "klayr:\(address)" } - - override func setTitle() { - walletTitleLabel.text = String.adamant.kly - } } diff --git a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift index c064b1f51..2c2d70a41 100644 --- a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift +++ b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift @@ -78,13 +78,13 @@ extension KlyWalletService: PrivateKeyGenerator { } var keyFormat: KeyFormat { .HEX } - - func generatePrivateKeyFor(passphrase: String) -> String? { + + func generatePrivateKeyFor(passphrase: String, password: String) -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase), let keypair = try? LiskKit.Crypto.keyPair( fromPassphrase: passphrase, - salt: salt - ) + salt: password.isEmpty ? salt : "mnemonic\(password)" + ) else { return nil } diff --git a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift index 47091c841..7ba5a6f24 100644 --- a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift +++ b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift @@ -81,6 +81,7 @@ final class KlyWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, @unc private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUniqueID, + coinAddress: wallet?.address ?? "", coreDataStack: coreDataStack, blockchainType: richMessageType ) @@ -108,10 +109,9 @@ final class KlyWalletService: WalletCoreProtocol, WalletStaticCoreProtocol, @unc // MARK: - func initWallet( - withPassphrase passphrase: String, - withPassword password: String + withPassphrase passphrase: String, withPassword password: String, storeInKVS: Bool ) async throws -> WalletAccount { - try await initWallet(passphrase: passphrase, password: password) + try await initWallet(passphrase: passphrase, password: password, storeInKVS: storeInKVS) } func setInitiationFailed(reason: String) { @@ -203,8 +203,6 @@ extension KlyWalletService: SwinjectDependentService { klyNodeApiService = container.resolve(KlyNodeApiService.self) vibroService = container.resolve(VibroService.self) coreDataStack = container.resolve(CoreDataStack.self) - - addTransactionObserver() } func addTransactionObserver() { @@ -427,8 +425,8 @@ extension KlyWalletService { } // MARK: - Init Wallet -extension KlyWalletService { - fileprivate func initWallet(passphrase: String, password: String) async throws -> WalletAccount { +private extension KlyWalletService { + func initWallet(passphrase: String, password: String, storeInKVS: Bool) async throws -> WalletAccount { guard let adamant = accountService.account else { throw WalletServiceError.notLogged } @@ -479,7 +477,16 @@ extension KlyWalletService { else { throw WalletServiceError.accountNotFound } - + + setState(.upToDate) + + Task { + await self.update() + self.addTransactionObserver() + } + + guard storeInKVS else { return eWallet } + // Save into KVS do { @@ -488,30 +495,18 @@ extension KlyWalletService { if address != eWallet.address { updateKvsAddress(kvsAddressModel) } - - setState(.upToDate) - - Task { - await update() - } - + return eWallet } catch let error as WalletServiceError { switch error { case .walletNotInitiated: /// The ADM Wallet is not initialized. Check the balance of the current wallet /// and save the wallet address to kvs when dropshipping ADM - setState(.upToDate) - - Task { - await update() - } - + updateKvsAddress(kvsAddressModel) return eWallet default: - setState(.upToDate) throw error } } diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index 63ffccf0f..298c63a92 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -1061,7 +1061,7 @@ class TransactionDetailsViewControllerBase: FormViewController { // MARK: - Tools func shareValue(_ value: String, from: UIView) { - dialogService.presentShareAlertFor(string: value, types: [.copyToPasteboard, .share], excludedActivityTypes: nil, animated: true, from: from) { + dialogService.presentShareAlertFor(title: nil, string: value, types: [.copyToPasteboard, .share], excludedActivityTypes: nil, animated: true, from: from) { [weak self] in guard let tableView = self?.tableView else { return diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index 0d9269e68..059754bf8 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -55,7 +55,8 @@ class TransactionsListViewControllerBase: UIViewController { let dialogService: DialogService let reachabilityMonitor: ReachabilityMonitor let screensFactory: ScreensFactory - + let secretWalletsViewModel: SecretWalletsViewModel + // MARK: - Proprieties var taskManager = TaskManager() @@ -94,13 +95,15 @@ class TransactionsListViewControllerBase: UIViewController { walletService: WalletService, dialogService: DialogService, reachabilityMonitor: ReachabilityMonitor, - screensFactory: ScreensFactory + screensFactory: ScreensFactory, + secretWalletsViewModel: SecretWalletsViewModel ) { self.walletService = walletService self.dialogService = dialogService self.reachabilityMonitor = reachabilityMonitor self.screensFactory = screensFactory - + self.secretWalletsViewModel = secretWalletsViewModel + super.init(nibName: String(describing: TransactionsListViewControllerBase.self), bundle: nil) } @@ -114,7 +117,7 @@ class TransactionsListViewControllerBase: UIViewController { super.viewDidLoad() navigationItem.largeTitleDisplayMode = .never - navigationItem.title = String.adamant.transactionList.title + navigationItem.title = String.adamant.transactionList.title + " " + secretWalletsViewModel.getCurrentWalletEmoji() emptyLabel.text = String.adamant.transactionList.noTransactionYet update(walletService.core.getLocalTransactionHistory()) diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index cd9242bf1..c0d8bb6da 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -99,6 +99,8 @@ class TransferViewControllerBase: FormViewController { // MARK: - Rows enum BaseRows { + case senderAddress + case type case balance case amount case fiat @@ -114,6 +116,8 @@ class TransferViewControllerBase: FormViewController { var tag: String { switch self { + case .senderAddress: return "senderAddress" + case .type: return "type" case .balance: return "balance" case .amount: return "amount" case .fiat: return "fiat" @@ -131,6 +135,8 @@ class TransferViewControllerBase: FormViewController { var localized: String { switch self { + case .senderAddress: return .localized("TransferScene.Row.SenderAddress", comment: "Transfer: sender address") + case .type: return .localized("TransferScene.Row.Type", comment: "Transfer: transaction type") case .balance: return .localized("TransferScene.Row.Balance", comment: "Transfer: logged user balance.") case .amount: return .localized("TransferScene.Row.Amount", comment: "Transfer: amount of adamant to transfer.") case .fiat: return .localized("TransferScene.Row.Fiat", comment: "Transfer: fiat value of crypto-amout") @@ -207,7 +213,9 @@ class TransferViewControllerBase: FormViewController { let walletCore: WalletCoreProtocol let reachabilityMonitor: ReachabilityMonitor let apiServiceCompose: ApiServiceComposeProtocol - + let secretWalletManager: SecretWalletsManagerProtocol + let secretWalletViewModel: SecretWalletsViewModel + // MARK: - Properties private var previousIsReadyToSend: Bool? @@ -339,7 +347,9 @@ class TransferViewControllerBase: FormViewController { vibroService: VibroService, walletService: WalletService, reachabilityMonitor: ReachabilityMonitor, - apiServiceCompose: ApiServiceComposeProtocol + apiServiceCompose: ApiServiceComposeProtocol, + secretWalletManager: SecretWalletsManagerProtocol, + secretWalletViewModel: SecretWalletsViewModel ) { self.accountService = accountService self.accountsProvider = accountsProvider @@ -353,6 +363,8 @@ class TransferViewControllerBase: FormViewController { self.walletCore = walletService.core self.reachabilityMonitor = reachabilityMonitor self.apiServiceCompose = apiServiceCompose + self.secretWalletManager = secretWalletManager + self.secretWalletViewModel = secretWalletViewModel super.init(style: .insetGrouped) } @@ -521,7 +533,9 @@ class TransferViewControllerBase: FormViewController { let section = Section(Sections.wallet.localized) { $0.tag = Sections.wallet.tag } - + + section.append(defaultRowFor(baseRow: BaseRows.senderAddress)) + section.append(defaultRowFor(baseRow: BaseRows.type)) section.append(defaultRowFor(baseRow: BaseRows.balance)) section.append(defaultRowFor(baseRow: BaseRows.maxToTransfer)) @@ -1050,6 +1064,23 @@ class TransferViewControllerBase: FormViewController { extension TransferViewControllerBase { func defaultRowFor(baseRow: BaseRows) -> BaseRow { switch baseRow { + case .senderAddress: + return LabelRow { [weak self] in + $0.title = BaseRows.senderAddress.localized + $0.tag = BaseRows.senderAddress.tag + $0.disabled = true + $0.cell.detailTextLabel?.lineBreakMode = .byTruncatingMiddle + $0.value = self?.walletCore.wallet?.address + } + case .type: + return LabelRow { + $0.title = BaseRows.type.localized + $0.tag = BaseRows.type.tag + $0.disabled = true + + $0.value = secretWalletViewModel.getNameFor(walletCore: walletCore, regularWithEmoji: false) + } + case .balance: return SafeDecimalRow { [weak self] in $0.title = BaseRows.balance.localized @@ -1295,7 +1326,7 @@ extension TransferViewControllerBase { return } - dialogService.presentShareAlertFor(string: value, types: [.copyToPasteboard, .share], excludedActivityTypes: nil, animated: true, from: from) { + dialogService.presentShareAlertFor(title: nil, string: value, types: [.copyToPasteboard, .share], excludedActivityTypes: nil, animated: true, from: from) { [weak self] in guard let tableView = self?.tableView else { return } diff --git a/Adamant/Modules/Wallets/WalletViewControllerBase.swift b/Adamant/Modules/Wallets/WalletViewControllerBase.swift index e59593496..417b167bb 100644 --- a/Adamant/Modules/Wallets/WalletViewControllerBase.swift +++ b/Adamant/Modules/Wallets/WalletViewControllerBase.swift @@ -23,6 +23,10 @@ protocol WalletViewControllerDelegate: AnyObject { } class WalletViewControllerBase: FormViewController, WalletViewController { + var walletName: String { + fatalError("Should be overridden") + } + // MARK: - Rows enum BaseRows { case address, balance, send @@ -54,6 +58,7 @@ class WalletViewControllerBase: FormViewController, WalletViewController { let dialogService: DialogService let screensFactory: ScreensFactory + let secretWalletsViewModel: SecretWalletsViewModel var service: WalletService? // MARK: - Properties, WalletViewController @@ -89,6 +94,7 @@ class WalletViewControllerBase: FormViewController, WalletViewController { accountService: AccountService, screensFactory: ScreensFactory, walletServiceCompose: WalletServiceCompose, + secretWalletsViewModel: SecretWalletsViewModel, service: WalletService? ) { self.dialogService = dialogService @@ -96,6 +102,7 @@ class WalletViewControllerBase: FormViewController, WalletViewController { self.accountService = accountService self.screensFactory = screensFactory self.walletServiceCompose = walletServiceCompose + self.secretWalletsViewModel = secretWalletsViewModel self.service = service super.init(nibName: "WalletViewControllerBase", bundle: nil) } @@ -313,6 +320,7 @@ class WalletViewControllerBase: FormViewController, WalletViewController { } self?.dialogService.presentShareAlertFor( + title: self?.makeTitle(), string: address, types: types, excludedActivityTypes: ShareContentType.address.excludedActivityTypes, @@ -324,9 +332,16 @@ class WalletViewControllerBase: FormViewController, WalletViewController { } return addressRow } - - func setTitle() {} - + + func makeTitle() -> String { + guard service != nil else { return "" } + return secretWalletsViewModel.getCurrentWalletCoinName(withCoinName: walletName) + } + + func setTitle() { + walletTitleLabel.text = makeTitle() + } + // MARK: - Other private var currentUiState: WalletServiceState = .upToDate diff --git a/Adamant/Modules/Wallets/WalletViewControllerBase.xib b/Adamant/Modules/Wallets/WalletViewControllerBase.xib index e96b04ae1..110d01984 100644 --- a/Adamant/Modules/Wallets/WalletViewControllerBase.xib +++ b/Adamant/Modules/Wallets/WalletViewControllerBase.xib @@ -1,9 +1,9 @@ - + - + @@ -31,10 +31,10 @@ - + - + diff --git a/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift b/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift index 5bdc254cd..e8e8fc8d3 100644 --- a/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift +++ b/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift @@ -313,7 +313,7 @@ protocol WalletCoreProtocol: AnyObject, Sendable { func updateStatus(for id: String, status: TransactionStatus?) func isExist(address: String) async throws -> Bool func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo - func initWallet(withPassphrase: String, withPassword: String) async throws -> WalletAccount + func initWallet(withPassphrase: String, withPassword: String, storeInKVS: Bool) async throws -> WalletAccount func setInitiationFailed(reason: String) func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString func getFee(comment: String) -> Decimal diff --git a/Adamant/ServiceProtocols/AccountService.swift b/Adamant/ServiceProtocols/AccountService.swift index 91c18244f..acd625e70 100644 --- a/Adamant/ServiceProtocols/AccountService.swift +++ b/Adamant/ServiceProtocols/AccountService.swift @@ -201,4 +201,7 @@ protocol AccountService: AnyObject, Sendable { /// Update use TouchID or FaceID to log in func updateUseBiometry(_ newValue: Bool) + + /// Get current passphrase + func getCurrentPassphrase() -> String? } diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift index b08659fc0..c029411f1 100644 --- a/Adamant/ServiceProtocols/DialogService.swift +++ b/Adamant/ServiceProtocols/DialogService.swift @@ -210,6 +210,7 @@ protocol DialogService: AnyObject { didSelect: ((AddressChatShareType) -> Void)? ) func presentShareAlertFor( + title: String?, string: String, types: [ShareType], excludedActivityTypes: [UIActivity.ActivityType]?, @@ -218,6 +219,7 @@ protocol DialogService: AnyObject { completion: (() -> Void)? ) func presentShareAlertFor( + title: String?, stringForPasteboard: String, stringForShare: String, stringForQR: String, diff --git a/Adamant/ServiceProtocols/SecretWalletsManagerProtocol.swift b/Adamant/ServiceProtocols/SecretWalletsManagerProtocol.swift index a2f395400..036984457 100644 --- a/Adamant/ServiceProtocols/SecretWalletsManagerProtocol.swift +++ b/Adamant/ServiceProtocols/SecretWalletsManagerProtocol.swift @@ -9,18 +9,20 @@ import CommonKit protocol SecretWalletsManagerProtocol { - var statePublisher: AnyObservable { get } - + var statePublisher: ObservableSender { get } + func createSecretWallet(withPassword password: String) func removeSecretWallet(at index: Int) -> WalletStoreServiceProtocol? func getCurrentWallet() -> WalletStoreServiceProtocol + func getRegularWallet() -> WalletStoreServiceProtocol func getSecretWallets() -> [WalletStoreServiceProtocol] func activateSecretWallet(at index: Int) func activateDefaultWallet() + func removeAllSecretWallets() } protocol SecretWalletsManagerStateProtocol { var currentWallet: WalletStoreServiceProtocol { get set } - var defaultWallet: WalletStoreServiceProtocol { get } + var regularWallet: WalletStoreServiceProtocol { get set } var secretWallets: [WalletStoreServiceProtocol] { get set } } diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index d96c06406..0d72aba3c 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -138,7 +138,11 @@ extension AdamantAccountService { private func getSavedPassphrase() -> String? { return SecureStore.get(.passphrase) } - + + func getCurrentPassphrase() -> String? { + passphrase + } + func dropSavedAccount() { useBiometry = false isBalanceExpired = true @@ -335,7 +339,15 @@ extension AdamantAccountService { self.passphrase = passphrase _ = await initWallets() - + + let userInfo = [AdamantUserInfoKey.AccountService.loggedAccountAddress: account.address] + + NotificationCenter.default.post( + name: Notification.Name.AdamantAccountService.userLoggedIn, + object: self, + userInfo: userInfo + ) + return .success(account: account, alert: nil) } @@ -406,15 +418,7 @@ extension AdamantAccountService { self.account = account self.keypair = keypair markBalanceAsFresh() - - let userInfo = [AdamantUserInfoKey.AccountService.loggedAccountAddress: account.address] - - NotificationCenter.default.post( - name: Notification.Name.AdamantAccountService.userLoggedIn, - object: self, - userInfo: userInfo - ) - + self.state = .loggedIn return account } catch let error as ApiServiceError { @@ -447,7 +451,8 @@ extension AdamantAccountService { group.addTask { let result = try? await wallet.core.initWallet( withPassphrase: passphrase, - withPassword: .empty + withPassword: .empty, + storeInKVS: true ) return result } diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift index 060edc89a..ddbfd8304 100644 --- a/Adamant/Services/AdamantCoinStorageService.swift +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -17,20 +17,22 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { private let blockchainType: String private let coinId: String + private let coinAddress: String private let coreDataStack: CoreDataStack private lazy var transactionController = getTransactionController() private var subscriptions = Set() @ObservableValue private var transactions: [TransactionDetails] = [] - + var transactionsPublisher: any Observable<[TransactionDetails]> { $transactions } // MARK: Init - init(coinId: String, coreDataStack: CoreDataStack, blockchainType: String) { + init(coinId: String, coinAddress: String, coreDataStack: CoreDataStack, blockchainType: String) { self.coinId = coinId + self.coinAddress = coinAddress self.coreDataStack = coreDataStack self.blockchainType = blockchainType super.init() @@ -67,6 +69,7 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { coinTransaction.senderId = transaction.senderAddress coinTransaction.isOutgoing = transaction.isOutgoing coinTransaction.coinId = coinId + coinTransaction.uniqueId = coinId + coinAddress coinTransaction.transactionId = transaction.txId coinTransaction.transactionStatus = transaction.transactionStatus coinTransaction.blockchainType = blockchainType @@ -106,25 +109,50 @@ extension AdamantCoinStorageService { ) .sink { [weak self] notification in let changes = notification.managedObjectContextChanges(of: CoinTransaction.self) - + + guard let self else { + return + } + + let uniqueId = self.coinId + self.coinAddress + if let inserted = changes.inserted, !inserted.isEmpty { let filteredInserted: [TransactionDetails] = inserted.filter { - $0.coinId == self?.coinId + $0.uniqueId == uniqueId } - self?.transactions.append(contentsOf: filteredInserted) + self.transactions.append(contentsOf: filteredInserted) } if let updated = changes.updated, !updated.isEmpty { - let filteredUpdated = updated.filter { $0.coinId == self?.coinId } - + let filteredUpdated = updated.filter { $0.uniqueId == uniqueId } + filteredUpdated.forEach { coinTransaction in - guard - let index = self?.transactions.firstIndex(where: { - $0.txId == coinTransaction.txId - }) + guard let index = self.transactions.firstIndex(where: { + $0.txId == coinTransaction.txId + }) else { return } - - self?.transactions[index] = coinTransaction + + /* + Workaround to correctly set isOutgoing when multiple transactions share the same txId across different accounts. + + This situation can happen when sending funds from a regular account to a secret one or vica versa — technically is the same transaction, but it should show different isOutgoing for sender and reciever. + + Currently, there's no reliable way to assign a truly unique ID to each TransactionDetails instance, so this workaround helps distinguish them for now. + PR - https://github.com/Adamant-im/adamant-iOS/pull/741 + */ + self.transactions[index] = SimpleTransactionDetails( + defaultCurrencySymbol: coinTransaction.defaultCurrencySymbol, + txId: coinTransaction.txId, + senderAddress: coinTransaction.senderAddress, + recipientAddress: coinTransaction.recipientAddress, + dateValue: coinTransaction.dateValue, + amountValue: coinTransaction.amountValue, + feeValue: coinTransaction.feeValue, + confirmationsValue: coinTransaction.confirmationsValue, + blockValue: coinTransaction.blockValue, + isOutgoing: self.coinAddress == coinTransaction.senderAddress ? true : false, + transactionStatus: coinTransaction.transactionStatus, + nonceRaw: coinTransaction.nonceRaw) } } } @@ -136,7 +164,7 @@ extension AdamantCoinStorageService { entityName: CoinTransaction.entityCoinName ) request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ - NSPredicate(format: "coinId = %@", coinId) + NSPredicate(format: "uniqueId = %@", coinId + coinAddress) ]) request.sortDescriptors = [ NSSortDescriptor(key: "date", ascending: true), @@ -157,7 +185,7 @@ extension AdamantCoinStorageService { /// - Returns: Transaction, if found fileprivate func getTransactionFromDB(id: String, context: NSManagedObjectContext) -> CoinTransaction? { let request = NSFetchRequest(entityName: CoinTransaction.entityCoinName) - request.predicate = NSPredicate(format: "transactionId == %@", String(id)) + request.predicate = NSPredicate(format: "transactionId == %@ AND uniqueId == %@", String(id), coinId + coinAddress) request.fetchLimit = 1 do { diff --git a/Adamant/Services/AdmDialogService/AdamantDialogService.swift b/Adamant/Services/AdmDialogService/AdamantDialogService.swift index 7354984df..b8ff69498 100644 --- a/Adamant/Services/AdmDialogService/AdamantDialogService.swift +++ b/Adamant/Services/AdmDialogService/AdamantDialogService.swift @@ -313,6 +313,7 @@ extension AdamantDialogService { } func presentShareAlertFor( + title: String? = nil, string: String, types: [ShareType], excludedActivityTypes: [UIActivity.ActivityType]?, @@ -323,6 +324,7 @@ extension AdamantDialogService { let source: UIAlertController.SourceView? = from.map { .view($0) } let alert = createShareAlertFor( + title: title, stringForPasteboard: string, stringForShare: string, stringForQR: string, @@ -363,6 +365,7 @@ extension AdamantDialogService { } func presentShareAlertFor( + title: String? = nil, stringForPasteboard: String, stringForShare: String, stringForQR: String, @@ -375,6 +378,7 @@ extension AdamantDialogService { let source: UIAlertController.SourceView? = from.map { .view($0) } let alert = createShareAlertFor( + title: title, stringForPasteboard: stringForPasteboard, stringForShare: stringForShare, stringForQR: stringForQR, @@ -390,6 +394,7 @@ extension AdamantDialogService { } private func createShareAlertFor( + title: String? = nil, stringForPasteboard: String, stringForShare: String, stringForQR: String, @@ -401,7 +406,7 @@ extension AdamantDialogService { didSelect: ((ShareType) -> Void)? = nil ) -> UIAlertController { let alert = UIAlertController( - title: nil, + title: title, message: nil, preferredStyleSafe: .actionSheet, source: from @@ -665,18 +670,16 @@ extension AdamantAlertAction { } extension AdamantDialogService { - func showAlert(title: String?, message: String?, style: AdamantAlertStyle, actions: [AdamantAlertAction]?, from: UIAlertController.SourceView?) { - switch style { - case .alert, .actionSheet: - let uiStyle = style.asUIAlertControllerStyle() - if let actions = actions { - let uiActions: [UIAlertAction] = actions.map { $0.asUIAlertAction() } - - showAlert(title: title, message: message, style: uiStyle, actions: uiActions, from: from) - } else { - showAlert(title: title, message: message, style: uiStyle, actions: nil, from: from) - } - } + func showAlert( + title: String?, + message: String?, + style: AdamantAlertStyle, + actions: [AdamantAlertAction]?, + from: UIAlertController.SourceView? + ) { + let uiStyle = style.asUIAlertControllerStyle() + let uiActions = actions?.map { $0.asUIAlertAction() } + showAlert(title: title, message: message, style: uiStyle, actions: uiActions, from: from) } func showAlert(title: String?, message: String?, style: UIAlertController.Style, actions: [UIAlertAction]?, from: UIAlertController.SourceView?) { diff --git a/Adamant/Services/SecretWalletsFactory.swift b/Adamant/Services/SecretWalletsFactory.swift index 054b3696f..fc302c36a 100644 --- a/Adamant/Services/SecretWalletsFactory.swift +++ b/Adamant/Services/SecretWalletsFactory.swift @@ -7,20 +7,21 @@ // import CommonKit +import Swinject struct SecretWalletsFactory { private let visibleWalletsService: VisibleWalletsService private let accountService: AccountService - private let SecureStore: SecureStore - + private let container: Container + init( visibleWalletsService: VisibleWalletsService, accountService: AccountService, - SecureStore: SecureStore + container: Container ) { self.visibleWalletsService = visibleWalletsService self.accountService = accountService - self.SecureStore = SecureStore + self.container = container } func makeSecretWallet(withPassword password: String) -> WalletStoreServiceProtocol { @@ -37,17 +38,27 @@ struct SecretWalletsFactory { ERC20WalletService(token: $0) } wallets.append(contentsOf: erc20WalletServices) + let walletServiceCompose = AdamantWalletServiceCompose(wallets: wallets) - Task.detached(priority: .userInitiated) { + Task { @MainActor in + await injectDependencies(in: walletServiceCompose) await initWallets(withPass: password, for: walletServiceCompose) } + let wallet = AdamantWalletStoreService(visibleWalletsService: visibleWalletsService, walletServiceCompose: walletServiceCompose) return wallet } - + + @MainActor + private func injectDependencies(in walletService: WalletServiceCompose) async { + walletService.getWallets().forEach { wallet in + (wallet.core as? SwinjectDependentService)?.injectDependencies(from: container) + } + } + private func initWallets(withPass password: String, for walletService: WalletServiceCompose) async { - guard let passphrase: String = SecureStore.get(StoreKey.accountService.passphrase) else { + guard let passphrase: String = accountService.getCurrentPassphrase() else { print("No passphrase found") return } @@ -57,7 +68,8 @@ struct SecretWalletsFactory { taskGroup.addTask { _ = try? await wallet.core.initWallet( withPassphrase: passphrase, - withPassword: password + withPassword: password, + storeInKVS: false ) } } diff --git a/Adamant/Services/SecretWalletsService.swift b/Adamant/Services/SecretWalletsManager.swift similarity index 69% rename from Adamant/Services/SecretWalletsService.swift rename to Adamant/Services/SecretWalletsManager.swift index 81fd3e7c3..e467c5187 100644 --- a/Adamant/Services/SecretWalletsService.swift +++ b/Adamant/Services/SecretWalletsManager.swift @@ -10,45 +10,40 @@ import CommonKit import Foundation import Swinject -extension AdamantSecretWalletsManager { +private extension AdamantSecretWalletsManager { struct State: SecretWalletsManagerStateProtocol { var currentWallet: WalletStoreServiceProtocol - let defaultWallet: WalletStoreServiceProtocol + var regularWallet: WalletStoreServiceProtocol var secretWallets: [WalletStoreServiceProtocol] = [] } } final class AdamantSecretWalletsManager: SecretWalletsManagerProtocol { private let secretWalletsFactory: SecretWalletsFactory - private let lock = NSLock() - + + @Atomic private var state: SecretWalletsManagerStateProtocol + var statePublisher = ObservableSender() + + var wallets: [WalletStoreServiceProtocol] { [state.regularWallet] + state.secretWallets } + init( walletsStoreService: WalletStoreServiceProtocol, secretWalletsFactory: SecretWalletsFactory ) { self.state = State( currentWallet: walletsStoreService, - defaultWallet: walletsStoreService + regularWallet: walletsStoreService ) self.secretWalletsFactory = secretWalletsFactory } - - @ObservableValue private var state: SecretWalletsManagerStateProtocol - var statePublisher: AnyObservable { - $state.eraseToAnyPublisher() - } - + // MARK: - Manage state func createSecretWallet(withPassword password: String) { let wallet = secretWalletsFactory.makeSecretWallet(withPassword: password) - lock.lock() - defer { lock.unlock() } state.secretWallets.append(wallet) } func removeSecretWallet(at index: Int) -> WalletStoreServiceProtocol? { - lock.lock() - defer { lock.unlock() } guard state.secretWallets.indices.contains(index) else { return nil } return state.secretWallets.remove(at: index) } @@ -56,21 +51,27 @@ final class AdamantSecretWalletsManager: SecretWalletsManagerProtocol { func getCurrentWallet() -> WalletStoreServiceProtocol { state.currentWallet } - + + func getRegularWallet() -> WalletStoreServiceProtocol { + state.regularWallet + } + func getSecretWallets() -> [WalletStoreServiceProtocol] { state.secretWallets } func activateSecretWallet(at index: Int) { - lock.lock() - defer { lock.unlock() } guard index < state.secretWallets.count else { return } state.currentWallet = state.secretWallets[index] + statePublisher.send(state) } func activateDefaultWallet() { - lock.lock() - defer { lock.unlock() } - state.currentWallet = state.defaultWallet + state.currentWallet = state.regularWallet + statePublisher.send(state) + } + + func removeAllSecretWallets() { + state.secretWallets.removeAll() } } diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 7e70d1f25..c91f46cbf 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -251,25 +251,25 @@ "AccountTab.Section.Application" = "Application"; /* Account tab: Adamant wallet */ -"AccountTab.Wallets.adamant_wallet" = "ADAMANT Wallet"; +"AccountTab.Wallets.adamant" = "ADAMANT"; /* Account tab: Ethereum wallet */ -"AccountTab.Wallets.ethereum_wallet" = "Ethereum Wallet"; +"AccountTab.Wallets.ethereum" = "Ethereum"; /* Account tab: Klayr wallet */ -"AccountTab.Wallets.kly_wallet" = "Klayr Wallet"; +"AccountTab.Wallets.kly" = "Klayr"; /* Account tab: Bitcoin wallet */ -"AccountTab.Wallets.bitcoin_wallet" = "Bitcoin Wallet"; +"AccountTab.Wallets.bitcoin" = "Bitcoin"; /* Account tab: Doge wallet */ -"AccountTab.Wallets.doge_wallet" = "Doge Wallet"; +"AccountTab.Wallets.doge" = "Doge"; /* Account tab: Dash wallet */ -"AccountTab.Wallets.dash_wallet" = "Dash Wallet"; +"AccountTab.Wallets.dash" = "Dash"; /* Account tab: ERC20 wallet */ -"AccountTab.Wallets.erc20_wallet" = "%@ Wallet"; +"AccountTab.Wallets.erc20" = "%@"; /* Account page: scene title */ "AccountTab.Title" = "Konto"; @@ -886,6 +886,12 @@ /* QRGenerator: Passphrase textview placeholder */ "QrGeneratorScene.Passphrase.Placeholder" = "Passphrase"; +/* QRGenerator: Password textview placeholder */ +"QrGeneratorScene.Password.Placeholder" = "Passwort"; + +/* QRGenerator: Toggle textview title */ +"QrGeneratorScene.Toggle.Title" = "Geheime Wallets"; + /* QRGenerator: small 'Tap to save' tooltip under generated QR */ "QrGeneratorScene.TapToSave" = "Tippen, um zu speichern"; @@ -1207,6 +1213,12 @@ /* Transfer: fiat value of crypto-amout */ "TransferScene.Row.Fiat" = "Wert"; +/* Transfer: sender address */ +"TransferScene.Row.SenderAddress" = "Adresse"; + +/* Transfer: type of wallet */ +"TransferScene.Row.Type" = "Typ"; + /* Transfer: logged user balance. */ "TransferScene.Row.Balance" = "Kontostand"; @@ -1393,3 +1405,36 @@ "Chat.Alert.ReviewNodesList" = "ADM-Knotenliste überprüfen"; "Chat.Timestamp.InFuture.Error" = "Ein Netzwerkknoten hat die Nachricht abgelehnt, weil die Zeit auf Ihrem Gerät vorgeht.\nÜberprüfen Sie die Uhrzeit des Geräts oder versuchen Sie erneut, eine Nachricht zu senden."; + +/* Secret Wallets */ +"SecretWallets.Menu.Regular" = "💰 Normal"; +"SecretWallets.Menu.Regular.WithoutEmoji" = "Normal"; +"SecretWallets.Menu.Secret1" = "🔐1️⃣ Geheim 1"; +"SecretWallets.Menu.Secret2" = "🔐2️⃣ Geheim 2"; +"SecretWallets.Menu.Secret3" = "🔐3️⃣ Geheim 3"; +"SecretWallets.Menu.Secret4" = "🔐4️⃣ Geheim 4"; +"SecretWallets.Menu.Secret5" = "🔐5️⃣ Geheim 5"; +"SecretWallets.Menu.Secret1.Emoji" = "🔐1️⃣"; +"SecretWallets.Menu.Secret2.Emoji" = "🔐2️⃣"; +"SecretWallets.Menu.Secret3.Emoji" = "🔐3️⃣"; +"SecretWallets.Menu.Secret4.Emoji" = "🔐4️⃣"; +"SecretWallets.Menu.Secret5.Emoji" = "🔐5️⃣"; +"SecretWallets.Menu.AddSecretWallet" = "🪄 Geheimen hinzufügen"; +"SecretWallets.Menu.TellMeMore" = "💡 Über Wallets"; +"SecretWallets.Menu.TellMeMore.Title" = "Lorem Ipsum"; +"SecretWallets.Menu.TellMeMore.Subtitle" = "is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."; +"SecretWallets.Menu.TellMeMore.LearnMore" = "Learn more"; +"SecretWallets.Menu.Title" = "Aktive Wallets"; +"SecretWallets.Menu.AddSecretWallet.Title" = "Geben Sie das Passwort ein, um eine geheime Wallet hinzuzufügen"; +"SecretWallets.Menu.AddSecretWallet.PasswordPlaceholder" = "Passwort"; +"SecretWallets.Menu.AddSecretWallet.Add" = "Hinzufügen"; +"SecretWallets.Row.Title" = "Geheime Wallets (Hinzufügen)"; + +/* Secret Wallets Coins names*/ +"SecretWallets.Coin.Regular" = "%@ Wallet"; +"SecretWallets.Coin.SecretRegular" = "Reguläres %@ Wallet"; +"SecretWallets.Coin.Secret1" = "Geheimes %@ Wallet 🔐1️⃣"; +"SecretWallets.Coin.Secret2" = "Geheimes %@ Wallet 🔐2️⃣"; +"SecretWallets.Coin.Secret3" = "Geheimes %@ Wallet 🔐3️⃣"; +"SecretWallets.Coin.Secret4" = "Geheimes %@ Wallet 🔐4️⃣"; +"SecretWallets.Coin.Secret5" = "Geheimes %@ Wallet 🔐5️⃣"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 01e99f3db..07fff666b 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -248,25 +248,25 @@ "AccountTab.Section.Application" = "Application"; /* Account tab: Adamant wallet */ -"AccountTab.Wallets.adamant_wallet" = "ADAMANT Wallet"; +"AccountTab.Wallets.adamant" = "ADAMANT"; /* Account tab: Ethereum wallet */ -"AccountTab.Wallets.ethereum_wallet" = "Ethereum Wallet"; +"AccountTab.Wallets.ethereum" = "Ethereum"; /* Account tab: Klayr wallet */ -"AccountTab.Wallets.kly_wallet" = "Klayr Wallet"; +"AccountTab.Wallets.kly" = "Klayr"; /* Account tab: Bitcoin wallet */ -"AccountTab.Wallets.bitcoin_wallet" = "Bitcoin Wallet"; +"AccountTab.Wallets.bitcoin" = "Bitcoin"; /* Account tab: Doge wallet */ -"AccountTab.Wallets.doge_wallet" = "Doge Wallet"; +"AccountTab.Wallets.doge" = "Doge"; /* Account tab: Dash wallet */ -"AccountTab.Wallets.dash_wallet" = "Dash Wallet"; +"AccountTab.Wallets.dash" = "Dash"; /* Account tab: ERC20 wallet */ -"AccountTab.Wallets.erc20_wallet" = "%@ Wallet"; +"AccountTab.Wallets.erc20" = "%@"; /* Account page: scene title */ "AccountTab.Title" = "Account"; @@ -877,6 +877,12 @@ /* QRGenerator: Passphrase textview placeholder */ "QrGeneratorScene.Passphrase.Placeholder" = "Passphrase"; +/* QRGenerator: Password textview placeholder */ +"QrGeneratorScene.Password.Placeholder" = "Password"; + +/* QRGenerator: Toggle textview title */ +"QrGeneratorScene.Toggle.Title" = "Secret wallets"; + /* QRGenerator: small 'Tap to save' tooltip under generated QR */ "QrGeneratorScene.TapToSave" = "Tap to save"; @@ -1183,6 +1189,12 @@ /* Transfer: fiat value of crypto-amout */ "TransferScene.Row.Fiat" = "Value"; +/* Transfer: sender address */ +"TransferScene.Row.SenderAddress" = "Address"; + +/* Transfer: type of wallet */ +"TransferScene.Row.Type" = "Type"; + /* Transfer: logged user balance. */ "TransferScene.Row.Balance" = "Balance"; @@ -1369,3 +1381,36 @@ "Chat.Alert.ReviewNodesList" = "Review ADM node list"; "Chat.Timestamp.InFuture.Error" = "A network node rejected the message because the time on your device is ahead.\nCheck the device's time or try sending a message again."; + +/* Secret Wallets */ +"SecretWallets.Menu.Regular" = "💰 Regular"; +"SecretWallets.Menu.Regular.WithoutEmoji" = "Regular"; +"SecretWallets.Menu.Secret1" = "🔐1️⃣ Secret 1"; +"SecretWallets.Menu.Secret2" = "🔐2️⃣ Secret 2"; +"SecretWallets.Menu.Secret3" = "🔐3️⃣ Secret 3"; +"SecretWallets.Menu.Secret4" = "🔐4️⃣ Secret 4"; +"SecretWallets.Menu.Secret5" = "🔐5️⃣ Secret 5"; +"SecretWallets.Menu.Secret1.Emoji" = "🔐1️⃣"; +"SecretWallets.Menu.Secret2.Emoji" = "🔐2️⃣"; +"SecretWallets.Menu.Secret3.Emoji" = "🔐3️⃣"; +"SecretWallets.Menu.Secret4.Emoji" = "🔐4️⃣"; +"SecretWallets.Menu.Secret5.Emoji" = "🔐5️⃣"; +"SecretWallets.Menu.AddSecretWallet" = "🪄 Add secret wallet"; +"SecretWallets.Menu.TellMeMore" = "💡 Tell me more"; +"SecretWallets.Menu.TellMeMore.Title" = "Lorem Ipsum"; +"SecretWallets.Menu.TellMeMore.Subtitle" = "is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."; +"SecretWallets.Menu.TellMeMore.LearnMore" = "Learn more"; +"SecretWallets.Menu.Title" = "Active wallets"; +"SecretWallets.Menu.AddSecretWallet.Title" = "Enter password to add secret wallet"; +"SecretWallets.Menu.AddSecretWallet.PasswordPlaceholder" = "Password"; +"SecretWallets.Menu.AddSecretWallet.Add" = "Add"; +"SecretWallets.Row.Title" = "Secret wallets (Add)"; + +/* Secret Wallets Coins names*/ +"SecretWallets.Coin.Regular" = "%@ Wallet"; +"SecretWallets.Coin.SecretRegular" = "Regular %@ Wallet"; +"SecretWallets.Coin.Secret1" = "Secret %@ Wallet 🔐1️⃣"; +"SecretWallets.Coin.Secret2" = "Secret %@ Wallet 🔐2️⃣"; +"SecretWallets.Coin.Secret3" = "Secret %@ Wallet 🔐3️⃣"; +"SecretWallets.Coin.Secret4" = "Secret %@ Wallet 🔐4️⃣"; +"SecretWallets.Coin.Secret5" = "Secret %@ Wallet 🔐5️⃣"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 055ab24ca..fd5f29aa4 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -248,25 +248,25 @@ "AccountTab.Section.Application" = "Приложение"; /* Account tab: Adamant wallet */ -"AccountTab.Wallets.adamant_wallet" = "Кошелек ADAMANT"; +"AccountTab.Wallets.adamant" = "ADAMANT"; /* Account tab: Ethereum wallet */ -"AccountTab.Wallets.ethereum_wallet" = "Кошелек Ethereum"; +"AccountTab.Wallets.ethereum" = "Ethereum"; /* Account tab: Klayr wallet */ -"AccountTab.Wallets.kly_wallet" = "Кошелек Klayr"; +"AccountTab.Wallets.kly" = "Klayr"; /* Account tab: Bitcoin wallet */ -"AccountTab.Wallets.bitcoin_wallet" = "Кошелек Bitcoin"; +"AccountTab.Wallets.bitcoin" = "Bitcoin"; /* Account tab: Doge wallet */ -"AccountTab.Wallets.doge_wallet" = "Кошелек Doge"; +"AccountTab.Wallets.doge" = "Doge"; /* Account tab: Dash wallet */ -"AccountTab.Wallets.dash_wallet" = "Кошелек Dash"; +"AccountTab.Wallets.dash" = "Dash"; /* Account tab: ERC20 wallet */ -"AccountTab.Wallets.erc20_wallet" = "Кошелек %@"; +"AccountTab.Wallets.erc20" = "%@"; /* Account tab: Delegates section title */ "AccountTab.Section.Delegates" = "Делегаты"; @@ -878,6 +878,12 @@ /* QRGenerator: Passphrase textview placeholder */ "QrGeneratorScene.Passphrase.Placeholder" = "Пароль"; +/* QRGenerator: Password textview placeholder */ +"QrGeneratorScene.Password.Placeholder" = "Пароль"; + +/* QRGenerator: Toggle textview title */ +"QrGeneratorScene.Toggle.Title" = "Секретные кошельки"; + /* QRGenerator: small 'Tap to save' tooltip under generated QR */ "QrGeneratorScene.TapToSave" = "Нажмите для сохранения"; @@ -1184,6 +1190,12 @@ /* Transfer: fiat value of crypto-amout */ "TransferScene.Row.Fiat" = "Ценность"; +/* Transfer: sender address */ +"TransferScene.Row.SenderAddress" = "Адрес"; + +/* Transfer: type of wallet */ +"TransferScene.Row.Type" = "Тип"; + /* Transfer: logged user balance. */ "TransferScene.Row.Balance" = "Баланс"; @@ -1370,3 +1382,36 @@ "Chat.Alert.ReviewNodesList" = "К списку узлов ADM"; "Chat.Timestamp.InFuture.Error" = "Узел сети отклонил сообщение, потому что время на вашем устройстве спешит.\nПроверьте время на устройстве или попробуйте отправить сообщение снова."; + +/* Secret Wallets */ +"SecretWallets.Menu.Regular" = "💰 Обычный"; +"SecretWallets.Menu.Regular.WithoutEmoji" = "Обычный"; +"SecretWallets.Menu.Secret1" = "🔐1️⃣ Секретный 1"; +"SecretWallets.Menu.Secret2" = "🔐2️⃣ Секретный 2"; +"SecretWallets.Menu.Secret3" = "🔐3️⃣ Секретный 3"; +"SecretWallets.Menu.Secret4" = "🔐4️⃣ Секретный 4"; +"SecretWallets.Menu.Secret5" = "🔐5️⃣ Секретный 5"; +"SecretWallets.Menu.Secret1.Emoji" = "🔐1️⃣"; +"SecretWallets.Menu.Secret2.Emoji" = "🔐2️⃣"; +"SecretWallets.Menu.Secret3.Emoji" = "🔐3️⃣"; +"SecretWallets.Menu.Secret4.Emoji" = "🔐4️⃣"; +"SecretWallets.Menu.Secret5.Emoji" = "🔐5️⃣"; +"SecretWallets.Menu.AddSecretWallet" = "🪄 Добавить секретный"; +"SecretWallets.Menu.TellMeMore" = "💡 О кошельках"; +"SecretWallets.Menu.TellMeMore.Title" = "Lorem Ipsum"; +"SecretWallets.Menu.TellMeMore.Subtitle" = "is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."; +"SecretWallets.Menu.TellMeMore.LearnMore" = "Learn more"; +"SecretWallets.Menu.Title" = "Активные кошельки"; +"SecretWallets.Menu.AddSecretWallet.Title" = "Введите пароль, чтобы добавить секретный кошелек"; +"SecretWallets.Menu.AddSecretWallet.PasswordPlaceholder" = "Пароль"; +"SecretWallets.Menu.AddSecretWallet.Add" = "Добавить"; +"SecretWallets.Row.Title" = "Секретные кошельки (Добавить)"; + +/* Secret Wallets Coins names*/ +"SecretWallets.Coin.Regular" = "%@ кошелёк"; +"SecretWallets.Coin.SecretRegular" = "Обычный %@ кошелёк"; +"SecretWallets.Coin.Secret1" = "Секретный %@ кошелёк 🔐1️⃣"; +"SecretWallets.Coin.Secret2" = "Секретный %@ кошелёк 🔐2️⃣"; +"SecretWallets.Coin.Secret3" = "Секретный %@ кошелёк 🔐3️⃣"; +"SecretWallets.Coin.Secret4" = "Секретный %@ кошелёк 🔐4️⃣"; +"SecretWallets.Coin.Secret5" = "Секретный %@ кошелёк 🔐5️⃣"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 888a6fc5b..00606360a 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -248,25 +248,25 @@ "AccountTab.Section.Application" = "应用"; /* Account tab: Adamant wallet */ -"AccountTab.Wallets.adamant_wallet" = "ADAMANT钱包"; +"AccountTab.Wallets.adamant" = "ADAMANT"; /* Account tab: Ethereum wallet */ -"AccountTab.Wallets.ethereum_wallet" = "以太坊钱包"; +"AccountTab.Wallets.ethereum" = "以太坊"; /* Account tab: Klayr wallet */ -"AccountTab.Wallets.kly_wallet" = "Klayr钱包"; +"AccountTab.Wallets.kly" = "Klayr"; /* Account tab: Bitcoin wallet */ -"AccountTab.Wallets.bitcoin_wallet" = "比特币钱包"; +"AccountTab.Wallets.bitcoin" = "比特币"; /* Account tab: Doge wallet */ -"AccountTab.Wallets.doge_wallet" = "狗狗钱包"; +"AccountTab.Wallets.doge" = "狗狗"; /* Account tab: Dash wallet */ -"AccountTab.Wallets.dash_wallet" = "达世币钱包"; +"AccountTab.Wallets.dash" = "达世币"; /* Account tab: ERC20 wallet */ -"AccountTab.Wallets.erc20_wallet" = "%@钱包"; +"AccountTab.Wallets.erc20" = "%@"; /* Account page: scene title */ "AccountTab.Title" = "帐户"; @@ -870,6 +870,12 @@ /* QRGenerator: Passphrase textview placeholder */ "QrGeneratorScene.Passphrase.Placeholder" = "密码短语"; +/* QRGenerator: Password textview placeholder */ +"QrGeneratorScene.Password.Placeholder" = "密码"; + +/* QRGenerator: Toggle textview title */ +"QrGeneratorScene.Toggle.Title" = "秘密钱包"; + /* QRGenerator: small 'Tap to save' tooltip under generated QR */ "QrGeneratorScene.TapToSave" = "点击保存"; @@ -1173,6 +1179,12 @@ /* Transfer: fiat value of crypto-amout */ "TransferScene.Row.Fiat" = "价值"; +/* Transfer: sender address */ +"TransferScene.Row.SenderAddress" = "地址"; + +/* Transfer: type of wallet */ +"TransferScene.Row.Type" = "类型"; + /* Transfer: logged user balance. */ "TransferScene.Row.Balance" = "余额"; @@ -1360,3 +1372,36 @@ "Chat.Alert.ReviewNodesList" = "查看 ADM 节点列表"; "Chat.Timestamp.InFuture.Error" = "由于您设备上的时间超前,网络节点拒绝了信息。\n请检查设备的时间或再次尝试发送信息。"; + +/* Secret Wallets */ +"SecretWallets.Menu.Regular" = "💰 普通"; +"SecretWallets.Menu.Regular.WithoutEmoji" = "普通"; +"SecretWallets.Menu.Secret1" = "🔐1️⃣ 秘密 1"; +"SecretWallets.Menu.Secret2" = "🔐2️⃣ 秘密 2"; +"SecretWallets.Menu.Secret3" = "🔐3️⃣ 秘密 3"; +"SecretWallets.Menu.Secret4" = "🔐4️⃣ 秘密 4"; +"SecretWallets.Menu.Secret5" = "🔐5️⃣ 秘密 5"; +"SecretWallets.Menu.Secret1.Emoji" = "🔐1️⃣"; +"SecretWallets.Menu.Secret2.Emoji" = "🔐2️⃣"; +"SecretWallets.Menu.Secret3.Emoji" = "🔐3️⃣"; +"SecretWallets.Menu.Secret4.Emoji" = "🔐4️⃣"; +"SecretWallets.Menu.Secret5.Emoji" = "🔐5️⃣"; +"SecretWallets.Menu.AddSecretWallet" = "🪄 添加秘密"; +"SecretWallets.Menu.TellMeMore" = "💡 关于钱包"; +"SecretWallets.Menu.TellMeMore.Title" = "Lorem Ipsum"; +"SecretWallets.Menu.TellMeMore.Subtitle" = "is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."; +"SecretWallets.Menu.TellMeMore.LearnMore" = "Learn more"; +"SecretWallets.Menu.Title" = "活动钱包"; +"SecretWallets.Menu.AddSecretWallet.Title" = "输入密码以添加秘密钱包"; +"SecretWallets.Menu.AddSecretWallet.PasswordPlaceholder" = "密码"; +"SecretWallets.Menu.AddSecretWallet.Add" = "添加"; +"SecretWallets.Row.Title" = "秘密钱包(添加)"; + +/* Secret Wallets Coins names*/ +"SecretWallets.Coin.Regular" = "%@ 钱包"; +"SecretWallets.Coin.SecretRegular" = "常规 %@ 钱包"; +"SecretWallets.Coin.Secret1" = "秘密 %@ 钱包 🔐1️⃣"; +"SecretWallets.Coin.Secret2" = "秘密 %@ 钱包 🔐2️⃣"; +"SecretWallets.Coin.Secret3" = "秘密 %@ 钱包 🔐3️⃣"; +"SecretWallets.Coin.Secret4" = "秘密 %@ 钱包 🔐4️⃣"; +"SecretWallets.Coin.Secret5" = "秘密 %@ 钱包 🔐5️⃣"; diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button.png deleted file mode 100644 index ece3149af..000000000 Binary files a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button.png and /dev/null differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button@2x.png deleted file mode 100644 index 8ff33b9a5..000000000 Binary files a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button@2x.png and /dev/null differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button@3x.png deleted file mode 100644 index e0d4812c1..000000000 Binary files a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Share_button@3x.png and /dev/null differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/Contents.json similarity index 67% rename from CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Contents.json rename to CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/Contents.json index 8111efaf5..9c484170b 100644 --- a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/Share_button_small.imageset/Contents.json +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "Share_button.png", + "filename" : "secure_wallet_badge_active.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "Share_button@2x.png", + "filename" : "secure_wallet_badge_active@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "Share_button@3x.png", + "filename" : "secure_wallet_badge_active@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active.png new file mode 100644 index 000000000..2aeab0f61 Binary files /dev/null and b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active.png differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active@2x.png new file mode 100644 index 000000000..9e7318f68 Binary files /dev/null and b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active@2x.png differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active@3x.png new file mode 100644 index 000000000..de6690887 Binary files /dev/null and b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_active.imageset/secure_wallet_badge_active@3x.png differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/Contents.json new file mode 100644 index 000000000..f51741af1 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "secure_wallet_badge_regular.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "secure_wallet_badge_regular@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "secure_wallet_badge_regular@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular.png new file mode 100644 index 000000000..4dad226ac Binary files /dev/null and b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular.png differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular@2x.png new file mode 100644 index 000000000..4b3cb3ce1 Binary files /dev/null and b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular@2x.png differ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular@3x.png new file mode 100644 index 000000000..41a74cee0 Binary files /dev/null and b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/secret_wallets_regular.imageset/secure_wallet_badge_regular@3x.png differ