mirror of
https://github.com/luxfi/safe-ios.git
synced 2026-10-30 15:19:20 +00:00
245 lines
7.8 KiB
Swift
245 lines
7.8 KiB
Swift
//
|
|
// ReviewExecutionCellBuilder.swift
|
|
// Multisig
|
|
//
|
|
// Created by Dmitry Bespalov on 11.01.22.
|
|
// Copyright © 2022 Gnosis Ltd. All rights reserved.
|
|
//
|
|
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import SwiftCryptoTokenFormatter
|
|
|
|
class ReviewExecutionCellBuilder: TransactionDetailCellBuilder {
|
|
var executionOptionsCellBuilder: ExecutionOptionsCellBuilder
|
|
var onTapPaymentMethod: () -> Void = {}
|
|
var onTapAccount: () -> Void = {}
|
|
var onTapFee: () -> Void = {}
|
|
var onTapAdvanced: () -> Void = {}
|
|
var userSelectedSigner = false
|
|
|
|
private var safe: Safe!
|
|
|
|
init(vc: UIViewController, tableView: UITableView, chain: Chain, safe: Safe) {
|
|
executionOptionsCellBuilder = ExecutionOptionsCellBuilder(
|
|
vc: vc,
|
|
tableView: tableView,
|
|
chain: chain
|
|
)
|
|
super.init(vc: vc, tableView: tableView, chain: chain)
|
|
self.safe = safe
|
|
|
|
tableView.registerCell(BorderedInnerTableCell.self)
|
|
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Spacer")
|
|
}
|
|
|
|
func build(_ model: ExecutionReviewUIModel) -> [UITableViewCell] {
|
|
result = []
|
|
// nothing to do for creation transaction
|
|
if case SCGModels.TxInfo.creation(_) = model.transaction.txInfo {
|
|
return result
|
|
}
|
|
buildHeader(model.transaction)
|
|
buildAssetContract(model.transaction)
|
|
buildSpacing()
|
|
|
|
executionOptionsCellBuilder.userSelectedSigner = userSelectedSigner
|
|
executionOptionsCellBuilder.onTapPaymentMethod = onTapPaymentMethod
|
|
executionOptionsCellBuilder.onTapAccount = onTapAccount
|
|
executionOptionsCellBuilder.onTapFee = onTapFee
|
|
executionOptionsCellBuilder.onTapAdvanced = onTapAdvanced
|
|
result.append(contentsOf: executionOptionsCellBuilder.buildExecutionOptions(model.executionOptions))
|
|
buildErrors(model.errorMessage)
|
|
return result
|
|
}
|
|
|
|
// TODO can this be moved to TransactionDetailCellBuilder ?
|
|
func buildSpacing() {
|
|
let cell = newCell(UITableViewCell.self, reuseId: "Spacer")
|
|
cell.contentView.heightAnchor.constraint(equalToConstant: 16).isActive = true
|
|
result.append(cell)
|
|
}
|
|
|
|
func buildErrors(_ errorText: String?) {
|
|
guard var errorText = errorText else {
|
|
return
|
|
}
|
|
let cell = newCell(DetailExpandableTextCell.self)
|
|
|
|
errorText = "!⃤ " + errorText
|
|
|
|
// restrict to 1 tweet length
|
|
let errorPreview = errorText.count <= 144 ? nil : (String(errorText.prefix(144)) + "…")
|
|
cell.tableView = tableView
|
|
cell.titleStyle = .calloutMediumError
|
|
cell.expandableTitleStyle = (collapsed: .calloutError, expanded: .calloutError)
|
|
cell.contentStyle = (collapsed: .bodyError, expanded: .body)
|
|
cell.setTitle(nil)
|
|
cell.setText(errorText)
|
|
cell.setCopyText(errorText)
|
|
cell.setExpandableTitle(errorPreview)
|
|
|
|
result.append(cell)
|
|
}
|
|
|
|
// MARK: New Transfer Header
|
|
|
|
override func buildHeader(_ tx: SCGModels.TransactionDetails) {
|
|
// multi-level enums
|
|
guard case SCGModels.TxInfo.transfer(let transferTx) = tx.txInfo else {
|
|
super.buildHeader(tx)
|
|
return
|
|
}
|
|
|
|
// amount
|
|
let amountModel = tokenAmount(from: transferTx.transferInfo)
|
|
buildAmount(amountModel: amountModel)
|
|
|
|
// from
|
|
let (senderLabel, senderLogo) = NamingPolicy.name(for: transferTx.sender, chainId: chain.id!)
|
|
let senderUrl = chain.browserURL(address: transferTx.sender.value.address.checksummed)
|
|
|
|
// to
|
|
let (recipientLabel, recipientLogo) = NamingPolicy.name(for: transferTx.recipient, chainId: chain.id!)
|
|
let recipientUrl = chain.browserURL(address: transferTx.recipient.value.address.checksummed)
|
|
|
|
self.addresses([
|
|
(address: transferTx.sender.value.address, label: senderLabel,
|
|
imageUri: senderLogo, title: "From", browseURL: senderUrl, prefix: chain.shortName),
|
|
|
|
(address: transferTx.recipient.value.address, label: recipientLabel,
|
|
imageUri: recipientLogo, title: "To", browseURL: recipientUrl, prefix: chain.shortName)
|
|
])
|
|
}
|
|
|
|
func buildAmount(amountModel: TokenAmountUIModel) {
|
|
let cell = newCell(DetailMultiAccountsCell.self)
|
|
let tokenView = TokenInfoView()
|
|
tokenView.setTitle("Amount")
|
|
tokenView.setImage(amountModel.tokenLogoURL, placeholder: amountModel.placeholder)
|
|
tokenView.setText(amountModel.formattedAmount)
|
|
tokenView.setDetail(amountModel.formattedFiatValue, style: .caption1.weight(.medium))
|
|
cell.setViews([tokenView])
|
|
result.append(cell)
|
|
}
|
|
|
|
func tokenAmount(from transferInfo: SCGModels.TxInfo.Transfer.TransferInfo) -> TokenAmountUIModel {
|
|
var result = TokenAmountUIModel()
|
|
|
|
switch transferInfo {
|
|
case .erc20(let erc20):
|
|
result.value = erc20.value.value
|
|
result.decimals = erc20.decimals
|
|
result.symbol = erc20.tokenSymbol ?? "ERC20"
|
|
result.tokenLogoUri = erc20.logoUri
|
|
|
|
case .nativeCoin(let native):
|
|
let coin = Chain.nativeCoin!
|
|
result.value = native.value.value
|
|
result.decimals = UInt64(coin.decimals)
|
|
result.symbol = coin.symbol!
|
|
result.tokenLogoUri = coin.logoUrl?.absoluteString
|
|
|
|
case .erc721(let erc721):
|
|
result.value = 1
|
|
result.decimals = 0
|
|
result.symbol = erc721.tokenSymbol ?? "NFT"
|
|
result.tokenLogoUri = erc721.logoUri
|
|
result.placeholder = UIImage(named: "ico-nft-placeholder")
|
|
result.detail = erc721.tokenId.description
|
|
|
|
case .unknown:
|
|
break
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
struct TokenAmountUIModel {
|
|
var value: UInt256?
|
|
var symbol: String = ""
|
|
var decimals: UInt64?
|
|
var detail: String?
|
|
var tokenLogoUri: String?
|
|
var placeholder = UIImage(named: "ico-token-placeholder")
|
|
|
|
var formattedAmount: String {
|
|
let tokenAmountText: String
|
|
if let value = value {
|
|
let decimalAmount = BigDecimal(Int256(value),
|
|
decimals.flatMap { Int($0) } ?? 0)
|
|
|
|
let amount = TokenFormatter().string(
|
|
from: decimalAmount,
|
|
decimalSeparator: Locale.autoupdatingCurrent.decimalSeparator ?? ".",
|
|
thousandSeparator: Locale.autoupdatingCurrent.groupingSeparator ?? ",",
|
|
forcePlusSign: false
|
|
)
|
|
|
|
tokenAmountText = "\(amount) \(symbol)"
|
|
} else {
|
|
tokenAmountText = "Unknown token"
|
|
}
|
|
return tokenAmountText
|
|
}
|
|
|
|
var formattedFiatValue: String {
|
|
detail ?? ""
|
|
}
|
|
|
|
var tokenLogoURL: URL? {
|
|
tokenLogoUri.flatMap { URL(string: $0) }
|
|
}
|
|
}
|
|
|
|
struct ExecutionReviewUIModel {
|
|
var transaction: SCGModels.TransactionDetails
|
|
var executionOptions: ExecutionOptionsUIModel
|
|
var errorMessage: String?
|
|
}
|
|
|
|
struct ExecutionOptionsUIModel {
|
|
var relayerState: ExecuteWithRelayerCellState = .none
|
|
var accountState: ExecuteWithAccountCellState = .none
|
|
var feeState: EstimatedFeeCellState = .loading
|
|
}
|
|
|
|
enum ExecuteWithAccountCellState {
|
|
case none
|
|
case loading
|
|
case empty
|
|
case filled(MiniAccountInfoUIModel)
|
|
}
|
|
|
|
enum ExecuteWithRelayerCellState {
|
|
case none
|
|
case loading
|
|
case filled(RelayerInfoUIModel)
|
|
}
|
|
|
|
struct RelayerInfoUIModel {
|
|
var remainingRelays: Int
|
|
var limit: Int
|
|
}
|
|
|
|
struct MiniAccountInfoUIModel {
|
|
var prefix: String?
|
|
var address: Address
|
|
var label: String?
|
|
var imageUri: URL?
|
|
var badge: String?
|
|
var balance: String?
|
|
}
|
|
|
|
enum EstimatedFeeCellState {
|
|
case loading
|
|
case empty
|
|
case loaded(EstimatedFeeUIModel)
|
|
}
|
|
|
|
struct EstimatedFeeUIModel {
|
|
var tokenAmount: String
|
|
var fiatAmount: String?
|
|
}
|