From 914954d2bbad0dcb54bd93ca20c29f5f4bcbba3d Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 28 Nov 2024 15:31:50 -0300 Subject: [PATCH 1/2] [trello.com/c/R8cyJtgT] Added formats to generated keys --- Adamant.xcodeproj/project.pbxproj | 32 ++- .../AdamantScreensFactory.swift | 4 +- .../PKGenerator/PKGeneratorFactory.swift | 36 +++ .../PKGenerator/PKGeneratorState.swift | 34 +++ .../PKGenerator/PKGeneratorView.swift | 98 +++++++ .../PKGenerator/PKGeneratorViewModel.swift | 155 +++++++++++ .../Settings/PKGeneratorViewController.swift | 241 ------------------ .../Settings/PrivateKeyGenerator.swift | 7 + .../Modules/Settings/SettingsFactory.swift | 7 - .../Wallets/Bitcoin/BtcWalletService.swift | 2 + .../Wallets/Dash/DashWalletService.swift | 2 + .../Wallets/Doge/DogeWalletService.swift | 2 + .../Wallets/Ethereum/EthWalletService.swift | 2 + .../KlyWalletService+WalletCore.swift | 2 + Adamant/SharedViews/AdamantSecureField.swift | 50 ++++ .../Localization/de.lproj/Localizable.strings | 3 + .../Localization/en.lproj/Localizable.strings | 3 + .../Localization/ru.lproj/Localizable.strings | 3 + .../Localization/zh.lproj/Localizable.strings | 3 + 19 files changed, 433 insertions(+), 253 deletions(-) create mode 100644 Adamant/Modules/Settings/PKGenerator/PKGeneratorFactory.swift create mode 100644 Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift create mode 100644 Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift create mode 100644 Adamant/Modules/Settings/PKGenerator/PKGeneratorViewModel.swift delete mode 100644 Adamant/Modules/Settings/PKGeneratorViewController.swift create mode 100644 Adamant/SharedViews/AdamantSecureField.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b62a66148..2b5eaaa75 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -269,6 +269,11 @@ 9304F8BE292F88F900173F18 /* ANSPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8BD292F88F900173F18 /* ANSPayload.swift */; }; 9304F8C2292F895C00173F18 /* PushNotificationsTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */; }; 9304F8C4292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */; }; + 9306E0932CF8C50E00A99BA4 /* PKGeneratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9306E0912CF8C50E00A99BA4 /* PKGeneratorView.swift */; }; + 9306E0942CF8C50E00A99BA4 /* PKGeneratorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9306E08F2CF8C50E00A99BA4 /* PKGeneratorFactory.swift */; }; + 9306E0952CF8C50E00A99BA4 /* PKGeneratorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9306E0922CF8C50E00A99BA4 /* PKGeneratorViewModel.swift */; }; + 9306E0962CF8C50E00A99BA4 /* PKGeneratorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9306E0902CF8C50E00A99BA4 /* PKGeneratorState.swift */; }; + 9306E0982CF8C67B00A99BA4 /* AdamantSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9306E0972CF8C67B00A99BA4 /* AdamantSecureField.swift */; }; 931224A92C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224A82C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift */; }; 931224AB2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224AA2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift */; }; 931224AD2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224AC2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift */; }; @@ -533,7 +538,6 @@ E9484B79227C617E008E10F0 /* BalanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9484B77227C617D008E10F0 /* BalanceTableViewCell.swift */; }; E9484B7A227CA93B008E10F0 /* BalanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9484B78227C617E008E10F0 /* BalanceTableViewCell.xib */; }; E9484B7D2285BAD9008E10F0 /* PrivateKeyGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9484B7C2285BAD8008E10F0 /* PrivateKeyGenerator.swift */; }; - E9484B7F2285C016008E10F0 /* PKGeneratorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */; }; E94883E7203F07CD00F6E1B0 /* PassphraseValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94883E6203F07CD00F6E1B0 /* PassphraseValidation.swift */; }; E948E03B20235E2300975D6B /* SettingsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E948E03A20235E2300975D6B /* SettingsFactory.swift */; }; E94E7B01205D3F090042B639 /* ChatListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E94E7B00205D3F090042B639 /* ChatListViewController.xib */; }; @@ -941,6 +945,11 @@ 9304F8BD292F88F900173F18 /* ANSPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ANSPayload.swift; sourceTree = ""; }; 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsTokenService.swift; sourceTree = ""; }; 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantPushNotificationsTokenService.swift; sourceTree = ""; }; + 9306E08F2CF8C50E00A99BA4 /* PKGeneratorFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKGeneratorFactory.swift; sourceTree = ""; }; + 9306E0902CF8C50E00A99BA4 /* PKGeneratorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKGeneratorState.swift; sourceTree = ""; }; + 9306E0912CF8C50E00A99BA4 /* PKGeneratorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKGeneratorView.swift; sourceTree = ""; }; + 9306E0922CF8C50E00A99BA4 /* PKGeneratorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKGeneratorViewModel.swift; sourceTree = ""; }; + 9306E0972CF8C67B00A99BA4 /* AdamantSecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantSecureField.swift; sourceTree = ""; }; 931224A82C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InfoServiceApiService+Extension.swift"; sourceTree = ""; }; 931224AA2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceRatesRequestDTO.swift; sourceTree = ""; }; 931224AC2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceHistoryRequestDTO.swift; sourceTree = ""; }; @@ -1175,7 +1184,6 @@ E9484B77227C617D008E10F0 /* BalanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceTableViewCell.swift; sourceTree = ""; }; E9484B78227C617E008E10F0 /* BalanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BalanceTableViewCell.xib; sourceTree = ""; }; E9484B7C2285BAD8008E10F0 /* PrivateKeyGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateKeyGenerator.swift; sourceTree = ""; }; - E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKGeneratorViewController.swift; sourceTree = ""; }; E94883E6203F07CD00F6E1B0 /* PassphraseValidation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassphraseValidation.swift; sourceTree = ""; }; E948E03A20235E2300975D6B /* SettingsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFactory.swift; sourceTree = ""; }; E94E7B00205D3F090042B639 /* ChatListViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChatListViewController.xib; sourceTree = ""; }; @@ -1735,6 +1743,17 @@ path = Doge; sourceTree = ""; }; + 9306E08E2CF8C4EF00A99BA4 /* PKGenerator */ = { + isa = PBXGroup; + children = ( + 9306E08F2CF8C50E00A99BA4 /* PKGeneratorFactory.swift */, + 9306E0902CF8C50E00A99BA4 /* PKGeneratorState.swift */, + 9306E0912CF8C50E00A99BA4 /* PKGeneratorView.swift */, + 9306E0922CF8C50E00A99BA4 /* PKGeneratorViewModel.swift */, + ); + path = PKGenerator; + sourceTree = ""; + }; 931224A72C7A9F9C009E0ED0 /* ApiService */ = { isa = PBXGroup; children = ( @@ -2688,6 +2707,7 @@ E982F69820235AF000566AC7 /* Settings */ = { isa = PBXGroup; children = ( + 9306E08E2CF8C4EF00A99BA4 /* PKGenerator */, 2621AB352C60E52900046D7A /* Notifications */, 411742FE2A39B1B1008CD98A /* Contribute */, 4197B9C72952FAA2004CAF64 /* VisibleWallets */, @@ -2697,7 +2717,6 @@ E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */, E90055FA20ECE78A00D0CB2D /* SecurityViewController+notifications.swift */, E9942B7F203C058C00C163AF /* QRGeneratorViewController.swift */, - E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */, E9484B7C2285BAD8008E10F0 /* PrivateKeyGenerator.swift */, ); path = Settings; @@ -2760,6 +2779,7 @@ E921534C20EE1E8700C0843F /* EurekaAlertLabelRow.swift */, E921534D20EE1E8700C0843F /* AlertLabelCell.xib */, E926E031213EC43B005E536B /* FullscreenAlertView.swift */, + 9306E0972CF8C67B00A99BA4 /* AdamantSecureField.swift */, E9B4E1A7210F079E007E77FC /* DoubleDetailsTableViewCell.swift */, E9B4E1A9210F08BE007E77FC /* DoubleDetailsTableViewCell.xib */, 649D6BEB21BD5A53009E727B /* UISuffixTextField.swift */, @@ -3350,6 +3370,7 @@ E9942B80203C058C00C163AF /* QRGeneratorViewController.swift in Sources */, 4184F1772A33173100D7B8B9 /* ContributeView.swift in Sources */, 3A7BD0122AA9BD5A0045AAB0 /* AdamantVibroType.swift in Sources */, + 9306E0982CF8C67B00A99BA4 /* AdamantSecureField.swift in Sources */, E921597520611A6A0000CA5C /* AdamantReachability.swift in Sources */, 3A2478AE2BB42967009D89E9 /* ChatDropView.swift in Sources */, E9960B3321F5154300C840A8 /* BaseAccount+CoreDataClass.swift in Sources */, @@ -3487,7 +3508,6 @@ E940088B2114F63000CD2D67 /* NSRegularExpression+adamant.swift in Sources */, 3AE0A4372BC6AA6000BF7125 /* FilesNetworkManagerProtocol.swift in Sources */, 932B34E92974AA4A002A75BA /* ChatPreservationProtocol.swift in Sources */, - E9484B7F2285C016008E10F0 /* PKGeneratorViewController.swift in Sources */, E9B3D3A9202082450019EB36 /* AdamantTransfersProvider.swift in Sources */, 6449BA71235CA0930033B936 /* ERC20WalletService+RichMessageProvider.swift in Sources */, 938F7D642955C94F001915CA /* ChatViewController.swift in Sources */, @@ -3537,6 +3557,10 @@ E94008722114EACF00CD2D67 /* WalletAccount.swift in Sources */, 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */, 3AF9DF0D2C049161009A43A8 /* CircularProgressView.swift in Sources */, + 9306E0932CF8C50E00A99BA4 /* PKGeneratorView.swift in Sources */, + 9306E0942CF8C50E00A99BA4 /* PKGeneratorFactory.swift in Sources */, + 9306E0952CF8C50E00A99BA4 /* PKGeneratorViewModel.swift in Sources */, + 9306E0962CF8C50E00A99BA4 /* PKGeneratorState.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, A50A41092822F8CE006BDFE1 /* BtcWalletViewController.swift in Sources */, E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */, diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index 59795f7c1..0bb351920 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -32,6 +32,7 @@ struct AdamantScreensFactory: ScreensFactory { private let notificationsFactory: NotificationsFactory private let notificationSoundsFactory: NotificationSoundsFactory private let storageUsageFactory: StorageUsageFactory + private let pkGeneratorFactory: PKGeneratorFactory init(assembler: Assembler) { admWalletFactory = .init(assembler: assembler) @@ -52,6 +53,7 @@ struct AdamantScreensFactory: ScreensFactory { notificationsFactory = .init(parent: assembler) notificationSoundsFactory = .init(parent: assembler) storageUsageFactory = .init(parent: assembler) + pkGeneratorFactory = .init(parent: assembler) walletFactoryCompose = AdamantWalletFactoryCompose( klyWalletFactory: .init(assembler: assembler), @@ -157,7 +159,7 @@ struct AdamantScreensFactory: ScreensFactory { } func makePKGenerator() -> UIViewController { - settingsFactory.makePKGeneratorVC() + pkGeneratorFactory.makeViewController() } func makeAbout() -> UIViewController { diff --git a/Adamant/Modules/Settings/PKGenerator/PKGeneratorFactory.swift b/Adamant/Modules/Settings/PKGenerator/PKGeneratorFactory.swift new file mode 100644 index 000000000..eac865843 --- /dev/null +++ b/Adamant/Modules/Settings/PKGenerator/PKGeneratorFactory.swift @@ -0,0 +1,36 @@ +// +// PKGeneratorFactory.swift +// Adamant +// +// Created by Andrew G on 28.11.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Swinject +import SwiftUI + +struct PKGeneratorFactory { + private let parent: Assembler + private let assemblies = [PKGeneratorAssembly()] + + init(parent: Assembler) { + self.parent = parent + } + + func makeViewController() -> UIViewController { + let assembler = Assembler(assemblies, parent: parent) + let viewModel = { assembler.resolver.resolve(PKGeneratorViewModel.self)! } + return UIHostingController(rootView: PKGeneratorView(viewModel: viewModel)) + } +} + +private struct PKGeneratorAssembly: Assembly { + func assemble(container: Container) { + container.register(PKGeneratorViewModel.self) { + .init( + dialogService: $0.resolve(DialogService.self)!, + walletServiceCompose: $0.resolve(WalletServiceCompose.self)! + ) + }.inObjectScope(.transient) + } +} diff --git a/Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift b/Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift new file mode 100644 index 000000000..dc28a23ce --- /dev/null +++ b/Adamant/Modules/Settings/PKGenerator/PKGeneratorState.swift @@ -0,0 +1,34 @@ +// +// PKGeneratorState.swift +// Adamant +// +// Created by Andrew G on 28.11.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import UIKit + +struct PKGeneratorState { + var passphrase: String + var keys: [KeyInfo] + var buttonDescription: AttributedString + var isLoading: Bool + + static let `default` = Self( + passphrase: .empty, + keys: .init(), + buttonDescription: .init(), + isLoading: false + ) +} + +extension PKGeneratorState { + struct KeyInfo: Identifiable { + var id: String { title } + + let title: String + let description: String + let icon: UIImage + let key: String + } +} diff --git a/Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift b/Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift new file mode 100644 index 000000000..336558c6e --- /dev/null +++ b/Adamant/Modules/Settings/PKGenerator/PKGeneratorView.swift @@ -0,0 +1,98 @@ +// +// PKGeneratorView.swift +// Adamant +// +// Created by Andrew G on 28.11.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import SwiftUI +import CommonKit + +struct PKGeneratorView: View { + @StateObject private var viewModel: PKGeneratorViewModel + + var body: some View { + List { + if !viewModel.state.keys.isEmpty { + keysSection + } + + inputSection + } + .withoutListBackground() + .background(Color(.adamant.secondBackgroundColor)) + .navigationTitle(String.adamant.pkGenerator.title) + .navigationBarTitleDisplayMode(.inline) + } + + init(viewModel: @escaping () -> PKGeneratorViewModel) { + _viewModel = .init(wrappedValue: viewModel()) + } +} + +private extension PKGeneratorView { + var loadingBackground: some View { + HStack { + Spacer() + + if viewModel.state.isLoading { + ProgressView() + } + } + } + + var keysSection: some View { + Section { + ForEach(viewModel.state.keys, content: keyView) + .listRowBackground(Color(uiColor: .adamant.cellColor)) + } + } + + var inputSection: some View { + Section { + Group { + Text(viewModel.state.buttonDescription) + .multilineTextAlignment(.center) + .padding(.vertical, 5) + + AdamantSecureField( + placeholder: .adamant.qrGenerator.passphrasePlaceholder, + text: $viewModel.state.passphrase + ) + + Button(action: { viewModel.generateKeys() }) { + Text(String.adamant.pkGenerator.generateButton) + .foregroundStyle(Color(uiColor: .adamant.primary)) + .padding(.horizontal, 30) + .background(loadingBackground) + .expanded(axes: .horizontal) + } + }.listRowBackground(Color(uiColor: .adamant.cellColor)) + } + } + + func keyView(_ keyInfo: PKGeneratorState.KeyInfo) -> some View { + NavigationButton(action: { viewModel.onTap(key: keyInfo.key) }) { + HStack { + Image(uiImage: keyInfo.icon) + .renderingMode(.template) + .resizable() + .frame(squareSize: 25) + .foregroundStyle(Color(uiColor: .adamant.tableRowIcons)) + + VStack(alignment: .leading) { + Text(keyInfo.title) + Text(keyInfo.description) + .foregroundStyle(Color(uiColor: .adamant.secondary)) + .font(.system(size: 12, weight: .ultraLight)) + } + + Spacer(minLength: .zero) + + 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 new file mode 100644 index 000000000..dec39e087 --- /dev/null +++ b/Adamant/Modules/Settings/PKGenerator/PKGeneratorViewModel.swift @@ -0,0 +1,155 @@ +// +// PKGeneratorViewModel.swift +// Adamant +// +// Created by Andrew G on 28.11.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import SwiftUI +import Combine +import CommonKit +import MarkdownKit + +// MARK: - Localization +extension String.adamant { + enum pkGenerator { + static var title: String { + .localized( + "PkGeneratorScene.Title", + comment: "PrivateKeyGenerator: scene title" + ) + } + static var alert: String { + .localized( + "PkGeneratorScene.Alert", + comment: "PrivateKeyGenerator: Security alert. Keep your passphrase safe" + ) + } + static var generateButton: String { + .localized( + "PkGeneratorScene.GenerateButton", + comment: "PrivateKeyGenerator: Generate button" + ) + } + + static func keyFormat(_ format: String) -> String { + .localizedStringWithFormat( + .localized( + "PkGeneratorScene.KeyFormat", + comment: "PrivateKeyGenerator: key format" + ), + format + ) + } + } +} + +@MainActor +final class PKGeneratorViewModel: ObservableObject { + @Published var state: PKGeneratorState = .default + + private let dialogService: DialogService + private let walletServiceCompose: WalletServiceCompose + + nonisolated init( + dialogService: DialogService, + walletServiceCompose: WalletServiceCompose + ) { + self.dialogService = dialogService + self.walletServiceCompose = walletServiceCompose + Task { @MainActor in configure() } + } + + func onTap(key: String) { + dialogService.presentShareAlertFor( + string: key, + types: [ + .copyToPasteboard, + .share, + .generateQr( + encodedContent: key, + sharingTip: nil, + withLogo: false + ) + ], + excludedActivityTypes: ShareContentType.passphrase.excludedActivityTypes, + animated: true, + from: Optional.none, + completion: nil + ) + } + + func generateKeys() { + guard !state.isLoading else { return } + withAnimation { state.isLoading = true } + let passphrase = state.passphrase.lowercased() + + Task { + defer { withAnimation { state.isLoading = false } } + + do { + let keys = try await Task.detached { [walletServiceCompose] in + try generatePrivateKeys( + passphrase: passphrase, + walletServiceCompose: walletServiceCompose + ) + }.value + + withAnimation { state.keys = keys } + } catch { + dialogService.showToastMessage(error.localizedDescription) + } + } + } +} + +private extension PKGeneratorViewModel { + func configure() { + state.buttonDescription = getButtonDescription() + } + + func getButtonDescription() -> AttributedString { + let parser = MarkdownParser( + font: UIFont.systemFont(ofSize: UIFont.systemFontSize), + color: .adamant.primary + ) + + let style = NSMutableParagraphStyle() + style.alignment = NSTextAlignment.center + + let mutableText = NSMutableAttributedString( + attributedString: parser.parse(.adamant.pkGenerator.alert) + ) + + mutableText.addAttribute( + .paragraphStyle, + value: style, + range: .init(location: .zero, length: mutableText.length) + ) + + return .init(mutableText) + } +} + +private func generatePrivateKeys( + passphrase: 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 + ) + } +} diff --git a/Adamant/Modules/Settings/PKGeneratorViewController.swift b/Adamant/Modules/Settings/PKGeneratorViewController.swift deleted file mode 100644 index d854e13ca..000000000 --- a/Adamant/Modules/Settings/PKGeneratorViewController.swift +++ /dev/null @@ -1,241 +0,0 @@ -// -// PKGeneratorViewController.swift -// Adamant -// -// Created by Anokhov Pavel on 10/05/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import UIKit -import EFQRCode -import Eureka -import Photos -import MarkdownKit -import CommonKit - -// MARK: - Localization -extension String.adamant { - enum pkGenerator { - static var title: String { - String.localized("PkGeneratorScene.Title", comment: "PrivateKeyGenerator: scene title") - } - static var alert: String { - String.localized("PkGeneratorScene.Alert", comment: "PrivateKeyGenerator: Security alert. Keep your passphrase safe") - } - static var generateButton: String { - String.localized("PkGeneratorScene.GenerateButton", comment: "PrivateKeyGenerator: Generate button") - } - } -} - -// MARK: - -final class PKGeneratorViewController: FormViewController { - - // MARK: Dependencies - - private let dialogService: DialogService - private let walletServiceCompose: WalletServiceCompose - - private enum Rows { - case alert - case passphrase - case generateButton - - var tag: String { - switch self { - case .alert: return "alrt" - case .passphrase: return "pp" - case .generateButton: return "generate" - } - } - } - - private enum Sections { - case privateKeys - case passphrase - - var tag: String { - switch self { - case .privateKeys: return "pks" - case .passphrase: return "pps" - } - } - } - - // MARK: - Properties - private var showKeysSection = false - - // MARK: Init - - init( - dialogService: DialogService, - walletServiceCompose: WalletServiceCompose - ) { - self.dialogService = dialogService - self.walletServiceCompose = walletServiceCompose - - super.init(style: .insetGrouped) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - - navigationItem.largeTitleDisplayMode = .never - navigationItem.title = String.adamant.pkGenerator.title - navigationOptions = .Disabled - - tableView.showsVerticalScrollIndicator = false - tableView.showsHorizontalScrollIndicator = false - - // MARK: PrivateKeys section - let pkSection = Section { - $0.tag = Sections.privateKeys.tag - $0.hidden = Condition.function([], { [weak self] _ -> Bool in - guard let show = self?.showKeysSection else { - return true - } - - return !show - }) - } - - // MARK: Passphrase section - let passphraseSection = Section { $0.tag = Sections.passphrase.tag } - - let alertRow = TextAreaRow { - $0.tag = Rows.alert.tag - $0.textAreaHeight = .dynamic(initialTextViewHeight: 44) - }.cellUpdate { (cell, _) in - cell.textView.textAlignment = .center - cell.textView.isSelectable = false - cell.textView.isEditable = false - - let parser = MarkdownParser(font: UIFont.systemFont(ofSize: UIFont.systemFontSize), color: UIColor.adamant.primary) - - let style = NSMutableParagraphStyle() - style.alignment = NSTextAlignment.center - - let mutableText = NSMutableAttributedString(attributedString: parser.parse(String.adamant.pkGenerator.alert)) - mutableText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: NSRange(location: 0, length: mutableText.length)) - - cell.textView.attributedText = mutableText - } - - let passphraseRow = PasswordRow { - $0.placeholder = String.adamant.qrGenerator.passphrasePlaceholder - $0.tag = Rows.passphrase.tag - $0.cell.textField.enablePasswordToggle() - } - - let generateButton = ButtonRow { - $0.title = String.adamant.pkGenerator.generateButton - $0.tag = Rows.generateButton.tag - }.onCellSelection { [weak self] (_, row) in - guard let row: PasswordRow = self?.form.rowBy(tag: Rows.passphrase.tag), - let passphrase = row.value, - AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) - else { - self?.dialogService.showToastMessage(String.adamant.qrGenerator.wrongPassphraseError) - return - } - - self?.generatePKKeys(for: passphrase) - } - - passphraseSection.append(contentsOf: [alertRow, passphraseRow, generateButton]) - - form.append(contentsOf: [pkSection, passphraseSection]) - - setColors() - } - - // MARK: - Other - - func setColors() { - view.backgroundColor = UIColor.adamant.secondBackgroundColor - tableView.backgroundColor = .clear - } - - // MARK: - PrivateKey tools - private func generatePKKeys(for passphrase: String) { - guard let section = form.sectionBy(tag: Sections.privateKeys.tag) else { - return - } - - section.removeAll() - - let passphraseLower = passphrase.lowercased() - - guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphraseLower) else { - dialogService.showToastMessage(String.adamant.qrGenerator.wrongPassphraseError) - return - } - - var index = 0 - var rows = [LabelRow]() - for case let generator as PrivateKeyGenerator in walletServiceCompose.getWallets().map({ $0.core }) { - guard let privateKey = generator.generatePrivateKeyFor(passphrase: passphraseLower) else { - continue - } - - let row = LabelRow { - $0.tag = "row\(index)" - index += 1 - $0.cell.imageView?.tintColor = UIColor.adamant.tableRowIcons - $0.cell.selectionStyle = .gray - - $0.title = generator.rowTitle - $0.value = privateKey - let imageSize = CGSize(width: 25, height: 25) - $0.cell.imageView?.image = generator.rowImage? - .withRenderingMode(.alwaysTemplate) - .imageResized(to: imageSize) - .withTintColor(UIColor.adamant.tableRowIcons) - }.cellUpdate { (cell, _) in - cell.accessoryType = .disclosureIndicator - }.onCellSelection { [weak self] (cell, row) in - guard let passphrase = row.value, let dialogService = self?.dialogService else { - return - } - - let encodedPassphrase = AdamantUriTools.encode( - request: AdamantUri.passphrase(passphrase: passphrase) - ) - - dialogService.presentShareAlertFor( - string: passphrase, - types: [ - .copyToPasteboard, - .share, - .generateQr( - encodedContent: encodedPassphrase, - sharingTip: nil, - withLogo: false - ) - ], - excludedActivityTypes: ShareContentType.passphrase.excludedActivityTypes, - animated: true, - from: cell, - completion: { - guard let indexPath = row.indexPath, - let tableView = self?.tableView - else { return } - - tableView.deselectRow(at: indexPath, animated: true) - } - ) - } - - rows.append(row) - } - - showKeysSection = rows.count > 0 - section.append(contentsOf: rows) - section.evaluateHidden() - } -} diff --git a/Adamant/Modules/Settings/PrivateKeyGenerator.swift b/Adamant/Modules/Settings/PrivateKeyGenerator.swift index 96ec694b1..c7415ad4b 100644 --- a/Adamant/Modules/Settings/PrivateKeyGenerator.swift +++ b/Adamant/Modules/Settings/PrivateKeyGenerator.swift @@ -8,8 +8,15 @@ import UIKit +enum KeyFormat: String { + case WIF + case HEX +} + protocol PrivateKeyGenerator { var rowTitle: String { get } var rowImage: UIImage? { get } + var keyFormat: KeyFormat { get } + func generatePrivateKeyFor(passphrase: String) -> String? } diff --git a/Adamant/Modules/Settings/SettingsFactory.swift b/Adamant/Modules/Settings/SettingsFactory.swift index 98a27efa8..e891df930 100644 --- a/Adamant/Modules/Settings/SettingsFactory.swift +++ b/Adamant/Modules/Settings/SettingsFactory.swift @@ -30,13 +30,6 @@ struct SettingsFactory { return c } - func makePKGeneratorVC() -> UIViewController { - PKGeneratorViewController( - dialogService: assembler.resolve(DialogService.self)!, - walletServiceCompose: assembler.resolve(WalletServiceCompose.self)! - ) - } - func makeAboutVC(screensFactory: ScreensFactory) -> UIViewController { AboutViewController( accountService: assembler.resolve(AccountService.self)!, diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 67ef465c3..d45a5043a 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -782,6 +782,8 @@ extension BtcWalletService: PrivateKeyGenerator { return .asset(named: "bitcoin_wallet_row") } + var keyFormat: KeyFormat { .WIF } + func generatePrivateKeyFor(passphrase: String) -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 3f5835263..f14fd660f 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -622,6 +622,8 @@ extension DashWalletService: PrivateKeyGenerator { return .asset(named: "dash_wallet_row") } + var keyFormat: KeyFormat { .WIF } + func generatePrivateKeyFor(passphrase: String) -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), let privateKeyData = passphrase.data(using: .utf8)?.sha256() else { return nil diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index c62d90c66..0fbb2eddd 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -714,6 +714,8 @@ extension DogeWalletService: PrivateKeyGenerator { return .asset(named: "doge_wallet_row") } + var keyFormat: KeyFormat { .WIF } + func generatePrivateKeyFor(passphrase: String) -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), let privateKeyData = passphrase.data(using: .utf8)?.sha256() else { return nil diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 18e4026a4..6b1fcef38 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -842,6 +842,8 @@ extension EthWalletService: PrivateKeyGenerator { return .asset(named: "ethereum_wallet_row") } + var keyFormat: KeyFormat { .HEX } + func generatePrivateKeyFor(passphrase: String) -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) else { return nil diff --git a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift index 63db98107..f01b19554 100644 --- a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift +++ b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService+WalletCore.swift @@ -77,6 +77,8 @@ extension KlyWalletService: PrivateKeyGenerator { .asset(named: "klayr_wallet_row") } + var keyFormat: KeyFormat { .HEX } + func generatePrivateKeyFor(passphrase: String) -> String? { guard AdamantUtilities.validateAdamantPassphrase(passphrase), let keypair = try? LiskKit.Crypto.keyPair( diff --git a/Adamant/SharedViews/AdamantSecureField.swift b/Adamant/SharedViews/AdamantSecureField.swift new file mode 100644 index 000000000..8844b1f12 --- /dev/null +++ b/Adamant/SharedViews/AdamantSecureField.swift @@ -0,0 +1,50 @@ +// +// AdamantSecureField.swift +// Adamant +// +// Created by Andrew G on 28.11.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import SwiftUI + +struct AdamantSecureField: UIViewRepresentable { + let placeholder: String? + let text: Binding + + func makeUIView(context _: Context) -> _View { .init() } + + func updateUIView(_ view: _View, context _: Context) { + view.text = text.wrappedValue + view.placeholder = placeholder + view.onChanged = { [text] in text.wrappedValue = $0 ?? .empty } + } +} + +extension AdamantSecureField { + final class _View: UITextField { + var onChanged: (String?) -> Void = { _ in } + + override init(frame: CGRect) { + super.init(frame: frame) + configure() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configure() + } + } +} + +private extension AdamantSecureField._View { + func configure() { + isSecureTextEntry = true + enablePasswordToggle() + addTarget(self, action: #selector(_onChanged), for: .editingChanged) + } + + @objc func _onChanged() { + onChanged(text) + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index db1bd67eb..98ad666b7 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -893,6 +893,9 @@ /* PrivateKeyGenerator: scene title */ "PkGeneratorScene.Title" = "PrivateKey Generator"; +/* PrivateKeyGenerator: key format */ +"PkGeneratorScene.KeyFormat" = "Format: %@"; + /* PrivateKeyGenerator: Security alert. Keep your passphrase safe */ "PkGeneratorScene.Alert" = "ADAMANT account includes crypto wallets. All of them are connected with your Passphrase, and only you have access to these wallets. You can export private key to use them in other applications. Store keys in secure place."; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index d6a39a650..0d17be684 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -877,6 +877,9 @@ /* PrivateKeyGenerator: scene title */ "PkGeneratorScene.Title" = "Private Key Generator"; +/* PrivateKeyGenerator: key format */ +"PkGeneratorScene.KeyFormat" = "Format: %@"; + /* PrivateKeyGenerator: Security alert. Keep your passphrase safe */ "PkGeneratorScene.Alert" = "ADAMANT account includes crypto wallets. All of them are connected with your Passphrase, and only you have access to these wallets. You can export private key to use them in other applications. Store keys in secure place."; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index d7941cc36..a50a3f859 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -877,6 +877,9 @@ /* PrivateKeyGenerator: scene title */ "PkGeneratorScene.Title" = "Генератор Приватных ключей"; +/* PrivateKeyGenerator: key format */ +"PkGeneratorScene.KeyFormat" = "Формат: %@"; + /* PrivateKeyGenerator: Security alert. Keep your passphrase safe */ "PkGeneratorScene.Alert" = "В аккаунте АДАМАНТа несколько встроенных криптокошельков. Все они объединены вашей пассфразой, и только у вас есть доступ к ним. Вы можете экспортировать приватные ключи, чтобы использовать эти кошельки в других приложениях. Храните ключи в надежном месте."; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 5c2e72c19..105a23fa6 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -877,6 +877,9 @@ /* PrivateKeyGenerator: scene title */ "PkGeneratorScene.Title" = "私钥生成器"; +/* PrivateKeyGenerator: key format */ +"PkGeneratorScene.KeyFormat" = "Format: %@"; + /* PrivateKeyGenerator: Security alert. Keep your passphrase safe */ "PkGeneratorScene.Alert" = "ADAMANT帐户包括加密钱包。所有这些钱包都与您的密码连接,只有您可以访问这些钱包。您可以导出私钥以在其他应用程序中使用它们。将密钥存储在安全的地方。"; From d1cc86606d7c4f9a5a8e9ad6f6138aa1d2a2646b Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Fri, 29 Nov 2024 13:53:29 -0300 Subject: [PATCH 2/2] [trello.com/c/R8cyJtgT] Secure field resizing fix --- Adamant/SharedViews/AdamantSecureField.swift | 25 +++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Adamant/SharedViews/AdamantSecureField.swift b/Adamant/SharedViews/AdamantSecureField.swift index 8844b1f12..a73991a0e 100644 --- a/Adamant/SharedViews/AdamantSecureField.swift +++ b/Adamant/SharedViews/AdamantSecureField.swift @@ -8,7 +8,19 @@ import SwiftUI -struct AdamantSecureField: UIViewRepresentable { +struct AdamantSecureField: View { + let placeholder: String? + let text: Binding + + var body: some View { + GeometryReader { geometry in + _AdamantSecureField(placeholder: placeholder, text: text) + .frame(maxWidth: geometry.frame(in: .local).size.width) + } + } +} + +private struct _AdamantSecureField: UIViewRepresentable { let placeholder: String? let text: Binding @@ -21,10 +33,17 @@ struct AdamantSecureField: UIViewRepresentable { } } -extension AdamantSecureField { +extension _AdamantSecureField { final class _View: UITextField { var onChanged: (String?) -> Void = { _ in } + override var intrinsicContentSize: CGSize { + .init( + width: UIView.noIntrinsicMetric, + height: super.intrinsicContentSize.height + ) + } + override init(frame: CGRect) { super.init(frame: frame) configure() @@ -37,7 +56,7 @@ extension AdamantSecureField { } } -private extension AdamantSecureField._View { +private extension _AdamantSecureField._View { func configure() { isSecureTextEntry = true enablePasswordToggle()