From 2956c2ed76197d121f3a93f48b646c5deed07ab6 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 20 Jun 2019 13:21:58 +0200 Subject: [PATCH] Adding a new scene `ReviewTransactionBeforeSigning` which presents a summary of the payment before proceeding with signing. --- .../Models/Manual/Payment/Payment.swift | 1 + .../Generated/SwiftGen/L10n-Constants.swift | 36 +++- .../Localization/en.lproj/Localizable.strings | 17 +- .../Extensions/UIKit/UIFont_Extension.swift | 2 + .../PrepareTransactionView.swift | 14 +- .../PrepareTransactionViewModel.swift | 14 +- .../ReviewTransactionBeforeSigning.swift | 32 ++++ .../ReviewTransactionBeforeSigningView.swift | 161 ++++++++++++++++++ ...iewTransactionBeforeSigningViewModel.swift | 112 ++++++++++++ .../SignTransaction.swift | 0 .../SignTransactionView.swift | 0 .../SignTransactionViewModel.swift | 0 .../PollTransactionStatusController.swift | 0 .../PollTransactionStatusView.swift | 0 .../PollTransactionStatusViewModel.swift | 0 .../1_Main/A_Send/SendCoordinator.swift | 15 +- Source/Views/Components/TitledValueView.swift | 5 +- Zhip.xcodeproj/project.pbxproj | 32 +++- 18 files changed, 413 insertions(+), 28 deletions(-) create mode 100644 Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigning.swift create mode 100644 Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningView.swift create mode 100644 Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningViewModel.swift rename Source/Scenes/1_Main/1_Main/A_Send/{2_SignTransaction => 3_SignTransaction}/SignTransaction.swift (100%) rename Source/Scenes/1_Main/1_Main/A_Send/{2_SignTransaction => 3_SignTransaction}/SignTransactionView.swift (100%) rename Source/Scenes/1_Main/1_Main/A_Send/{2_SignTransaction => 3_SignTransaction}/SignTransactionViewModel.swift (100%) rename Source/Scenes/1_Main/1_Main/A_Send/{3_PollTransactionStatus => 4_PollTransactionStatus}/PollTransactionStatusController.swift (100%) rename Source/Scenes/1_Main/1_Main/A_Send/{3_PollTransactionStatus => 4_PollTransactionStatus}/PollTransactionStatusView.swift (100%) rename Source/Scenes/1_Main/1_Main/A_Send/{3_PollTransactionStatus => 4_PollTransactionStatus}/PollTransactionStatusViewModel.swift (100%) diff --git a/Pods/Zesame/Source/Models/Manual/Payment/Payment.swift b/Pods/Zesame/Source/Models/Manual/Payment/Payment.swift index 72ad8cfe..c3e24330 100644 --- a/Pods/Zesame/Source/Models/Manual/Payment/Payment.swift +++ b/Pods/Zesame/Source/Models/Manual/Payment/Payment.swift @@ -47,6 +47,7 @@ public struct Payment { } public extension Payment { + static func estimatedTotalTransactionFee(gasPrice: GasPrice, gasLimit: GasLimit = .defaultGasLimit) throws -> Qa { return Qa(qa: Qa.Magnitude(gasLimit) * gasPrice.qa) } diff --git a/Source/Application/Generated/SwiftGen/L10n-Constants.swift b/Source/Application/Generated/SwiftGen/L10n-Constants.swift index f2616e99..19a249b6 100644 --- a/Source/Application/Generated/SwiftGen/L10n-Constants.swift +++ b/Source/Application/Generated/SwiftGen/L10n-Constants.swift @@ -380,8 +380,8 @@ internal enum L10n { internal enum Button { /// Max internal static let maxAmount = L10n.tr("Localizable", "Scene.PrepareTransaction.Button.MaxAmount") - /// Send - internal static let send = L10n.tr("Localizable", "Scene.PrepareTransaction.Button.Send") + /// Review Payment + internal static let reviewPayment = L10n.tr("Localizable", "Scene.PrepareTransaction.Button.ReviewPayment") } internal enum Field { /// Amount in %@ @@ -406,8 +406,6 @@ internal enum L10n { internal static func gasInSmallUnits(_ p1: String) -> String { return L10n.tr("Localizable", "Scene.PrepareTransaction.Label.GasInSmallUnits", p1) } - /// Transaction Id - internal static let transactionId = L10n.tr("Localizable", "Scene.PrepareTransaction.Label.TransactionId") } internal enum Labels { internal enum Balance { @@ -484,6 +482,36 @@ internal enum L10n { internal static let privateKey = L10n.tr("Localizable", "Scene.RestoreWallet.Segment.PrivateKey") } } + internal enum ReviewTransactionBeforeSigning { + /// Summary + internal static let title = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Title") + internal enum Button { + /// To signing + internal static let hasReviewedProceedToSigning = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Button.HasReviewedProceedToSigning") + } + internal enum Checkbox { + /// I've reviewed the payment and understand I'm responsible for any loss if anything is incorrect. + internal static let hasReviewedPayment = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Checkbox.HasReviewedPayment") + } + internal enum Label { + /// Amount + internal static let amount = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Label.Amount") + /// Amount to recipient + internal static let amountToSend = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Label.AmountToSend") + /// Recipient + internal static let recipient = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Label.Recipient") + /// Transaction total cost + internal static let totalCostOfTransaction = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Label.TotalCostOfTransaction") + /// Transaction fee + internal static let transactionFee = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Label.TransactionFee") + internal enum Address { + /// Address on new bech32 format + internal static let bech32 = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Label.Address.Bech32") + /// Address on old base16 format + internal static let legacy = L10n.tr("Localizable", "Scene.ReviewTransactionBeforeSigning.Label.Address.Legacy") + } + } + } internal enum ScanQRCode { /// Scan QR internal static let title = L10n.tr("Localizable", "Scene.ScanQRCode.Title") diff --git a/Source/Application/Localization/en.lproj/Localizable.strings b/Source/Application/Localization/en.lproj/Localizable.strings index f8e0ec33..e9ef47ec 100644 --- a/Source/Application/Localization/en.lproj/Localizable.strings +++ b/Source/Application/Localization/en.lproj/Localizable.strings @@ -170,8 +170,8 @@ "Scene.PrepareTransaction.Label.GasInSmallUnits" = "Gas price is measured in %@"; "Scene.PrepareTransaction.Field.GasPrice" = "Gas price (min %@)"; "Scene.PrepareTransaction.Field.EncryptionPassword" = "Encryption password"; -"Scene.PrepareTransaction.Button.Send" = "Send"; -"Scene.PrepareTransaction.Label.TransactionId" = "Transaction Id"; +"Scene.PrepareTransaction.Button.ReviewPayment" = "Review Payment"; +//"Scene.PrepareTransaction.Label.TransactionId" = "Transaction Id"; "Scene.PrepareTransaction.Labels.Balance.Title" = "Current balance"; "Scene.PrepareTransaction.Label.CostOfTransactionInZil" = "Transaction fee: %@"; @@ -180,6 +180,19 @@ "Scene.ScanQRCode.Event.Toast.IncompatibleQrCode.Title" = "QR code contains no compatible Zilliqa address"; "Scene.ScanQRCode.Event.Toast.IncompatibleQrCode.Dismiss" = "Dismiss"; +// MARK: ReviewTransaction +"Scene.ReviewTransactionBeforeSigning.Title" = "Summary"; +"Scene.ReviewTransactionBeforeSigning.Label.Recipient" = "Recipient"; +"Scene.ReviewTransactionBeforeSigning.Label.Address.Legacy" = "Address on old base16 format"; +"Scene.ReviewTransactionBeforeSigning.Label.Address.Bech32" = "Address on new bech32 format"; +"Scene.ReviewTransactionBeforeSigning.Label.Amount" = "Amount"; +"Scene.ReviewTransactionBeforeSigning.Label.AmountToSend" = "Amount to recipient"; +"Scene.ReviewTransactionBeforeSigning.Label.TransactionFee" = "Transaction fee"; +"Scene.ReviewTransactionBeforeSigning.Label.TotalCostOfTransaction" = "Transaction total cost"; +"Scene.ReviewTransactionBeforeSigning.Checkbox.HasReviewedPayment" = "I've reviewed the payment and understand I'm responsible for any loss if anything is incorrect. "; +"Scene.ReviewTransactionBeforeSigning.Button.HasReviewedProceedToSigning" = "To signing"; + + // MARK: SignTransaction "Scene.SignTransaction.Label.SignTransactionWithEncryptionPassword" = "Confirm transaction with your password"; "Scene.SignTransaction.Field.EncryptionPassword" = "Encryption password"; diff --git a/Source/Extensions/UIKit/UIFont_Extension.swift b/Source/Extensions/UIKit/UIFont_Extension.swift index c0faf122..c6065c91 100644 --- a/Source/Extensions/UIKit/UIFont_Extension.swift +++ b/Source/Extensions/UIKit/UIFont_Extension.swift @@ -26,6 +26,8 @@ import UIKit extension UIFont { // For `UITextField` floating placeholder static let hint = Font(.𝟙𝟞, .medium).make() + static let valueTitle = Font(.𝟙𝟞, .regular).make() + static let value = Font(.𝟙𝟠, .bold).make() /// For bread text static let body = Font(.𝟙𝟠, .regular).make() diff --git a/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionView.swift b/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionView.swift index bae0fed4..0c23cb42 100644 --- a/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionView.swift +++ b/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionView.swift @@ -41,7 +41,7 @@ final class PrepareTransactionView: ScrollableStackViewOwner, PullToRefreshCapab private lazy var maxAmounButton = amountToSendField.addBottomAlignedButton(titled: €.Button.maxAmount) private lazy var gasMeasuredInSmallUnitsLabel = UILabel() private lazy var gasPriceField = FloatingLabelTextField() - private lazy var sendButton = UIButton() + private lazy var toReviewButton = UIButton() private lazy var costOfTransactionLabel = UILabel() // MARK: - StackViewStyling @@ -52,7 +52,7 @@ final class PrepareTransactionView: ScrollableStackViewOwner, PullToRefreshCapab gasPriceField, costOfTransactionLabel, .spacer, - sendButton + toReviewButton ] override func setup() { @@ -73,7 +73,7 @@ extension PrepareTransactionView: ViewModelled { viewModel.amountPlaceholder --> amountToSendField.rx.placeholder, viewModel.amount --> amountToSendField.rx.text, viewModel.recipient --> recipientAddressField.rx.text, - viewModel.isSendButtonEnabled --> sendButton.rx.isEnabled, + viewModel.isReviewButtonEnabled --> toReviewButton.rx.isEnabled, viewModel.balance --> balanceValueLabel.rx.text, viewModel.recipientAddressValidation --> recipientAddressField.rx.validation, viewModel.amountValidation --> amountToSendField.rx.validation, @@ -89,7 +89,7 @@ extension PrepareTransactionView: ViewModelled { pullToRefreshTrigger: rx.pullToRefreshTrigger, scanQRTrigger: scanQRButton.rx.tap.asDriver(), maxAmountTrigger: maxAmounButton.rx.tap.asDriver(), - sendTrigger: sendButton.rx.tap.asDriver(), + toReviewTrigger: toReviewButton.rx.tap.asDriver(), recepientAddress: recipientAddressField.rx.text.orEmpty.asDriver().skip(1), didEndEditingRecipientAddress: recipientAddressField.rx.didEndEditing, @@ -138,8 +138,8 @@ private extension PrepareTransactionView { gasPriceField.withStyle(.number) - sendButton.withStyle(.primary) { - $0.title(€.Button.send) + toReviewButton.withStyle(.primary) { + $0.title(€.Button.reviewPayment) .disabled() } } @@ -150,7 +150,7 @@ private extension PrepareTransactionView { func prefillValuesForDebugBuilds() { #if DEBUG recipientAddressField.text = "zil175grxdeqchwnc0qghj8qsh5vnqwww353msqj82" - amountToSendField.text = Int.random(in: 100...500).description + amountToSendField.text = Int.random(in: 1...5).description gasPriceField.text = Int.random(in: 1000...2000).description DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { [unowned self] in diff --git a/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionViewModel.swift b/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionViewModel.swift index 2e10442b..392d4678 100644 --- a/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionViewModel.swift +++ b/Source/Scenes/1_Main/1_Main/A_Send/1_PrepareTransaction/0_PrepareTransaction/PrepareTransactionViewModel.swift @@ -30,7 +30,7 @@ import Zesame // MARK: - PrepareTransactionUserAction enum PrepareTransactionUserAction { case cancel - case signPayment(Payment) + case reviewPayment(Payment) case scanQRCode } @@ -189,8 +189,8 @@ final class PrepareTransactionViewModel: BaseViewModel< .do(onNext: { userIntends(to: .scanQRCode) }) .drive(), - input.fromView.sendTrigger.withLatestFrom(payment.filterNil()) - .do(onNext: { userIntends(to: .signPayment($0)) }) + input.fromView.toReviewTrigger.withLatestFrom(payment.filterNil()) + .do(onNext: { userIntends(to: .reviewPayment($0)) }) .drive() ] @@ -213,7 +213,7 @@ final class PrepareTransactionViewModel: BaseViewModel< } }) - let isSendButtonEnabled = payment.map { $0 != nil } + let isReviewButtonEnabled = payment.map { $0 != nil } let gasPricePlaceholder = Driver.just(GasPrice.min).map { €.Field.gasPrice(formatter.format(amount: $0, in: .li, formatThousands: true, showUnit: true)) } @@ -237,7 +237,7 @@ final class PrepareTransactionViewModel: BaseViewModel< return Output( refreshControlLastUpdatedTitle: refreshControlLastUpdatedTitle, isFetchingBalance: activityIndicator.asDriver(), - isSendButtonEnabled: isSendButtonEnabled, + isReviewButtonEnabled: isReviewButtonEnabled, balance: balanceFormatted, recipient: recipientFormatted, @@ -271,7 +271,7 @@ extension PrepareTransactionViewModel { let pullToRefreshTrigger: Driver let scanQRTrigger: Driver let maxAmountTrigger: Driver - let sendTrigger: Driver + let toReviewTrigger: Driver let recepientAddress: Driver let didEndEditingRecipientAddress: Driver @@ -286,7 +286,7 @@ extension PrepareTransactionViewModel { struct Output { let refreshControlLastUpdatedTitle: Driver let isFetchingBalance: Driver - let isSendButtonEnabled: Driver + let isReviewButtonEnabled: Driver let balance: Driver let recipient: Driver diff --git a/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigning.swift b/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigning.swift new file mode 100644 index 00000000..193a72ea --- /dev/null +++ b/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigning.swift @@ -0,0 +1,32 @@ +// +// MIT License +// +// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +private typealias € = L10n.Scene.ReviewTransactionBeforeSigning +final class ReviewTransactionBeforeSigning: Scene {} + +extension ReviewTransactionBeforeSigning { + static let title = €.title +} diff --git a/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningView.swift b/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningView.swift new file mode 100644 index 00000000..9ac92385 --- /dev/null +++ b/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningView.swift @@ -0,0 +1,161 @@ +// +// MIT License +// +// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import UIKit +import RxSwift +import RxCocoa + +private typealias € = L10n.Scene.ReviewTransactionBeforeSigning + +final class ReviewTransactionBeforeSigningView: ScrollableStackViewOwner { + + private lazy var recipientAddressesLabel = UILabel() + private lazy var recipientLegacyAddressView = TitledValueView() + private lazy var recipientBech32AddressView = TitledValueView() + private lazy var recipientViewsStackView = UIStackView(arrangedSubviews: [recipientAddressesLabel, recipientLegacyAddressView, recipientBech32AddressView]) + + private lazy var amountLabel = UILabel() + private lazy var amountToSendView = TitledValueView() + private lazy var transactionFeeView = TitledValueView() + private lazy var totalCostOfTransactionView = TitledValueView() + private lazy var amountViewsStackView = UIStackView(arrangedSubviews: [ + amountLabel, + amountToSendView, + transactionFeeView, + totalCostOfTransactionView + ]) + + private lazy var hasReviewedPaymentCheckBox = CheckboxWithLabel() + private lazy var acceptPaymentProceedToSigningButton = UIButton() + + lazy var stackViewStyle: UIStackView.Style = [ + recipientViewsStackView, + amountViewsStackView, + hasReviewedPaymentCheckBox, + acceptPaymentProceedToSigningButton, + .spacer + ] + + override func setup() { + setupSubviews() + } +} + +extension ReviewTransactionBeforeSigningView: ViewModelled { + typealias ViewModel = ReviewTransactionBeforeSigningViewModel + + var inputFromView: InputFromView { + return InputFromView( + isHasReviewedPaymentCheckboxChecked: hasReviewedPaymentCheckBox.rx.isChecked.asDriverOnErrorReturnEmpty(), + hasReviewedNowProceedWithSigningTrigger: acceptPaymentProceedToSigningButton.rx.tap.asDriver() + ) + } + + func populate(with viewModel: ViewModel.Output) -> [Disposable] { + return [ + viewModel.isHasReviewedNowProceedWithSigningButtonEnabled --> acceptPaymentProceedToSigningButton.rx.isEnabled, + viewModel.recipientBech32Address --> recipientBech32AddressView.rx.value, + viewModel.recipientLegacyAddress --> recipientLegacyAddressView.rx.value, + viewModel.amountToPay --> amountToSendView.rx.value, + viewModel.paymentFee --> transactionFeeView.rx.value, + viewModel.totalCost --> totalCostOfTransactionView.rx.value + ] + } +} + +private extension ReviewTransactionBeforeSigningView { + + // swiftlint:disable:next function_body_length + func setupSubviews() { + + func setup(titledValueView: TitledValueView, customizeTitleStyle: @escaping ((UILabel.Style) -> (UILabel.Style))) { + + let titleStyle: UILabel.Style = UILabel.Style.body.font(UIFont.valueTitle) + let valueStyle: UITextView.Style = UITextView.Style.nonSelectable + .isScrollEnabled(false) + .textAlignment(.natural) + .textColor(.teal) + .font(.value) + + let stackViewStyle = UIStackView.Style( + spacing: -2, + layoutMargins: .zero, + isLayoutMarginsRelativeArrangement: false + ) + + titledValueView.withStyles( + forTitle: titleStyle, + forValue: valueStyle, + forStackView: stackViewStyle, + customizeTitleStyle: customizeTitleStyle) + } + + let labelStyleForHeaders: UILabel.Style = .header + let stackViewStyle: UIStackView.Style = .vertical + + // MARK: - Recipient views + recipientAddressesLabel.withStyle(labelStyleForHeaders) { + $0.text(€.Label.recipient) + } + + setup(titledValueView: recipientLegacyAddressView) { + $0.text(€.Label.Address.legacy) + } + + setup(titledValueView: recipientBech32AddressView) { + $0.text(€.Label.Address.bech32) + } + + recipientViewsStackView.withStyle(stackViewStyle) + + // MARK: - Amount views + amountLabel.withStyle(labelStyleForHeaders) { + $0.text(€.Label.amount) + } + + setup(titledValueView: amountToSendView) { + $0.text(€.Label.amountToSend) + } + + setup(titledValueView: transactionFeeView) { + $0.text(€.Label.transactionFee) + } + + setup(titledValueView: totalCostOfTransactionView) { + $0.text(€.Label.totalCostOfTransaction) + } + + amountViewsStackView.withStyle(stackViewStyle) + + // MARK: Checkbox and button + hasReviewedPaymentCheckBox.withStyle(.default) { + $0.text(€.Checkbox.hasReviewedPayment) + } + + acceptPaymentProceedToSigningButton.withStyle(.primary) { + $0.title(€.Button.hasReviewedProceedToSigning) + .disabled() + } + } +} diff --git a/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningViewModel.swift b/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningViewModel.swift new file mode 100644 index 00000000..ea7215e8 --- /dev/null +++ b/Source/Scenes/1_Main/1_Main/A_Send/2_ReviewTransaction/ReviewTransactionBeforeSigningViewModel.swift @@ -0,0 +1,112 @@ +// +// MIT License +// +// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import RxSwift +import RxCocoa +import Zesame + +enum ReviewTransactionBeforeSigningUserAction { + case acceptPaymentProceedWithSigning(Payment) +} + +final class ReviewTransactionBeforeSigningViewModel: BaseViewModel< + ReviewTransactionBeforeSigningUserAction, + ReviewTransactionBeforeSigningViewModel.InputFromView, + ReviewTransactionBeforeSigningViewModel.Output +> { + + private let paymentToReview: Payment + + init(paymentToReview: Payment) { //}, walletUseCase: WalletUseCase, transactionUseCase: TransactionsUseCase) { + self.paymentToReview = paymentToReview + } + + // swiftlint:disable:next function_body_length + override func transform(input: Input) -> Output { + func userDid(_ userAction: NavigationStep) { + navigator.next(userAction) + } + + // MARK: - Validate input + bag <~ [ + input.fromView.hasReviewedNowProceedWithSigningTrigger.map { self.paymentToReview } + .do(onNext: { userDid(.acceptPaymentProceedWithSigning($0)) }) + .drive() + ] + + let payment = Driver.just(self.paymentToReview) + let recipientLegacyAddress = payment.map { $0.recipient } + let recipientBech32Address = payment.map { try? Bech32Address(ethStyleAddress: $0.recipient, network: network) }.filterNil() + + let amountFormatter = AmountFormatter() + + let amountToPay = payment.map { amountFormatter.format(amount: $0.amount, in: .zil, formatThousands: true, minFractionDigits: 2, showUnit: true) } + let paymentFee = payment.map { amountFormatter.format(amount: $0.transactionFee, in: .zil, formatThousands: false, minFractionDigits: 5, showUnit: true) } + let totalCost = payment.map { amountFormatter.format(amount: $0.totalCostInZil, in: .zil, formatThousands: true, minFractionDigits: 2, showUnit: true) } + + return Output( + isHasReviewedNowProceedWithSigningButtonEnabled: input.fromView.isHasReviewedPaymentCheckboxChecked, + recipientLegacyAddress: recipientLegacyAddress.map { $0.asString }, + recipientBech32Address: recipientBech32Address.map { $0.asString }, + amountToPay: amountToPay, + paymentFee: paymentFee, + totalCost: totalCost + ) + } + +} + +extension ReviewTransactionBeforeSigningViewModel { + + struct InputFromView { + let isHasReviewedPaymentCheckboxChecked: Driver + let hasReviewedNowProceedWithSigningTrigger: Driver + } + + struct Output { + let isHasReviewedNowProceedWithSigningButtonEnabled: Driver + let recipientLegacyAddress: Driver + let recipientBech32Address: Driver + let amountToPay: Driver + let paymentFee: Driver + let totalCost: Driver + } +} + +private extension Payment { + var transactionFee: Qa { + return (try? Payment.estimatedTotalTransactionFee(gasPrice: gasPrice, gasLimit: gasLimit)) ?? gasPrice.asQa + } + + var totalCostInZil: ZilAmount { + if let estimatedTotal = try? Payment.estimatedTotalCostOfTransaction(amount: amount, gasPrice: gasPrice, gasLimit: gasLimit) { + return estimatedTotal + } else { + let totalInQa = amount.asQa + transactionFee + // swiftlint:disable:next force_try + return try! ZilAmount(qa: totalInQa) + } + } +} diff --git a/Source/Scenes/1_Main/1_Main/A_Send/2_SignTransaction/SignTransaction.swift b/Source/Scenes/1_Main/1_Main/A_Send/3_SignTransaction/SignTransaction.swift similarity index 100% rename from Source/Scenes/1_Main/1_Main/A_Send/2_SignTransaction/SignTransaction.swift rename to Source/Scenes/1_Main/1_Main/A_Send/3_SignTransaction/SignTransaction.swift diff --git a/Source/Scenes/1_Main/1_Main/A_Send/2_SignTransaction/SignTransactionView.swift b/Source/Scenes/1_Main/1_Main/A_Send/3_SignTransaction/SignTransactionView.swift similarity index 100% rename from Source/Scenes/1_Main/1_Main/A_Send/2_SignTransaction/SignTransactionView.swift rename to Source/Scenes/1_Main/1_Main/A_Send/3_SignTransaction/SignTransactionView.swift diff --git a/Source/Scenes/1_Main/1_Main/A_Send/2_SignTransaction/SignTransactionViewModel.swift b/Source/Scenes/1_Main/1_Main/A_Send/3_SignTransaction/SignTransactionViewModel.swift similarity index 100% rename from Source/Scenes/1_Main/1_Main/A_Send/2_SignTransaction/SignTransactionViewModel.swift rename to Source/Scenes/1_Main/1_Main/A_Send/3_SignTransaction/SignTransactionViewModel.swift diff --git a/Source/Scenes/1_Main/1_Main/A_Send/3_PollTransactionStatus/PollTransactionStatusController.swift b/Source/Scenes/1_Main/1_Main/A_Send/4_PollTransactionStatus/PollTransactionStatusController.swift similarity index 100% rename from Source/Scenes/1_Main/1_Main/A_Send/3_PollTransactionStatus/PollTransactionStatusController.swift rename to Source/Scenes/1_Main/1_Main/A_Send/4_PollTransactionStatus/PollTransactionStatusController.swift diff --git a/Source/Scenes/1_Main/1_Main/A_Send/3_PollTransactionStatus/PollTransactionStatusView.swift b/Source/Scenes/1_Main/1_Main/A_Send/4_PollTransactionStatus/PollTransactionStatusView.swift similarity index 100% rename from Source/Scenes/1_Main/1_Main/A_Send/3_PollTransactionStatus/PollTransactionStatusView.swift rename to Source/Scenes/1_Main/1_Main/A_Send/4_PollTransactionStatus/PollTransactionStatusView.swift diff --git a/Source/Scenes/1_Main/1_Main/A_Send/3_PollTransactionStatus/PollTransactionStatusViewModel.swift b/Source/Scenes/1_Main/1_Main/A_Send/4_PollTransactionStatus/PollTransactionStatusViewModel.swift similarity index 100% rename from Source/Scenes/1_Main/1_Main/A_Send/3_PollTransactionStatus/PollTransactionStatusViewModel.swift rename to Source/Scenes/1_Main/1_Main/A_Send/4_PollTransactionStatus/PollTransactionStatusViewModel.swift diff --git a/Source/Scenes/1_Main/1_Main/A_Send/SendCoordinator.swift b/Source/Scenes/1_Main/1_Main/A_Send/SendCoordinator.swift index dac3bd7c..b365d916 100644 --- a/Source/Scenes/1_Main/1_Main/A_Send/SendCoordinator.swift +++ b/Source/Scenes/1_Main/1_Main/A_Send/SendCoordinator.swift @@ -94,7 +94,7 @@ private extension SendCoordinator { switch userIntendsTo { case .cancel: self.finish() case .scanQRCode: self.toScanQRCode() - case .signPayment(let payment): self.toSignPayment(payment) + case .reviewPayment(let payment): self.toReviewPaymentBeforeSigning(payment) } } } @@ -111,6 +111,19 @@ private extension SendCoordinator { } } } + + func toReviewPaymentBeforeSigning(_ payment: Payment) { + let viewModel = ReviewTransactionBeforeSigningViewModel( + paymentToReview: payment + ) + + push(scene: ReviewTransactionBeforeSigning.self, viewModel: viewModel) { [unowned self] userDid in + switch userDid { + case .acceptPaymentProceedWithSigning(let reviewedPayment): + self.toSignPayment(reviewedPayment) + } + } + } func toSignPayment(_ payment: Payment) { let viewModel = SignTransactionViewModel( diff --git a/Source/Views/Components/TitledValueView.swift b/Source/Views/Components/TitledValueView.swift index c6dcb7f5..f0d9d1c1 100644 --- a/Source/Views/Components/TitledValueView.swift +++ b/Source/Views/Components/TitledValueView.swift @@ -43,6 +43,7 @@ extension TitledValueView { func withStyles( forTitle titleStyle: UILabel.Style? = nil, forValue valueStyle: UITextView.Style? = nil, + forStackView stackViewStyle: UIStackView.Style? = nil, customizeTitleStyle: ((UILabel.Style) -> (UILabel.Style))? = nil ) { defer { isSetup = true } @@ -66,8 +67,10 @@ extension TitledValueView { layoutMargins: .zero, isLayoutMarginsRelativeArrangement: false ) + + let stackViewStyleUsed = stackViewStyle ?? defaultStackViewStyle - apply(style: defaultStackViewStyle) + apply(style: stackViewStyleUsed) [valueTextView, titleLabel].forEach { insertArrangedSubview($0, at: 0) } } diff --git a/Zhip.xcodeproj/project.pbxproj b/Zhip.xcodeproj/project.pbxproj index 6043bc30..3c948c36 100644 --- a/Zhip.xcodeproj/project.pbxproj +++ b/Zhip.xcodeproj/project.pbxproj @@ -307,6 +307,9 @@ E6414452225E76F3008C146D /* ConfirmWalletRemovalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4819BD8921C0CAEE00443F2A /* ConfirmWalletRemovalViewModel.swift */; }; E6622DFA22B512440086134D /* CharacterSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6622DF822B5123B0086134D /* CharacterSetTests.swift */; }; E682358522BB70BE0028F1EC /* AmountFromText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E682358422BB70BE0028F1EC /* AmountFromText.swift */; }; + E682358B22BB86620028F1EC /* ReviewTransactionBeforeSigningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E682358822BB86620028F1EC /* ReviewTransactionBeforeSigningViewModel.swift */; }; + E682358C22BB86620028F1EC /* ReviewTransactionBeforeSigningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E682358922BB86620028F1EC /* ReviewTransactionBeforeSigningView.swift */; }; + E682358D22BB86620028F1EC /* ReviewTransactionBeforeSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = E682358A22BB86620028F1EC /* ReviewTransactionBeforeSigning.swift */; }; E6942A4E2262180700778FF6 /* libRxCocoa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E6942A4D2262180700778FF6 /* libRxCocoa.a */; }; E6942A502262180700778FF6 /* libRxSwift.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E6942A4F2262180700778FF6 /* libRxSwift.a */; }; E6942A522262180700778FF6 /* libRxTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E6942A512262180700778FF6 /* libRxTest.a */; }; @@ -642,6 +645,9 @@ D072578D42806D1D0CB17AEE /* Pods-ZhipTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZhipTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ZhipTests/Pods-ZhipTests.release.xcconfig"; sourceTree = ""; }; E6622DF822B5123B0086134D /* CharacterSetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterSetTests.swift; sourceTree = ""; }; E682358422BB70BE0028F1EC /* AmountFromText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountFromText.swift; sourceTree = ""; }; + E682358822BB86620028F1EC /* ReviewTransactionBeforeSigningViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReviewTransactionBeforeSigningViewModel.swift; sourceTree = ""; }; + E682358922BB86620028F1EC /* ReviewTransactionBeforeSigningView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReviewTransactionBeforeSigningView.swift; sourceTree = ""; }; + E682358A22BB86620028F1EC /* ReviewTransactionBeforeSigning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReviewTransactionBeforeSigning.swift; sourceTree = ""; }; E6942A4D2262180700778FF6 /* libRxCocoa.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRxCocoa.a; sourceTree = BUILT_PRODUCTS_DIR; }; E6942A4F2262180700778FF6 /* libRxSwift.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRxSwift.a; sourceTree = BUILT_PRODUCTS_DIR; }; E6942A512262180700778FF6 /* libRxTest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRxTest.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -808,14 +814,14 @@ path = B_ConfirmWalletRemoval; sourceTree = ""; }; - 4819BD9221C2235500443F2A /* 3_PollTransactionStatus */ = { + 4819BD9221C2235500443F2A /* 4_PollTransactionStatus */ = { isa = PBXGroup; children = ( 4819BD9721C223B200443F2A /* PollTransactionStatusController.swift */, 4819BD9621C223B100443F2A /* PollTransactionStatusView.swift */, 4819BD9821C223B200443F2A /* PollTransactionStatusViewModel.swift */, ); - path = 3_PollTransactionStatus; + path = 4_PollTransactionStatus; sourceTree = ""; }; 4819BDAA21C27BEC00443F2A /* 0_PrepareTransaction */ = { @@ -1537,8 +1543,9 @@ children = ( 48ECF57A21A18F140078D659 /* SendCoordinator.swift */, 48ECF57621A18F140078D659 /* 1_PrepareTransaction */, - 48ECF57B21A18F140078D659 /* 2_SignTransaction */, - 4819BD9221C2235500443F2A /* 3_PollTransactionStatus */, + E682358722BB85E00028F1EC /* 2_ReviewTransaction */, + 48ECF57B21A18F140078D659 /* 3_SignTransaction */, + 4819BD9221C2235500443F2A /* 4_PollTransactionStatus */, ); path = A_Send; sourceTree = ""; @@ -1552,14 +1559,14 @@ path = 1_PrepareTransaction; sourceTree = ""; }; - 48ECF57B21A18F140078D659 /* 2_SignTransaction */ = { + 48ECF57B21A18F140078D659 /* 3_SignTransaction */ = { isa = PBXGroup; children = ( 48ECF57C21A18F140078D659 /* SignTransactionView.swift */, 48ECF57D21A18F140078D659 /* SignTransaction.swift */, 48ECF57E21A18F140078D659 /* SignTransactionViewModel.swift */, ); - path = 2_SignTransaction; + path = 3_SignTransaction; sourceTree = ""; }; 48ECF57F21A18F140078D659 /* C_Settings */ = { @@ -1874,6 +1881,16 @@ name = Pods; sourceTree = ""; }; + E682358722BB85E00028F1EC /* 2_ReviewTransaction */ = { + isa = PBXGroup; + children = ( + E682358A22BB86620028F1EC /* ReviewTransactionBeforeSigning.swift */, + E682358922BB86620028F1EC /* ReviewTransactionBeforeSigningView.swift */, + E682358822BB86620028F1EC /* ReviewTransactionBeforeSigningViewModel.swift */, + ); + path = 2_ReviewTransaction; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2171,6 +2188,7 @@ 487C19E6219A2FCD00308550 /* Weak.swift in Sources */, 487C19E4219A2F4A00308550 /* Array_Extension.swift in Sources */, E641444A225E76F3008C146D /* Settings.swift in Sources */, + E682358C22BB86620028F1EC /* ReviewTransactionBeforeSigningView.swift in Sources */, E635A4C7225D301B00124823 /* RestoreWalletUsingKeystoreViewModel.swift in Sources */, 48F37D6621918EA4008A2DA4 /* Dictionary_Extension.swift in Sources */, 48A640022186273300B08C67 /* WalletEncryptionPassphrase.swift in Sources */, @@ -2178,6 +2196,7 @@ 48053F3D21EBEF17008FCE13 /* FloatingLabelTextField+RightLeftView.swift in Sources */, 48ECF61C21A29FF20078D659 /* ClassIdentifiable.swift in Sources */, 483CE9D32194E9E300BED31C /* Navigator.swift in Sources */, + E682358B22BB86620028F1EC /* ReviewTransactionBeforeSigningViewModel.swift in Sources */, E6019A4D225E75020079DDE8 /* Main.swift in Sources */, E6414439225E76A4008C146D /* ReceiveCoordinator.swift in Sources */, 48C89FDB214331050063B0FA /* UseCaseProvider.swift in Sources */, @@ -2310,6 +2329,7 @@ 483DDEA9219B6448009D3F4C /* PincodeUseCase.swift in Sources */, 485AA29121DD303200E700C5 /* AmountFormatter.swift in Sources */, 48241B5121B15C740054ED8E /* Coordinating+Child+Start.swift in Sources */, + E682358D22BB86620028F1EC /* ReviewTransactionBeforeSigning.swift in Sources */, E635A4A9225D2FDB00124823 /* WarningERC20ViewModel.swift in Sources */, 480B0E9721CE2C8E00A72133 /* AppAppearance.swift in Sources */, 4805A7E521E952C5008D6778 /* Validation+CustomStringConvertible.swift in Sources */,