From 2433829cd5aac2aeb75e0bb6b9aca8fbd3531e93 Mon Sep 17 00:00:00 2001 From: Anton Stavnichiy Date: Wed, 12 Jun 2024 17:54:11 +0600 Subject: [PATCH] Fix negative values on ETF chart. --- .../Core/Adapters/ZcashAdapter.swift | 4 +- .../Models/TransactionValue.swift | 6 +- .../CoinIndicatorViewItemFactory.swift | 2 +- .../Modules/Coin/CoinChartFactory.swift | 2 +- .../Market/Global/MarketGlobalView.swift | 2 +- .../MetricChart/MetricChartFactory.swift | 12 ++-- .../NftCollectionOverviewViewModel.swift | 2 +- .../TransactionsViewItemFactory.swift | 16 ++--- .../Wallet/WalletViewItemFactory.swift | 2 +- .../UserInterface/SwiftUI/DiffText.swift | 2 +- .../UserInterface/ValueFormatter.swift | 58 +++++++++++-------- 11 files changed, 59 insertions(+), 49 deletions(-) diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift index b4edc7dafe..c72e59ed6b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift @@ -858,7 +858,7 @@ enum ZCashAdapterState: Equatable { case let .downloadingSapling(progress): return .customSyncing(main: "balance.downloading_sapling".localized(progress), secondary: nil, progress: progress) case let .downloadingBlocks(progress, _): - let percentValue = ValueFormatter.instance.format(percentValue: Decimal(Double(progress * 100)), showSign: false) + let percentValue = ValueFormatter.instance.format(percentValue: Decimal(Double(progress * 100)), signType: .never) return .customSyncing(main: "balance.downloading_blocks".localized, secondary: percentValue, progress: Int(progress * 100)) case let .notSynced(error): return .notSynced(error: error) } @@ -872,7 +872,7 @@ enum ZCashAdapterState: Equatable { case let .syncing(progress, lastDate): return "Syncing: progress = \(progress?.description ?? "N/A"), lastBlockDate: \(lastDate?.description ?? "N/A")" case let .downloadingSapling(progress): return "downloadingSapling: progress = \(progress)" case let .downloadingBlocks(progress, _): - let percentValue = ValueFormatter.instance.format(percentValue: Decimal(Double(progress * 100)), showSign: false) + let percentValue = ValueFormatter.instance.format(percentValue: Decimal(Double(progress * 100)), signType: .never) return "Downloading Blocks: \(percentValue?.description ?? "N/A") : \(Int(progress * 100))" case let .notSynced(error): return "Not synced \(error.localizedDescription)" } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift b/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift index f02866b60d..6dd24cbbf3 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift @@ -95,12 +95,12 @@ enum TransactionValue { } } - func formattedShort(showSign: Bool = false) -> String? { + func formattedShort(signType: ValueFormatter.SignType = .never) -> String? { switch self { case let .coinValue(token, value): - return ValueFormatter.instance.formatShort(value: value, decimalCount: token.decimals, symbol: token.coin.code, showSign: showSign) + return ValueFormatter.instance.formatShort(value: value, decimalCount: token.decimals, symbol: token.coin.code, signType: signType) case let .tokenValue(_, tokenCode, tokenDecimals, value): - return ValueFormatter.instance.formatShort(value: value, decimalCount: tokenDecimals, symbol: tokenCode, showSign: showSign) + return ValueFormatter.instance.formatShort(value: value, decimalCount: tokenDecimals, symbol: tokenCode, signType: signType) case let .nftValue(_, value, _, tokenSymbol): return "\(value.sign == .plus ? "+" : "")\(value) \(tokenSymbol ?? "NFT")" case .rawValue: diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/TechnicalIndicators/CoinIndicatorViewItemFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/TechnicalIndicators/CoinIndicatorViewItemFactory.swift index 0739f727d8..d40c5d48ad 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/TechnicalIndicators/CoinIndicatorViewItemFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/TechnicalIndicators/CoinIndicatorViewItemFactory.swift @@ -87,7 +87,7 @@ extension CoinIndicatorViewItemFactory { rsiLine = "70%" } - let rsiValue = technicalAdvice.rsi.flatMap { ValueFormatter.instance.format(percentValue: $0, showSign: false) } + let rsiValue = technicalAdvice.rsi.flatMap { ValueFormatter.instance.format(percentValue: $0, signType: .never) } let signalTimeString = technicalAdvice.signalTimestamp.flatMap { let date = DateHelper.instance.formatShortDateOnly(date: Date(timeIntervalSince1970: $0)) return "technical_advice.over.indicators.signal_date".localized(date) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinChartFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinChartFactory.swift index 225ddfb1a8..5478b15e30 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinChartFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinChartFactory.swift @@ -61,7 +61,7 @@ class CoinChartFactory { let chartData = ChartData(items: items, startWindow: firstPoint.timestamp, endWindow: lastPoint.timestamp) let diff = (lastPoint.value - firstPoint.value) / firstPoint.value * 100 - let diffString = ValueFormatter.instance.format(percentValue: diff, showSign: true) + let diffString = ValueFormatter.instance.format(percentValue: diff, signType: .always) let valueDiff = diffString.map { ValueDiff(value: $0, trend: diff.isSignMinus ? .down : .up) } return ChartModule.ViewItem( value: ValueFormatter.instance.formatFull(currencyValue: CurrencyValue(currency: currency, value: item.rate)), diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Global/MarketGlobalView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Global/MarketGlobalView.swift index a68cd0a4cd..dc9b1cf27c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Global/MarketGlobalView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Global/MarketGlobalView.swift @@ -72,7 +72,7 @@ struct MarketGlobalView: View { diffView( title: "market.global.btc_dominance".localized, - amount: marketGlobal?.btcDominance.flatMap { ValueFormatter.instance.format(percentValue: $0, showSign: false) }, + amount: marketGlobal?.btcDominance.flatMap { ValueFormatter.instance.format(percentValue: $0, signType: .never) }, diff: marketGlobal?.btcDominanceChange.map { .percent(value: $0) }, redacted: redacted ) { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/MetricChart/MetricChartFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/MetricChart/MetricChartFactory.swift index 53bb4395ca..26020cd1a0 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/MetricChart/MetricChartFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/MetricChart/MetricChartFactory.swift @@ -21,7 +21,7 @@ class MetricChartFactory { switch valueType { case .percent: - return ValueFormatter.instance.format(percentValue: value, showSign: false) + return ValueFormatter.instance.format(percentValue: value, signType: .never) case let .currencyValue(currency): return ValueFormatter.instance.formatFull(currency: currency, value: value) case .counter: @@ -40,9 +40,9 @@ class MetricChartFactory { return [valueString, coin.code].compactMap { $0 }.joined(separator: " ") case let .compactCurrencyValue(currency): if exactlyValue { - return ValueFormatter.instance.formatFull(currency: currency, value: value, showSign: true) + return ValueFormatter.instance.formatFull(currency: currency, value: value, signType: .always) } else { - return ValueFormatter.instance.formatShort(currency: currency, value: value) + return ValueFormatter.instance.formatShort(currency: currency, value: value, signType: .auto) } } } @@ -85,7 +85,7 @@ extension MetricChartFactory { let diff = (lastItem.value - firstItem.value) / firstItem.value * 100 chartTrend = diff.isSignMinus ? .down : .up - let valueString = ValueFormatter.instance.format(percentValue: diff, showSign: true) + let valueString = ValueFormatter.instance.format(percentValue: diff, signType: .always) valueDiff = valueString.map { ValueDiff(value: $0, trend: chartTrend) } if let hardcodedRightMode { @@ -98,7 +98,7 @@ extension MetricChartFactory { value = Self.format(value: last, valueType: valueType) } - let valueString = ValueFormatter.instance.formatShort(currency: currencyManager.baseCurrency, value: lastItem.value) + let valueString = ValueFormatter.instance.formatShort(currency: currencyManager.baseCurrency, value: lastItem.value, signType: .always) valueDiff = valueString.map { ValueDiff(value: $0, trend: lastItem.value.isSignMinus ? .down : .up) } chartTrend = .neutral @@ -187,7 +187,7 @@ extension MetricChartFactory { // if etf chart if let totalInflow = chartItem.indicators[ChartIndicator.LineConfiguration.totalInflowId] { let formattedValue = ValueFormatter.instance.formatShort(currency: currencyManager.baseCurrency, value: totalInflow) - let diffString = ValueFormatter.instance.formatShort(currency: currencyManager.baseCurrency, value: value) + let diffString = ValueFormatter.instance.formatShort(currency: currencyManager.baseCurrency, value: value, signType: .always) let diff = diffString.map { ValueDiff(value: $0, trend: value.isSignMinus ? .down : .up) } return ChartModule.SelectedPointViewItem( diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/NftCollection/Overview/NftCollectionOverviewViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/NftCollection/Overview/NftCollectionOverviewViewModel.swift index 0c7540b413..74c94b1d35 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/NftCollection/Overview/NftCollectionOverviewViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/NftCollection/Overview/NftCollectionOverviewViewModel.swift @@ -50,7 +50,7 @@ class NftCollectionOverviewViewModel { contracts: collection.contracts.map { contractViewItem(contract: $0) }, links: linkViewItems(collection: collection), statsViewItems: statViewItem(collection: collection), - royalty: collection.royalty.flatMap { ValueFormatter.instance.format(percentValue: $0, showSign: false) }, + royalty: collection.royalty.flatMap { ValueFormatter.instance.format(percentValue: $0, signType: .never) }, inceptionDate: collection.inceptionDate.map { DateFormatter.cachedFormatter(format: "MMMM d, yyyy").string(from: $0) } ) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift index 26b861a159..b67448d626 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift @@ -25,8 +25,8 @@ class TransactionsViewItemFactory { contactLabelService.contactData(for: address, blockchainType: blockchainType).name ?? evmLabelManager.mapped(address: address) } - private func coinString(from transactionValue: TransactionValue, showSign: Bool = true) -> String { - guard let value = transactionValue.formattedShort(showSign: showSign) else { + private func coinString(from transactionValue: TransactionValue, signType: ValueFormatter.SignType = .always) -> String { + guard let value = transactionValue.formattedShort(signType: signType) else { return "n/a".localized } @@ -192,7 +192,7 @@ class TransactionsViewItemFactory { title = "transactions.send".localized subTitle = "transactions.to".localized(mapped(address: record.to, blockchainType: item.record.source.blockchainType)) - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, showSign: !record.sentToSelf), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, signType: record.sentToSelf ? .never : .always), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) secondaryValue = singleValueSecondaryValue(value: record.value, currencyValue: item.currencyValue, nftMetadata: item.nftMetadata) sentToSelf = record.sentToSelf @@ -229,7 +229,7 @@ class TransactionsViewItemFactory { primaryValue = BaseTransactionsViewModel.Value(text: "∞ \(record.value.coinCode)", type: .neutral) secondaryValue = BaseTransactionsViewModel.Value(text: "transactions.value.unlimited".localized, type: .secondary) } else { - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, showSign: false), type: .neutral) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, signType: .never), type: .neutral) if let currencyValue = item.currencyValue { secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) @@ -290,7 +290,7 @@ class TransactionsViewItemFactory { title = "transactions.send".localized subTitle = record.to.flatMap { "transactions.to".localized(mapped(address: $0, blockchainType: item.record.source.blockchainType)) } ?? "---" - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, showSign: !record.sentToSelf), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, signType: record.sentToSelf ? .never : .always), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) if let currencyValue = item.currencyValue { secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) @@ -317,7 +317,7 @@ class TransactionsViewItemFactory { title = "transactions.send".localized subTitle = "transactions.to".localized(mapped(address: record.to, blockchainType: item.record.source.blockchainType)) - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, showSign: !record.sentToSelf), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, signType: record.sentToSelf ? .never : .always), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) if let currencyValue = item.currencyValue { secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) @@ -341,7 +341,7 @@ class TransactionsViewItemFactory { title = "transactions.send".localized subTitle = "transactions.to".localized(mapped(address: record.to, blockchainType: item.record.source.blockchainType)) - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, showSign: !record.sentToSelf), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, signType: record.sentToSelf ? .never : .always), type: type(value: record.value, condition: record.sentToSelf, .neutral, .outgoing)) secondaryValue = singleValueSecondaryValue(value: record.value, currencyValue: item.currencyValue, nftMetadata: item.nftMetadata) sentToSelf = record.sentToSelf @@ -355,7 +355,7 @@ class TransactionsViewItemFactory { primaryValue = BaseTransactionsViewModel.Value(text: "∞ \(record.value.coinCode)", type: .neutral) secondaryValue = BaseTransactionsViewModel.Value(text: "transactions.value.unlimited".localized, type: .secondary) } else { - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, showSign: false), type: .neutral) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.value, signType: .never), type: .neutral) if let currencyValue = item.currencyValue { secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewItemFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewItemFactory.swift index a0dda38c09..df7447596d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewItemFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewItemFactory.swift @@ -92,7 +92,7 @@ class WalletViewItemFactory { return nil } - guard let formattedValue = ValueFormatter.instance.format(percentValue: value, showSign: true) else { + guard let formattedValue = ValueFormatter.instance.format(percentValue: value, signType: .always) else { return nil } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/DiffText.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/DiffText.swift index 2f03520287..6693dae373 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/DiffText.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/DiffText.swift @@ -44,7 +44,7 @@ struct DiffText: View { switch diff { case let .percent(value): return ValueFormatter.instance.format(percentValue: value).map { ($0, value) } - case let .change(value, currency): return ValueFormatter.instance.formatShort(currency: currency, value: value, showSign: true).map { ($0, value) } + case let .change(value, currency): return ValueFormatter.instance.formatShort(currency: currency, value: value, signType: .always).map { ($0, value) } } } } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/ValueFormatter.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/ValueFormatter.swift index 61d5bd4960..1e33f0abe7 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/ValueFormatter.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/ValueFormatter.swift @@ -142,7 +142,7 @@ class ValueFormatter { return (value: value, digits: max(digits, minDigits)) } - private func decorated(string: String, suffix: String? = nil, symbol: String? = nil, signValue: Decimal? = nil, tooSmall: Bool = false) -> String { + private func decorated(string: String, suffix: String? = nil, symbol: String? = nil, signType: SignType = .never, signValue: Decimal, tooSmall: Bool = false) -> String { var string = string if let suffix { @@ -153,12 +153,14 @@ class ValueFormatter { string = "\(string) \(symbol)" } - if let signValue { - var sign = "" - if !signValue.isZero { - sign = signValue.isSignMinus ? "-" : "+" - } - string = "\(sign)\(string)" + var sign = "" + if !signValue.isZero { + sign = signValue.isSignMinus ? "-" : "+" + } + switch signType { + case .never: () + case .always: string = "\(sign)\(string)" + case .auto: if signValue.isSignMinus { string = "\(sign)\(string)" } } if tooSmall { @@ -189,7 +191,7 @@ class ValueFormatter { return nil } - return pattern.replacingOccurrences(of: "1", with: decorated(string: string, suffix: suffix)) + return pattern.replacingOccurrences(of: "1", with: decorated(string: string, suffix: suffix, signValue: value)) } } @@ -206,10 +208,10 @@ extension ValueFormatter { return nil } - return decorated(string: string, suffix: suffix, tooSmall: tooSmall) + return decorated(string: string, suffix: suffix, signValue: value, tooSmall: tooSmall) } - func formatShort(value: Decimal, decimalCount: Int, symbol: String? = nil, showSign: Bool = false) -> String? { + func formatShort(value: Decimal, decimalCount: Int, symbol: String? = nil, signType: SignType = .never) -> String? { let (transformedValue, digits, suffix, tooSmall) = transformedShort(value: value, maxDigits: decimalCount) let string: String? = rawFormatterQueue.sync { @@ -221,7 +223,7 @@ extension ValueFormatter { return nil } - return decorated(string: string, suffix: suffix, symbol: symbol, signValue: showSign ? value : nil, tooSmall: tooSmall) + return decorated(string: string, suffix: suffix, symbol: symbol, signType: signType, signValue: value, tooSmall: tooSmall) } func formatFull(value: Decimal, decimalCount: Int, symbol: String? = nil, showSign: Bool = false) -> String? { @@ -236,46 +238,46 @@ extension ValueFormatter { return nil } - return decorated(string: string, symbol: symbol, signValue: showSign ? value : nil) + return decorated(string: string, symbol: symbol, signValue: value) } - func formatShort(coinValue: CoinValue, showCode: Bool = true, showSign: Bool = false) -> String? { - formatShort(value: coinValue.value, decimalCount: coinValue.decimals, symbol: showCode ? coinValue.symbol : nil, showSign: showSign) + func formatShort(coinValue: CoinValue, showCode: Bool = true, signType: SignType = .never) -> String? { + formatShort(value: coinValue.value, decimalCount: coinValue.decimals, symbol: showCode ? coinValue.symbol : nil, signType: signType) } func formatFull(coinValue: CoinValue, showCode: Bool = true, showSign: Bool = false) -> String? { formatFull(value: coinValue.value, decimalCount: coinValue.decimals, symbol: showCode ? coinValue.symbol : nil, showSign: showSign) } - func formatShort(currency: Currency, value: Decimal, showSign: Bool = false) -> String? { + func formatShort(currency: Currency, value: Decimal, signType: SignType = .never) -> String? { let (transformedValue, digits, suffix, tooSmall) = transformedShort(value: value) guard let string = formattedCurrency(value: transformedValue, digits: digits, code: currency.code, symbol: currency.symbol, suffix: suffix) else { return nil } - return decorated(string: string, signValue: showSign ? value : nil, tooSmall: tooSmall) + return decorated(string: string, signType: signType, signValue: value, tooSmall: tooSmall) } - func formatShort(currencyValue: CurrencyValue, showSign: Bool = false) -> String? { - formatShort(currency: currencyValue.currency, value: currencyValue.value, showSign: showSign) + func formatShort(currencyValue: CurrencyValue, signType: SignType = .never) -> String? { + formatShort(currency: currencyValue.currency, value: currencyValue.value, signType: signType) } - func formatFull(currency: Currency, value: Decimal, showSign: Bool = false) -> String? { + func formatFull(currency: Currency, value: Decimal, signType: SignType = .never) -> String? { let (transformedValue, digits) = transformedFull(value: value, maxDigits: 18) guard let string = formattedCurrency(value: transformedValue, digits: digits, code: currency.code, symbol: currency.symbol) else { return nil } - return decorated(string: string, signValue: showSign ? value : nil) + return decorated(string: string, signType: signType, signValue: value) } - func formatFull(currencyValue: CurrencyValue, showSign: Bool = false) -> String? { - formatFull(currency: currencyValue.currency, value: currencyValue.value, showSign: showSign) + func formatFull(currencyValue: CurrencyValue, signType: SignType = .never) -> String? { + formatFull(currency: currencyValue.currency, value: currencyValue.value, signType: signType) } - func format(percentValue: Decimal, showSign: Bool = true) -> String? { + func format(percentValue: Decimal, signType: SignType = .never) -> String? { let (transformedValue, digits) = transformedFull(value: percentValue, maxDigits: 2) let string: String? = rawFormatterQueue.sync { @@ -287,6 +289,14 @@ extension ValueFormatter { return nil } - return decorated(string: string, signValue: showSign ? percentValue : nil) + "%" + return decorated(string: string, signType: signType, signValue: percentValue) + "%" + } +} + +extension ValueFormatter { + enum SignType { + case never + case auto + case always } }