Show warnings for high/low/congested gas prices in actionsheet for transaction confirmation and editing gas option screen

pull/2378/head
Hwee-Boon Yar 4 years ago
parent 96f54db0b3
commit f68129ca86
  1. 21
      AlphaWallet/Assets.xcassets/gasWarning.imageset/Contents.json
  2. BIN
      AlphaWallet/Assets.xcassets/gasWarning.imageset/gasWarning.pdf
  3. 9
      AlphaWallet/Localization/en.lproj/Localizable.strings
  4. 9
      AlphaWallet/Localization/es.lproj/Localizable.strings
  5. 9
      AlphaWallet/Localization/ja.lproj/Localizable.strings
  6. 9
      AlphaWallet/Localization/ko.lproj/Localizable.strings
  7. 9
      AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings
  8. 1
      AlphaWallet/Settings/Types/Constants.swift
  9. 58
      AlphaWallet/Transfer/Controllers/TransactionConfigurator.swift
  10. 5
      AlphaWallet/Transfer/Types/TransactionConfiguration.swift
  11. 18
      AlphaWallet/Transfer/Types/TransactionConfigurations.swift
  12. 94
      AlphaWallet/Transfer/ViewControllers/ConfigureTransactionViewController.swift
  13. 98
      AlphaWallet/Transfer/ViewModels/ConfigureTransactionViewModel.swift
  14. 2
      AlphaWallet/Transfer/ViewModels/GasSpeedTableViewCellViewModel.swift
  15. 23
      AlphaWallet/Transfer/ViewModels/TransactionConfirmationTableHeaderViewModel.swift
  16. 54
      AlphaWallet/Transfer/ViewModels/TransactionConfirmationViewModel.swift
  17. 19
      AlphaWallet/Transfer/Views/TransactionConfirmationHeaderView.swift

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

@ -500,6 +500,15 @@
"transactionConfiguration.Type.average.time" = "< 5 min";
"transactionConfiguration.Type.fast.time" = "< 2 min";
"transactionConfiguration.Type.rapid.time" = "ASAP";
"transactionConfiguration.gasPrice.tooHigh.short" = "High Price";
"transactionConfiguration.gasPrice.tooLow.short" = "Low Price";
"transactionConfiguration.gasPrice.tooHigh.long" = "High Gas Price Warning!";
"transactionConfiguration.gasPrice.tooLow.long" = "Gas Price Might Be Too Low";
"transactionConfiguration.gasPrice.tooHigh.description" = "You set a really high gas price. Please make sure this is not a mistake. AlphaWallet uses gasnow.org";
"transactionConfiguration.gasPrice.tooLow.description" = "Gas price is set below the 'slow' speed. Your transaction may take a long time to be written or may never be written.";
"transactionConfiguration.gasPrice.congested.description" = "The Ethereum blockchain network is very congested now. Fees might be higher than usual.
You can check the latest gas price on gasnow.org";
"transactionConfirmation.Row.title.ens" = "ENS";
"transactionConfirmation.Row.title.wallet" = "Wallet Address";
"transactionConfirmation.Send.Section.Gas.title" = "Gas";

@ -500,6 +500,15 @@
"transactionConfiguration.Type.average.time" = "< 5 min";
"transactionConfiguration.Type.fast.time" = "< 2 min";
"transactionConfiguration.Type.rapid.time" = "ASAP";
"transactionConfiguration.gasPrice.tooHigh.short" = "High Price";
"transactionConfiguration.gasPrice.tooLow.short" = "Low Price";
"transactionConfiguration.gasPrice.tooHigh.long" = "High Gas Price Warning!";
"transactionConfiguration.gasPrice.tooLow.long" = "Gas Price Might Be Too Low";
"transactionConfiguration.gasPrice.tooHigh.description" = "You set a really high gas price. Please make sure this is not a mistake. AlphaWallet uses gasnow.org";
"transactionConfiguration.gasPrice.tooLow.description" = "Gas price is set below the 'slow' speed. Your transaction may take a long time to be written or may never be written.";
"transactionConfiguration.gasPrice.congested.description" = "The Ethereum blockchain network is very congested now. Fees might be higher than usual.
You can check the latest gas price on gasnow.org";
"transactionConfirmation.Row.title.ens" = "ENS";
"transactionConfirmation.Row.title.wallet" = "Wallet Address";
"transactionConfirmation.Send.Section.Gas.title" = "Gas";

@ -500,6 +500,15 @@
"transactionConfiguration.Type.average.time" = "< 5 min";
"transactionConfiguration.Type.fast.time" = "< 2 min";
"transactionConfiguration.Type.rapid.time" = "ASAP";
"transactionConfiguration.gasPrice.tooHigh.short" = "High Price";
"transactionConfiguration.gasPrice.tooLow.short" = "Low Price";
"transactionConfiguration.gasPrice.tooHigh.long" = "High Gas Price Warning!";
"transactionConfiguration.gasPrice.tooLow.long" = "Gas Price Might Be Too Low";
"transactionConfiguration.gasPrice.tooHigh.description" = "You set a really high gas price. Please make sure this is not a mistake. AlphaWallet uses gasnow.org";
"transactionConfiguration.gasPrice.tooLow.description" = "Gas price is set below the 'slow' speed. Your transaction may take a long time to be written or may never be written.";
"transactionConfiguration.gasPrice.congested.description" = "The Ethereum blockchain network is very congested now. Fees might be higher than usual.
You can check the latest gas price on gasnow.org";
"transactionConfirmation.Row.title.ens" = "ENS";
"transactionConfirmation.Row.title.wallet" = "Wallet Address";
"transactionConfirmation.Send.Section.Gas.title" = "Gas";

@ -500,6 +500,15 @@
"transactionConfiguration.Type.average.time" = "< 5 min";
"transactionConfiguration.Type.fast.time" = "< 2 min";
"transactionConfiguration.Type.rapid.time" = "ASAP";
"transactionConfiguration.gasPrice.tooHigh.short" = "High Price";
"transactionConfiguration.gasPrice.tooLow.short" = "Low Price";
"transactionConfiguration.gasPrice.tooHigh.long" = "High Gas Price Warning!";
"transactionConfiguration.gasPrice.tooLow.long" = "Gas Price Might Be Too Low";
"transactionConfiguration.gasPrice.tooHigh.description" = "You set a really high gas price. Please make sure this is not a mistake. AlphaWallet uses gasnow.org";
"transactionConfiguration.gasPrice.tooLow.description" = "Gas price is set below the 'slow' speed. Your transaction may take a long time to be written or may never be written.";
"transactionConfiguration.gasPrice.congested.description" = "The Ethereum blockchain network is very congested now. Fees might be higher than usual.
You can check the latest gas price on gasnow.org";
"transactionConfirmation.Row.title.ens" = "ENS";
"transactionConfirmation.Row.title.wallet" = "Wallet Address";
"transactionConfirmation.Send.Section.Gas.title" = "Gas";

@ -500,6 +500,15 @@
"transactionConfiguration.Type.average.time" = "< 5 min";
"transactionConfiguration.Type.fast.time" = "< 2 min";
"transactionConfiguration.Type.rapid.time" = "ASAP";
"transactionConfiguration.gasPrice.tooHigh.short" = "High Price";
"transactionConfiguration.gasPrice.tooLow.short" = "Low Price";
"transactionConfiguration.gasPrice.tooHigh.long" = "High Gas Price Warning!";
"transactionConfiguration.gasPrice.tooLow.long" = "Gas Price Might Be Too Low";
"transactionConfiguration.gasPrice.tooHigh.description" = "You set a really high gas price. Please make sure this is not a mistake. AlphaWallet uses gasnow.org";
"transactionConfiguration.gasPrice.tooLow.description" = "Gas price is set below the 'slow' speed. Your transaction may take a long time to be written or may never be written.";
"transactionConfiguration.gasPrice.congested.description" = "The Ethereum blockchain network is very congested now. Fees might be higher than usual.
You can check the latest gas price on gasnow.org";
"transactionConfirmation.Row.title.ens" = "ENS";
"transactionConfirmation.Row.title.wallet" = "Wallet Address";
"transactionConfirmation.Send.Section.Gas.title" = "Gas";

@ -125,6 +125,7 @@ public struct Constants {
static let ENSRegistrarGoerli = ENSRegistrarAddress
static let gasNowEndpointBaseUrl = "https://www.gasnow.org"
static let highStandardGasThresholdGwei = BigInt(55)
//Misc
public static let etherReceivedNotificationIdentifier = "etherReceivedNotificationIdentifier"

@ -15,6 +15,41 @@ protocol TransactionConfiguratorDelegate: class {
}
class TransactionConfigurator {
enum GasPriceWarning {
case tooHighCustomGasPrice
case networkCongested
case tooLowCustomGasPrice
var shortTitle: String {
switch self {
case .tooHighCustomGasPrice, .networkCongested:
return R.string.localizable.transactionConfigurationGasPriceTooHighShort()
case .tooLowCustomGasPrice:
return R.string.localizable.transactionConfigurationGasPriceTooLowShort()
}
}
var longTitle: String {
switch self {
case .tooHighCustomGasPrice, .networkCongested:
return R.string.localizable.transactionConfigurationGasPriceTooHighLong()
case .tooLowCustomGasPrice:
return R.string.localizable.transactionConfigurationGasPriceTooLowLong()
}
}
var description: String {
switch self {
case .tooHighCustomGasPrice:
return R.string.localizable.transactionConfigurationGasPriceTooHighDescription()
case .networkCongested:
return R.string.localizable.transactionConfigurationGasPriceCongestedDescription()
case .tooLowCustomGasPrice:
return R.string.localizable.transactionConfigurationGasPriceTooLowDescription()
}
}
}
private let account: AlphaWallet.Address
private var isGasLimitSpecifiedByTransaction: Bool {
@ -62,6 +97,10 @@ class TransactionConfigurator {
}
}
var gasPriceWarning: GasPriceWarning? {
gasPriceWarning(forConfiguration: currentConfiguration)
}
init(session: WalletSession, transaction: UnconfirmedTransaction) {
self.session = session
self.account = session.account.address
@ -185,6 +224,25 @@ class TransactionConfigurator {
}
}
func gasPriceWarning(forConfiguration configuration: TransactionConfiguration) -> GasPriceWarning? {
if let fastestConfig = configurations.fastestThirdPartyConfiguration, configuration.gasPrice > fastestConfig.gasPrice {
return .tooHighCustomGasPrice
}
//Conversion to gwei is needed so we that 17 (entered) is equal to 17.1 (fetched). Because 17.1 is displayed as "17" in the UI and might confuse the user if it's not treated as equal
if let slowestConfig = configurations.slowestThirdPartyConfiguration, (configuration.gasPrice / BigInt(EthereumUnit.gwei.rawValue)) < (slowestConfig.gasPrice / BigInt(EthereumUnit.gwei.rawValue)) {
return .tooLowCustomGasPrice
}
switch session.server {
case .main:
if (configurations.standard.gasPrice / BigInt(EthereumUnit.gwei.rawValue)) > Constants.highStandardGasThresholdGwei {
return .networkCongested
}
case .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom:
break
}
return nil
}
private static func computeDefaultGasPrice(server: RPCServer, transaction: UnconfirmedTransaction) -> BigInt {
switch server {
case .xDai:

@ -50,6 +50,11 @@ enum TransactionConfigurationType: Int, CaseIterable {
case rapid
case custom
static var sortedThirdPartyFastestFirst: [TransactionConfigurationType] {
//We intentionally do not include `.standard`
[.rapid, .fast, .slow]
}
var title: String {
switch self {
case .standard:

@ -12,6 +12,24 @@ struct TransactionConfigurations {
others.keys + [.standard, .custom]
}
var fastestThirdPartyConfiguration: TransactionConfiguration? {
for each in TransactionConfigurationType.sortedThirdPartyFastestFirst {
if let config = others[each] {
return config
}
}
return nil
}
var slowestThirdPartyConfiguration: TransactionConfiguration? {
for each in TransactionConfigurationType.sortedThirdPartyFastestFirst.reversed() {
if let config = others[each] {
return config
}
}
return nil
}
subscript(configurationType: TransactionConfigurationType) -> TransactionConfiguration? {
get {
switch configurationType {

@ -27,7 +27,7 @@ class ConfigureTransactionViewController: UIViewController {
let tableView = UITableView()
tableView.register(GasSpeedTableViewCell.self)
tableView.registerHeaderFooterView(GasSpeedTableViewHeaderView.self)
tableView.tableFooterView = createTableInformationFooter()
tableView.tableFooterView = createTableFooter()
tableView.separatorStyle = .none
tableView.allowsSelection = true
return tableView
@ -102,6 +102,8 @@ class ConfigureTransactionViewController: UIViewController {
updatedViewModel.configurations = configurator.configurations
viewModel = updatedViewModel
recalculateTotalFeeForCustomGas()
showGasPriceWarning()
tableView.tableFooterView = createTableFooter()
tableView.reloadData()
}
@ -153,13 +155,21 @@ class ConfigureTransactionViewController: UIViewController {
})
}
private func createTableInformationFooter() -> UIView {
private func createTableFooter() -> UIView {
if let gasPriceWarning = viewModel.gasPriceWarning {
return createTableFooterForGasPriceWarning(gasPriceWarning)
} else {
return createTableFooterForGasInformation()
}
}
private func createTableFooterForGasInformation() -> UIView {
let footer = UIView(frame: .init(x: 0, y: 0, width: 0, height: 100))
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.numberOfLines = 0
label.font = Fonts.regular(size: 12)
label.font = Fonts.regular(size: 15)
label.textColor = R.color.dove()
label.text = R.string.localizable.transactionConfirmationFeeFooterText()
footer.addSubview(label)
@ -169,12 +179,84 @@ class ConfigureTransactionViewController: UIViewController {
return footer
}
private func createTableFooterForGasPriceWarning(_ gasPriceWarning: TransactionConfigurator.GasPriceWarning) -> UIView {
let footer = UIView(frame: .init(x: 0, y: 0, width: 0, height: 0))
let background = UIView()
background.translatesAutoresizingMaskIntoConstraints = false
background.backgroundColor = .init(red: 255, green: 235, blue: 234)
background.borderColor = .init(red: 252, green: 187, blue: 183)
background.cornerRadius = 8
background.borderWidth = 1
footer.addSubview(background)
let warningIcon = UIImageView(image: R.image.gasWarning())
warningIcon.translatesAutoresizingMaskIntoConstraints = false
let titleLabel = UILabel()
titleLabel.textAlignment = .center
titleLabel.font = Fonts.semibold(size: 20)
titleLabel.textColor = R.color.danger()
titleLabel.text = gasPriceWarning.longTitle
let descriptionLabel = UITextView()
descriptionLabel.backgroundColor = .clear
descriptionLabel.textColor = R.color.dove()
descriptionLabel.textAlignment = .center
descriptionLabel.isEditable = false
descriptionLabel.isSelectable = true
descriptionLabel.isUserInteractionEnabled = true
descriptionLabel.isScrollEnabled = false
descriptionLabel.dataDetectorTypes = .link
descriptionLabel.font = Fonts.regular(size: 15)
descriptionLabel.text = gasPriceWarning.description
let row0 = [warningIcon, titleLabel].asStackView(axis: .horizontal, spacing: 6)
let row1 = descriptionLabel
let stackView = [
row0,
row1,
].asStackView(axis: .vertical, spacing: 6, alignment: .center)
stackView.translatesAutoresizingMaskIntoConstraints = false
footer.addSubview(stackView)
NSLayoutConstraint.activate([
background.anchorsConstraint(to: footer, margin: 16),
stackView.anchorsConstraint(to: background, edgeInsets: UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 16)),
warningIcon.widthAnchor.constraint(equalToConstant: 24),
warningIcon.widthAnchor.constraint(equalTo: warningIcon.heightAnchor),
descriptionLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -50),
descriptionLabel.widthAnchor.constraint(equalToConstant: 250),
])
var frame = footer.frame
frame.size.height = footer.systemLayoutSizeFitting(footer.frame.size).height
footer.frame = frame
return footer
}
private func recalculateTotalFeeForCustomGas() {
cells.totalFee.value = viewModel.gasViewModel.feeText
let configurationTypes = viewModel.configurationTypes
if let indexPath = configurationTypes.index(of: .custom).flatMap { IndexPath(row: $0, section: ConfigureTransactionViewModel.Section.configurationTypes.rawValue) }, let cell = tableView.cellForRow(at: indexPath) as? GasSpeedTableViewCell {
cell.configure(viewModel: viewModel.gasSpeedViewModel(indexPath: indexPath))
}
showGasPriceWarning()
tableView.tableFooterView = createTableFooter()
}
private func showGasPriceWarning() {
if viewModel.gasPriceWarning == nil {
cells.gasPrice.textField.status = .none
} else {
cells.gasPrice.textField.status = .error("")
}
}
@objc private func saveButtonSelected(_ sender: UIBarButtonItem) {
@ -212,6 +294,12 @@ class ConfigureTransactionViewController: UIViewController {
cells.nonce.textField.status = .error(ConfigureTransactionError.nonceNotPositiveNumber.localizedDescription)
}
if viewModel.gasPriceWarning == nil {
cells.gasPrice.textField.status = .none
} else {
cells.gasPrice.textField.status = .error("")
}
guard canSave else {
tableView.reloadData()
return

@ -4,10 +4,17 @@ import Foundation
import BigInt
struct ConfigureTransactionViewModel {
private let server: RPCServer
private let ethPrice: Subscribable<Double>
private let transactionType: TransactionType
private let configurator: TransactionConfigurator
private let currencyRate: CurrencyRate?
private let fullFormatter = EtherNumberFormatter.full
private var totalFee: BigInt {
return configurationToEdit.gasPrice * configurationToEdit.gasLimit
}
var selectedConfigurationType: TransactionConfigurationType
let server: RPCServer
let ethPrice: Subscribable<Double>
let transactionType: TransactionType
var configurationToEdit: EditedTransactionConfiguration {
didSet {
configurations.custom = configurationToEdit.configuration
@ -19,30 +26,14 @@ struct ConfigureTransactionViewModel {
configurationTypes = ConfigureTransactionViewModel.sortedConfigurationTypes(fromConfigurations: configurations)
}
}
let currencyRate: CurrencyRate?
init(server: RPCServer, configurator: TransactionConfigurator, ethPrice: Subscribable<Double>, currencyRate: CurrencyRate?) {
self.server = server
self.ethPrice = ethPrice
let configurations = configurator.configurations
self.configurationTypes = ConfigureTransactionViewModel.sortedConfigurationTypes(fromConfigurations: configurations)
self.configurations = configurations
self.currencyRate = currencyRate
transactionType = configurator.transaction.transactionType
selectedConfigurationType = configurator.selectedConfigurationType
configurationToEdit = EditedTransactionConfiguration(configuration: configurator.configurations.custom)
var gasPriceWarning: TransactionConfigurator.GasPriceWarning? {
configurator.gasPriceWarning(forConfiguration: configurationToEdit.configuration)
}
private let fullFormatter = EtherNumberFormatter.full
var gasViewModel: GasViewModel {
return GasViewModel(fee: totalFee, symbol: server.symbol, currencyRate: currencyRate, formatter: fullFormatter)
}
private var totalFee: BigInt {
return configurationToEdit.gasPrice * configurationToEdit.gasLimit
}
var backgroundColor: UIColor {
return R.color.alabaster()!
}
@ -60,20 +51,6 @@ struct ConfigureTransactionViewModel {
}
}
static func sortedConfigurationTypes(fromConfigurations configurations: TransactionConfigurations) -> [TransactionConfigurationType] {
let available = configurations.types
let all: [TransactionConfigurationType] = [.slow, .standard, .fast, .rapid, .custom]
return all.filter { available.contains($0) }
}
func gasSpeedViewModel(indexPath: IndexPath) -> GasSpeedTableViewCellViewModel {
let configurationType = configurationTypes[indexPath.row]
let isSelected = selectedConfigurationType == configurationType
let configuration = configurations[configurationType]!
//TODO if subscribable price are resolved or changes, will be good to refresh, but not essential
return .init(configuration: configuration, configurationType: configurationType, cryptoToDollarRate: ethPrice.value, symbol: server.symbol, title: configurationType.title, isSelected: isSelected)
}
var gasLimitSliderViewModel: SliderTableViewCellViewModel {
return .init(
value: configurationToEdit.gasLimitRawValue,
@ -109,17 +86,6 @@ struct ConfigureTransactionViewModel {
return .init(placeholder: placeholder, value: gasViewModel.feeText, allowEditing: false)
}
func numberOfRowsInSections(in section: Int) -> Int {
switch sections[section] {
case .configurationTypes:
return configurationTypes.count
case .gasPrice:
return 1
case .gasLimit:
return gasLimitRows.count
}
}
var sections: [Section] {
switch selectedConfigurationType {
case .standard, .slow, .fast, .rapid:
@ -159,6 +125,44 @@ struct ConfigureTransactionViewModel {
case totalFee
}
}
init(server: RPCServer, configurator: TransactionConfigurator, ethPrice: Subscribable<Double>, currencyRate: CurrencyRate?) {
self.server = server
self.ethPrice = ethPrice
let configurations = configurator.configurations
self.configurationTypes = ConfigureTransactionViewModel.sortedConfigurationTypes(fromConfigurations: configurations)
self.configurator = configurator
self.configurations = configurations
self.currencyRate = currencyRate
transactionType = configurator.transaction.transactionType
selectedConfigurationType = configurator.selectedConfigurationType
configurationToEdit = EditedTransactionConfiguration(configuration: configurator.configurations.custom)
}
static func sortedConfigurationTypes(fromConfigurations configurations: TransactionConfigurations) -> [TransactionConfigurationType] {
let available = configurations.types
let all: [TransactionConfigurationType] = [.slow, .standard, .fast, .rapid, .custom]
return all.filter { available.contains($0) }
}
func gasSpeedViewModel(indexPath: IndexPath) -> GasSpeedTableViewCellViewModel {
let configurationType = configurationTypes[indexPath.row]
let isSelected = selectedConfigurationType == configurationType
let configuration = configurations[configurationType]!
//TODO if subscribable price are resolved or changes, will be good to refresh, but not essential
return .init(configuration: configuration, configurationType: configurationType, cryptoToDollarRate: ethPrice.value, symbol: server.symbol, title: configurationType.title, isSelected: isSelected)
}
func numberOfRowsInSections(in section: Int) -> Int {
switch sections[section] {
case .configurationTypes:
return configurationTypes.count
case .gasPrice:
return 1
case .gasLimit:
return gasLimitRows.count
}
}
}
private let numberValueFormatter: NumberFormatter = {
@ -172,4 +176,4 @@ extension String {
var numberValue: NSNumber? {
return numberValueFormatter.number(from: self)
}
}
}

@ -30,7 +30,7 @@ struct GasSpeedTableViewCellViewModel {
private var gasPriceString: String {
let price = configuration.gasPrice / BigInt(EthereumUnit.gwei.rawValue)
return "\(R.string.localizable.configureTransactionHeaderGasPrice()): \(price) Gwei"
return "\(R.string.localizable.configureTransactionHeaderGasPrice()): \(price) \(EthereumUnit.gwei.name)"
}
private var estimatedTime: String? {

@ -8,11 +8,16 @@
import UIKit
struct TransactionConfirmationHeaderViewModel {
enum Title {
case normal(String?)
case warning(String)
}
let title: String?
var headerName: String?
var details: String?
let headerName: String?
let details: String?
var configuration: TransactionConfirmationHeaderView.Configuration
let isWarning: Bool
var chevronImage: UIImage? {
let image = configuration.isOpened ? R.image.expand() : R.image.not_expand()
return image?.withRenderingMode(.alwaysTemplate)
@ -56,4 +61,18 @@ struct TransactionConfirmationHeaderViewModel {
var backgroundColor: UIColor {
return Colors.appBackground
}
init(title: Title, headerName: String?, details: String? = nil, configuration: TransactionConfirmationHeaderView.Configuration) {
switch title {
case .normal(let title):
self.title = title
self.isWarning = false
case .warning(let title):
self.title = title
self.isWarning = true
}
self.headerName = headerName
self.details = details
self.configuration = configuration
}
}

@ -229,14 +229,18 @@ extension TransactionConfirmationViewModel {
switch sections[section] {
case .balance:
let title = R.string.localizable.tokenTransactionConfirmationDefault()
return .init(title: balance ?? title, headerName: headerName, details: newBalance, configuration: configuration)
return .init(title: .normal(balance ?? title), headerName: headerName, details: newBalance, configuration: configuration)
case .gas:
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, headerName: headerName, details: gasFee, configuration: configuration)
if let warning = configurator.gasPriceWarning {
return .init(title: .warning(warning.shortTitle), headerName: headerName, details: gasFee, configuration: configuration)
} else {
return .init(title: .normal(configurationTitle), headerName: headerName, details: gasFee, configuration: configuration)
}
case .amount:
return .init(title: formattedAmountValue, headerName: headerName, configuration: configuration)
return .init(title: .normal(formattedAmountValue), headerName: headerName, configuration: configuration)
case .recipient:
return .init(title: recipientResolver.value, headerName: headerName, configuration: configuration)
return .init(title: .normal(recipientResolver.value), headerName: headerName, configuration: configuration)
}
}
}
@ -311,11 +315,15 @@ extension TransactionConfirmationViewModel {
switch sections[section] {
case .gas:
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, headerName: headerName, details: gasFee, configuration: configuration)
if let warning = configurator.gasPriceWarning {
return .init(title: .warning(warning.shortTitle), headerName: headerName, details: gasFee, configuration: configuration)
} else {
return .init(title: .normal(configurationTitle), headerName: headerName, details: gasFee, configuration: configuration)
}
case .amount:
return .init(title: formattedAmountValue, headerName: headerName, configuration: configuration)
return .init(title: .normal(formattedAmountValue), headerName: headerName, configuration: configuration)
case .function(let functionCallMetaData):
return .init(title: functionCallMetaData.name, headerName: headerName, configuration: configuration)
return .init(title: .normal(functionCallMetaData.name), headerName: headerName, configuration: configuration)
}
}
@ -384,13 +392,17 @@ extension TransactionConfirmationViewModel {
switch sections[section] {
case .gas:
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, headerName: headerName, details: gasFee, configuration: configuration)
if let warning = configurator.gasPriceWarning {
return .init(title: .warning(warning.shortTitle), headerName: headerName, details: gasFee, configuration: configuration)
} else {
return .init(title: .normal(configurationTitle), headerName: headerName, details: gasFee, configuration: configuration)
}
case .contract:
return .init(title: address.truncateMiddle, headerName: headerName, configuration: configuration)
return .init(title: .normal(address.truncateMiddle), headerName: headerName, configuration: configuration)
case .function:
return .init(title: functionCallMetaData.name, headerName: headerName, configuration: configuration)
return .init(title: .normal(functionCallMetaData.name), headerName: headerName, configuration: configuration)
case .amount:
return .init(title: formattedAmountValue, headerName: headerName, configuration: configuration)
return .init(title: .normal(formattedAmountValue), headerName: headerName, configuration: configuration)
}
}
@ -475,7 +487,11 @@ extension TransactionConfirmationViewModel {
switch sections[section] {
case .gas:
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, headerName: headerName, details: gasFee, configuration: configuration)
if let warning = configurator.gasPriceWarning {
return .init(title: .warning(warning.shortTitle), headerName: headerName, details: gasFee, configuration: configuration)
} else {
return .init(title: .normal(configurationTitle), headerName: headerName, details: gasFee, configuration: configuration)
}
case .tokenId:
let tokenId = configurator.transaction.tokenId.flatMap({ String($0) })
let title: String
@ -488,9 +504,9 @@ extension TransactionConfirmationViewModel {
} else {
title = tokenId ?? ""
}
return .init(title: title, headerName: headerName, configuration: configuration)
return .init(title: .normal(title), headerName: headerName, configuration: configuration)
case .recipient:
return .init(title: recipientResolver.value, headerName: headerName, configuration: configuration)
return .init(title: .normal(recipientResolver.value), headerName: headerName, configuration: configuration)
}
}
}
@ -548,7 +564,11 @@ extension TransactionConfirmationViewModel {
let headerName = sections[section].title
switch sections[section] {
case .gas:
return .init(title: configurationTitle, headerName: headerName, configuration: configuration)
if let warning = configurator.gasPriceWarning {
return .init(title: .warning(warning.shortTitle), headerName: headerName, configuration: configuration)
} else {
return .init(title: .normal(configurationTitle), headerName: headerName, configuration: configuration)
}
case .amount:
let cryptoToDollarSymbol = Constants.Currency.usd
let nativeCryptoSymbol = configurator.session.server.symbol
@ -560,9 +580,9 @@ extension TransactionConfirmationViewModel {
} else {
formattedAmountValue = "\(nativeCryptoPrice) \(nativeCryptoSymbol)"
}
return .init(title: formattedAmountValue, headerName: headerName, configuration: configuration)
return .init(title: .normal(formattedAmountValue), headerName: headerName, configuration: configuration)
case .numberOfTokens:
return .init(title: String(numberOfTokens), headerName: headerName, configuration: configuration)
return .init(title: .normal(String(numberOfTokens)), headerName: headerName, configuration: configuration)
}
}
}

@ -33,14 +33,17 @@ class TransactionConfirmationHeaderView: UIView {
private let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
private let warningIcon: UIImageView = {
let imageView = UIImageView(image: R.image.gasWarning())
return imageView
}()
private let detailsLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
@ -48,7 +51,6 @@ class TransactionConfirmationHeaderView: UIView {
private lazy var chevronView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(chevronImageView)
return view
@ -86,9 +88,12 @@ class TransactionConfirmationHeaderView: UIView {
separatorLine.translatesAutoresizingMaskIntoConstraints = false
separatorLine.backgroundColor = R.color.mercury()
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
let titleRow = [warningIcon, titleLabel].asStackView(axis: .horizontal, spacing: 6)
let col0 = nameLabel
let col1 = [
titleLabel,
titleRow,
detailsLabel
].asStackView(axis: .vertical, alignment: .leading)
col1.translatesAutoresizingMaskIntoConstraints = false
@ -125,6 +130,9 @@ class TransactionConfirmationHeaderView: UIView {
titleLabel.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor),
warningIcon.widthAnchor.constraint(equalToConstant: 24),
warningIcon.widthAnchor.constraint(equalTo: warningIcon.heightAnchor),
col1.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: ScreenChecker().isNarrowScreen ? 8 : 16),
col1.trailingAnchor.constraint(equalTo: contents.trailingAnchor),
col1.topAnchor.constraint(lessThanOrEqualTo: nameLabel.topAnchor),
@ -153,6 +161,9 @@ class TransactionConfirmationHeaderView: UIView {
chevronView.isHidden = viewModel.configuration.shouldHideChevron
chevronImageView.image = viewModel.chevronImage
warningIcon.isHidden = !viewModel.isWarning
titleLabel.alpha = viewModel.titleAlpha
titleLabel.attributedText = viewModel.titleAttributedString

Loading…
Cancel
Save