mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Update formatting
This commit is contained in:
parent
20325dd69c
commit
a6e2f05b3a
@ -44,7 +44,7 @@ private enum BotCheckoutSection: Int32 {
|
||||
enum BotCheckoutEntry: ItemListNodeEntry {
|
||||
case header(PresentationTheme, TelegramMediaInvoice, String)
|
||||
case price(Int, PresentationTheme, String, String, Bool, Bool)
|
||||
case tip(Int, PresentationTheme, String, String, String, Int64, [(String, Int64)])
|
||||
case tip(Int, PresentationTheme, String, String, String, Int64, Int64, [(String, Int64)])
|
||||
case paymentMethod(PresentationTheme, String, String)
|
||||
case shippingInfo(PresentationTheme, String, String)
|
||||
case shippingMethod(PresentationTheme, String, String)
|
||||
@ -69,7 +69,7 @@ enum BotCheckoutEntry: ItemListNodeEntry {
|
||||
return 0
|
||||
case let .price(index, _, _, _, _, _):
|
||||
return 1 + Int32(index)
|
||||
case let .tip(index, _, _, _, _, _, _):
|
||||
case let .tip(index, _, _, _, _, _, _, _):
|
||||
return 1 + Int32(index)
|
||||
case .paymentMethod:
|
||||
return 10000 + 2
|
||||
@ -127,8 +127,8 @@ enum BotCheckoutEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .tip(lhsIndex, lhsTheme, lhsText, lhsCurrency, lhsValue, lhsNumericValue, lhsVariants):
|
||||
if case let .tip(rhsIndex, rhsTheme, rhsText, rhsCurrency, rhsValue, rhsNumericValue, rhsVariants) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsText == rhsText, lhsCurrency == rhsCurrency, lhsValue == rhsValue, lhsNumericValue == rhsNumericValue {
|
||||
case let .tip(lhsIndex, lhsTheme, lhsText, lhsCurrency, lhsValue, lhsNumericValue, lhsMaxValue, lhsVariants):
|
||||
if case let .tip(rhsIndex, rhsTheme, rhsText, rhsCurrency, rhsValue, rhsNumericValue, rhsMaxValue, rhsVariants) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsText == rhsText, lhsCurrency == rhsCurrency, lhsValue == rhsValue, lhsNumericValue == rhsNumericValue, lhsMaxValue == rhsMaxValue {
|
||||
if lhsVariants.count != rhsVariants.count {
|
||||
return false
|
||||
}
|
||||
@ -194,8 +194,8 @@ enum BotCheckoutEntry: ItemListNodeEntry {
|
||||
return BotCheckoutHeaderItem(account: arguments.account, theme: theme, invoice: invoice, botName: botName, sectionId: self.section)
|
||||
case let .price(_, theme, text, value, isFinal, hasSeparator):
|
||||
return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, hasSeparator: hasSeparator, sectionId: self.section)
|
||||
case let .tip(_, _, text, currency, value, numericValue, variants):
|
||||
return BotCheckoutTipItem(theme: presentationData.theme, title: text, currency: currency, value: value, numericValue: numericValue, availableVariants: variants, sectionId: self.section, updateValue: { value in
|
||||
case let .tip(_, _, text, currency, value, numericValue, maxValue, variants):
|
||||
return BotCheckoutTipItem(theme: presentationData.theme, strings: presentationData.strings, title: text, currency: currency, value: value, numericValue: numericValue, maxValue: maxValue, availableVariants: variants, sectionId: self.section, updateValue: { value in
|
||||
arguments.updateTip(value)
|
||||
})
|
||||
case let .paymentMethod(_, text, value):
|
||||
@ -324,7 +324,7 @@ private func botCheckoutControllerEntries(presentationData: PresentationData, st
|
||||
let tipTitle: String
|
||||
//TODO:localize
|
||||
tipTitle = "Tip (Optional)"
|
||||
entries.append(.tip(index, presentationData.theme, tipTitle, paymentForm.invoice.currency, "\(formatCurrencyAmount(currentTip ?? 0, currency: paymentForm.invoice.currency))", currentTip ?? 0, tip.suggested.map { item -> (String, Int64) in
|
||||
entries.append(.tip(index, presentationData.theme, tipTitle, paymentForm.invoice.currency, "\(formatCurrencyAmount(currentTip ?? 0, currency: paymentForm.invoice.currency))", currentTip ?? 0, tip.max, tip.suggested.map { item -> (String, Int64) in
|
||||
return ("\(formatCurrencyAmount(item, currency: paymentForm.invoice.currency))", item)
|
||||
}))
|
||||
index += 1
|
||||
|
@ -10,10 +10,12 @@ import TelegramStringFormatting
|
||||
|
||||
class BotCheckoutTipItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let title: String
|
||||
let currency: String
|
||||
let value: String
|
||||
let numericValue: Int64
|
||||
let maxValue: Int64
|
||||
let availableVariants: [(String, Int64)]
|
||||
let updateValue: (Int64) -> Void
|
||||
|
||||
@ -21,12 +23,14 @@ class BotCheckoutTipItem: ListViewItem, ItemListItem {
|
||||
|
||||
let requestsNoInset: Bool = true
|
||||
|
||||
init(theme: PresentationTheme, title: String, currency: String, value: String, numericValue: Int64, availableVariants: [(String, Int64)], sectionId: ItemListSectionId, updateValue: @escaping (Int64) -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, title: String, currency: String, value: String, numericValue: Int64, maxValue: Int64, availableVariants: [(String, Int64)], sectionId: ItemListSectionId, updateValue: @escaping (Int64) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.title = title
|
||||
self.currency = currency
|
||||
self.value = value
|
||||
self.numericValue = numericValue
|
||||
self.maxValue = maxValue
|
||||
self.availableVariants = availableVariants
|
||||
self.updateValue = updateValue
|
||||
self.sectionId = sectionId
|
||||
@ -154,6 +158,8 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||
let labelNode: TextNode
|
||||
private let textNode: TextFieldNode
|
||||
|
||||
private var formatterDelegate: CurrencyUITextFieldDelegate?
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private var valueNodes: [TipValueNode] = []
|
||||
|
||||
@ -187,7 +193,6 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||
self.addSubnode(self.scrollNode)
|
||||
|
||||
self.textNode.clipsToBounds = true
|
||||
self.textNode.textField.delegate = self
|
||||
self.textNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged)
|
||||
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
}
|
||||
@ -216,6 +221,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
//TODO:locali
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Enter Custom", font: textFont, textColor: textColor.withMultipliedAlpha(0.8)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||
@ -241,6 +247,25 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||
strongSelf.labelNode.isHidden = !text.isEmpty
|
||||
}
|
||||
|
||||
if strongSelf.formatterDelegate == nil {
|
||||
strongSelf.formatterDelegate = CurrencyUITextFieldDelegate(formatter: CurrencyFormatter(currency: item.currency, { formatter in
|
||||
formatter.maxValue = currencyToFractionalAmount(value: item.maxValue, currency: item.currency) ?? 10000.0
|
||||
formatter.minValue = 0.0
|
||||
formatter.hasDecimals = true
|
||||
}))
|
||||
strongSelf.formatterDelegate?.passthroughDelegate = strongSelf
|
||||
|
||||
strongSelf.formatterDelegate?.textUpdated = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.textFieldTextChanged(strongSelf.textNode.textField)
|
||||
}
|
||||
|
||||
strongSelf.textNode.clipsToBounds = true
|
||||
strongSelf.textNode.textField.delegate = strongSelf.formatterDelegate
|
||||
}
|
||||
|
||||
strongSelf.textNode.textField.typingAttributes = [NSAttributedString.Key.font: titleFont]
|
||||
strongSelf.textNode.textField.font = titleFont
|
||||
|
||||
@ -323,11 +348,6 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||
}
|
||||
|
||||
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
guard let item = self.item else {
|
||||
return false
|
||||
}
|
||||
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
178
submodules/BotPaymentsUI/Sources/Formatter/Currency.swift
Normal file
178
submodules/BotPaymentsUI/Sources/Formatter/Currency.swift
Normal file
@ -0,0 +1,178 @@
|
||||
//
|
||||
// CurrencyCode.swift
|
||||
// CurrencyText
|
||||
//
|
||||
// Created by Felipe Lefèvre Marino on 1/26/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Currency wraps all availabe currencies that can represented as formatted monetary values
|
||||
/// A currency code is a three-letter code that is, in most cases,
|
||||
/// composed of a country’s two-character Internet country code plus an extra character
|
||||
/// to denote the currency unit. For example, the currency code for the Australian
|
||||
/// dollar is “AUD”. Currency codes are based on the ISO 4217 standard
|
||||
public enum Currency: String {
|
||||
case afghani = "AFN",
|
||||
algerianDinar = "DZD",
|
||||
argentinePeso = "ARS",
|
||||
armenianDram = "AMD",
|
||||
arubanFlorin = "AWG",
|
||||
australianDollar = "AUD",
|
||||
azerbaijanManat = "AZN",
|
||||
bahamianDollar = "BSD",
|
||||
bahrainiDinar = "BHD",
|
||||
baht = "THB",
|
||||
balboa = "PAB",
|
||||
barbadosDollar = "BBD",
|
||||
belarusianRuble = "BYN",
|
||||
belizeDollar = "BZD",
|
||||
bermudianDollar = "BMD",
|
||||
boliviano = "BOB",
|
||||
bolívar = "VEF",
|
||||
brazilianReal = "BRL",
|
||||
bruneiDollar = "BND",
|
||||
bulgarianLev = "BGN",
|
||||
burundiFranc = "BIF",
|
||||
caboVerdeEscudo = "CVE",
|
||||
canadianDollar = "CAD",
|
||||
caymanIslandsDollar = "KYD",
|
||||
chileanPeso = "CLP",
|
||||
colombianPeso = "COP",
|
||||
comorianFranc = "KMF",
|
||||
congoleseFranc = "CDF",
|
||||
convertibleMark = "BAM",
|
||||
cordobaOro = "NIO",
|
||||
costaRicanColon = "CRC",
|
||||
cubanPeso = "CUP",
|
||||
czechKoruna = "CZK",
|
||||
dalasi = "GMD",
|
||||
danishKrone = "DKK",
|
||||
denar = "MKD",
|
||||
djiboutiFranc = "DJF",
|
||||
dobra = "STN",
|
||||
dollar = "USD",
|
||||
dominicanPeso = "DOP",
|
||||
dong = "VND",
|
||||
eastCaribbeanDollar = "XCD",
|
||||
egyptianPound = "EGP",
|
||||
elSalvadorColon = "SVC",
|
||||
ethiopianBirr = "ETB",
|
||||
euro = "EUR",
|
||||
falklandIslandsPound = "FKP",
|
||||
fijiDollar = "FJD",
|
||||
forint = "HUF",
|
||||
ghanaCedi = "GHS",
|
||||
gibraltarPound = "GIP",
|
||||
gourde = "HTG",
|
||||
guarani = "PYG",
|
||||
guineanFranc = "GNF",
|
||||
guyanaDollar = "GYD",
|
||||
hongKongDollar = "HKD",
|
||||
hryvnia = "UAH",
|
||||
icelandKrona = "ISK",
|
||||
indianRupee = "INR",
|
||||
iranianRial = "IRR",
|
||||
iraqiDinar = "IQD",
|
||||
jamaicanDollar = "JMD",
|
||||
jordanianDinar = "JOD",
|
||||
kenyanShilling = "KES",
|
||||
kina = "PGK",
|
||||
kuna = "HRK",
|
||||
kuwaitiDinar = "KWD",
|
||||
kwanza = "AOA",
|
||||
kyat = "MMK",
|
||||
laoKip = "LAK",
|
||||
lari = "GEL",
|
||||
lebanesePound = "LBP",
|
||||
lek = "ALL",
|
||||
lempira = "HNL",
|
||||
leone = "SLL",
|
||||
liberianDollar = "LRD",
|
||||
libyanDinar = "LYD",
|
||||
lilangeni = "SZL",
|
||||
loti = "LSL",
|
||||
malagasyAriary = "MGA",
|
||||
malawiKwacha = "MWK",
|
||||
malaysianRinggit = "MYR",
|
||||
mauritiusRupee = "MUR",
|
||||
mexicanPeso = "MXN",
|
||||
mexicanUnidadDeInversion = "MXV",
|
||||
moldovanLeu = "MDL",
|
||||
moroccanDirham = "MAD",
|
||||
mozambiqueMetical = "MZN",
|
||||
mvdol = "BOV",
|
||||
naira = "NGN",
|
||||
nakfa = "ERN",
|
||||
namibiaDollar = "NAD",
|
||||
nepaleseRupee = "NPR",
|
||||
netherlandsAntilleanGuilder = "ANG",
|
||||
newIsraeliSheqel = "ILS",
|
||||
newTaiwanDollar = "TWD",
|
||||
newZealandDollar = "NZD",
|
||||
ngultrum = "BTN",
|
||||
northKoreanWon = "KPW",
|
||||
norwegianKrone = "NOK",
|
||||
ouguiya = "MRU",
|
||||
paanga = "TOP",
|
||||
pakistanRupee = "PKR",
|
||||
pataca = "MOP",
|
||||
pesoConvertible = "CUC",
|
||||
pesoUruguayo = "UYU",
|
||||
philippinePiso = "PHP",
|
||||
poundSterling = "GBP",
|
||||
pula = "BWP",
|
||||
qatariRial = "QAR",
|
||||
quetzal = "GTQ",
|
||||
rand = "ZAR",
|
||||
rialOmani = "OMR",
|
||||
riel = "KHR",
|
||||
romanianLeu = "RON",
|
||||
rufiyaa = "MVR",
|
||||
rupiah = "IDR",
|
||||
russianRuble = "RUB",
|
||||
rwandaFranc = "RWF",
|
||||
saintHelenaPound = "SHP",
|
||||
saudiRiyal = "SAR",
|
||||
serbianDinar = "RSD",
|
||||
seychellesRupee = "SCR",
|
||||
singaporeDollar = "SGD",
|
||||
sol = "PEN",
|
||||
solomonIslandsDollar = "SBD",
|
||||
som = "KGS",
|
||||
somaliShilling = "SOS",
|
||||
somoni = "TJS",
|
||||
southSudanesePound = "SSP",
|
||||
sriLankaRupee = "LKR",
|
||||
sudanesePound = "SDG",
|
||||
surinamDollar = "SRD",
|
||||
swedishKrona = "SEK",
|
||||
swissFranc = "CHF",
|
||||
syrianPound = "SYP",
|
||||
taka = "BDT",
|
||||
tala = "WST",
|
||||
tanzanianShilling = "TZS",
|
||||
tenge = "KZT",
|
||||
trinidadAndTobagoDollar = "TTD",
|
||||
tugrik = "MNT",
|
||||
tunisianDinar = "TND",
|
||||
turkishLira = "TRY",
|
||||
turkmenistanNewManat = "TMT",
|
||||
uaeDirham = "AED",
|
||||
ugandaShilling = "UGX",
|
||||
unidadDeFomento = "CLF",
|
||||
unidadDeValorReal = "COU",
|
||||
uruguayPesoEnUnidadesIndexadas = "UYI",
|
||||
uzbekistanSum = "UZS",
|
||||
vatu = "VUV",
|
||||
wirEuro = "CHE",
|
||||
wirFranc = "CHW",
|
||||
won = "KRW",
|
||||
yemeniRial = "YER",
|
||||
yen = "JPY",
|
||||
yuanRenminbi = "CNY",
|
||||
zambianKwacha = "ZMW",
|
||||
zimbabweDollar = "ZWL",
|
||||
zloty = "PLN",
|
||||
none
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
//
|
||||
// CurrencyFormatter.swift
|
||||
// CurrencyText
|
||||
//
|
||||
// Created by Felipe Lefèvre Marino on 1/27/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import TelegramStringFormatting
|
||||
|
||||
// MARK: - Currency protocols
|
||||
|
||||
public protocol CurrencyFormatting {
|
||||
var maxDigitsCount: Int { get }
|
||||
var decimalDigits: Int { get set }
|
||||
var maxValue: Double? { get set }
|
||||
var minValue: Double? { get set }
|
||||
var initialText: String { get }
|
||||
var currencySymbol: String { get set }
|
||||
|
||||
func string(from double: Double) -> String?
|
||||
func unformatted(string: String) -> String?
|
||||
func double(from string: String) -> Double?
|
||||
}
|
||||
|
||||
public protocol CurrencyAdjusting {
|
||||
func formattedStringWithAdjustedDecimalSeparator(from string: String) -> String?
|
||||
func formattedStringAdjustedToFitAllowedValues(from string: String) -> String?
|
||||
}
|
||||
|
||||
// MARK: - Currency formatter
|
||||
|
||||
public class CurrencyFormatter: CurrencyFormatting {
|
||||
|
||||
/// Set the locale to retrieve the currency from
|
||||
/// You can pass a Swift type Locale or one of the
|
||||
/// Locales enum options - that encapsulates all available locales.
|
||||
public var locale: LocaleConvertible {
|
||||
set { self.numberFormatter.locale = newValue.locale }
|
||||
get { self.numberFormatter.locale }
|
||||
}
|
||||
|
||||
/// Set the desired currency type
|
||||
/// * Note: The currency take effetcs above the displayed currency symbol,
|
||||
/// however details such as decimal separators, grouping separators and others
|
||||
/// will be set based on the defined locale. So for a precise experience, please
|
||||
/// preferarbly setup both, when you are setting a currency that does not match the
|
||||
/// default/current user locale.
|
||||
public var currency: Currency {
|
||||
set { numberFormatter.currencyCode = newValue.rawValue }
|
||||
get { Currency(rawValue: numberFormatter.currencyCode) ?? .dollar }
|
||||
}
|
||||
|
||||
/// Define if currency symbol should be presented or not.
|
||||
/// Note: when set to false the current currency symbol is removed
|
||||
public var showCurrencySymbol: Bool = true {
|
||||
didSet {
|
||||
numberFormatter.currencySymbol = showCurrencySymbol ? numberFormatter.currencySymbol : ""
|
||||
}
|
||||
}
|
||||
|
||||
/// The currency's symbol.
|
||||
/// Can be used to read or set a custom symbol.
|
||||
/// Note: showCurrencySymbol must be set to true for
|
||||
/// the currencySymbol to be correctly changed.
|
||||
public var currencySymbol: String {
|
||||
set {
|
||||
guard showCurrencySymbol else { return }
|
||||
numberFormatter.currencySymbol = newValue
|
||||
}
|
||||
get { numberFormatter.currencySymbol }
|
||||
}
|
||||
|
||||
/// The lowest number allowed as input.
|
||||
/// This value is initially set to the text field text
|
||||
/// when defined.
|
||||
public var minValue: Double? {
|
||||
set {
|
||||
guard let newValue = newValue else { return }
|
||||
numberFormatter.minimum = NSNumber(value: newValue)
|
||||
}
|
||||
get {
|
||||
if let minValue = numberFormatter.minimum {
|
||||
return Double(truncating: minValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// The highest number allowed as input.
|
||||
/// The text field will not allow the user to increase the input
|
||||
/// value beyond it, when defined.
|
||||
public var maxValue: Double? {
|
||||
set {
|
||||
guard let newValue = newValue else { return }
|
||||
numberFormatter.maximum = NSNumber(value: newValue)
|
||||
}
|
||||
get {
|
||||
if let maxValue = numberFormatter.maximum {
|
||||
return Double(truncating: maxValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of decimal digits shown.
|
||||
/// default is set to zero.
|
||||
/// * Example: With decimal digits set to 3, if the value to represent is "1",
|
||||
/// the formatted text in the fractions will be ",001".
|
||||
/// Other than that with the value as 1, the formatted text fractions will be ",1".
|
||||
public var decimalDigits: Int {
|
||||
set {
|
||||
numberFormatter.minimumFractionDigits = newValue
|
||||
numberFormatter.maximumFractionDigits = newValue
|
||||
}
|
||||
get { numberFormatter.minimumFractionDigits }
|
||||
}
|
||||
|
||||
/// Set decimal numbers behavior.
|
||||
/// When set to true decimalDigits are automatically set to 2 (most currencies pattern),
|
||||
/// and the decimal separator is presented. Otherwise decimal digits are not shown and
|
||||
/// the separator gets hidden as well
|
||||
/// When reading it returns the current pattern based on the setup.
|
||||
/// Note: Setting decimal digits after, or alwaysShowsDecimalSeparator can overlap this definitios,
|
||||
/// and should be only done if you need specific cases
|
||||
public var hasDecimals: Bool {
|
||||
set {
|
||||
self.decimalDigits = newValue ? 2 : 0
|
||||
self.numberFormatter.alwaysShowsDecimalSeparator = newValue ? true : false
|
||||
}
|
||||
get { decimalDigits != 0 }
|
||||
}
|
||||
|
||||
/// Defines the string that is the decimal separator
|
||||
/// Note: only presented when hasDecimals is true OR decimalDigits
|
||||
/// is greater than 0.
|
||||
public var decimalSeparator: String {
|
||||
set { self.numberFormatter.currencyDecimalSeparator = newValue }
|
||||
get { numberFormatter.currencyDecimalSeparator }
|
||||
}
|
||||
|
||||
/// Can be used to set a custom currency code string
|
||||
public var currencyCode: String {
|
||||
set { self.numberFormatter.currencyCode = newValue }
|
||||
get { numberFormatter.currencyCode }
|
||||
}
|
||||
|
||||
/// Sets if decimal separator should always be presented,
|
||||
/// even when decimal digits are disabled
|
||||
public var alwaysShowsDecimalSeparator: Bool {
|
||||
set { self.numberFormatter.alwaysShowsDecimalSeparator = newValue }
|
||||
get { numberFormatter.alwaysShowsDecimalSeparator }
|
||||
}
|
||||
|
||||
/// The amount of grouped numbers. This definition is fixed for at least
|
||||
/// the first non-decimal group of numbers, and is applied to all other
|
||||
/// groups if secondaryGroupingSize does not have another value.
|
||||
public var groupingSize: Int {
|
||||
set { self.numberFormatter.groupingSize = newValue }
|
||||
get { numberFormatter.groupingSize }
|
||||
}
|
||||
|
||||
/// The amount of grouped numbers after the first group.
|
||||
/// Example: for the given value of 99999999999, when grouping size
|
||||
/// is set to 3 and secondaryGroupingSize has 4 as value,
|
||||
/// the number is represented as: (9999) (9999) [999].
|
||||
/// Beign [] grouping size and () secondary grouping size.
|
||||
public var secondaryGroupingSize: Int {
|
||||
set { self.numberFormatter.secondaryGroupingSize = newValue }
|
||||
get { numberFormatter.secondaryGroupingSize }
|
||||
}
|
||||
|
||||
/// Defines the string that is shown between groups of numbers
|
||||
/// * Example: a monetary value of a thousand (1000) with a grouping
|
||||
/// separator == "." is represented as `1.000` *.
|
||||
/// Note: It automatically sets hasGroupingSeparator to true.
|
||||
public var groupingSeparator: String {
|
||||
set {
|
||||
self.numberFormatter.currencyGroupingSeparator = newValue
|
||||
self.numberFormatter.usesGroupingSeparator = true
|
||||
}
|
||||
get { self.numberFormatter.currencyGroupingSeparator }
|
||||
}
|
||||
|
||||
/// Sets if has separator between all group of numbers.
|
||||
/// * Example: when set to false, a bug number such as a million
|
||||
/// is represented by tight numbers "1000000". Otherwise if set
|
||||
/// to true each group is separated by the defined `groupingSeparator`. *
|
||||
/// Note: When set to true only works by defining a grouping separator.
|
||||
public var hasGroupingSeparator: Bool {
|
||||
set { self.numberFormatter.usesGroupingSeparator = newValue }
|
||||
get { self.numberFormatter.usesGroupingSeparator }
|
||||
}
|
||||
|
||||
/// Value that will be presented when the text field
|
||||
/// text values matches zero (0)
|
||||
public var zeroSymbol: String? {
|
||||
set { numberFormatter.zeroSymbol = newValue }
|
||||
get { numberFormatter.zeroSymbol }
|
||||
}
|
||||
|
||||
/// Value that will be presented when the text field
|
||||
/// is empty. The default is "" - empty string
|
||||
public var nilSymbol: String {
|
||||
set { numberFormatter.nilSymbol = newValue }
|
||||
get { return numberFormatter.nilSymbol }
|
||||
}
|
||||
|
||||
/// Encapsulated Number formatter
|
||||
let numberFormatter: NumberFormatter
|
||||
|
||||
/// Maximum allowed number of integers
|
||||
public var maxIntegers: Int? {
|
||||
set {
|
||||
guard let maxIntegers = newValue else { return }
|
||||
numberFormatter.maximumIntegerDigits = maxIntegers
|
||||
}
|
||||
get { return numberFormatter.maximumIntegerDigits }
|
||||
}
|
||||
|
||||
/// Returns the maximum allowed number of numerical characters
|
||||
public var maxDigitsCount: Int {
|
||||
numberFormatter.maximumIntegerDigits + numberFormatter.maximumFractionDigits
|
||||
}
|
||||
|
||||
/// The value zero formatted to serve as initial text.
|
||||
public var initialText: String {
|
||||
numberFormatter.string(from: 0) ?? "0.0"
|
||||
}
|
||||
|
||||
//MARK: - INIT
|
||||
|
||||
/// Handler to initialize a new style.
|
||||
public typealias InitHandler = ((CurrencyFormatter) -> (Void))
|
||||
|
||||
/// Initialize a new currency formatter with optional configuration handler callback.
|
||||
///
|
||||
/// - Parameter handler: configuration handler callback.
|
||||
|
||||
public init(currency: String, _ handler: InitHandler? = nil) {
|
||||
numberFormatter = setupCurrencyNumberFormatter(currency: currency)
|
||||
|
||||
numberFormatter.alwaysShowsDecimalSeparator = false
|
||||
/*numberFormatter.numberStyle = .currency
|
||||
|
||||
numberFormatter.minimumFractionDigits = 2
|
||||
numberFormatter.maximumFractionDigits = 2
|
||||
numberFormatter.minimumIntegerDigits = 1*/
|
||||
|
||||
handler?(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Format
|
||||
extension CurrencyFormatter {
|
||||
|
||||
/// Returns a currency string from a given double value.
|
||||
///
|
||||
/// - Parameter double: the monetary amount.
|
||||
/// - Returns: formatted currency string.
|
||||
public func string(from double: Double) -> String? {
|
||||
let validValue = valueAdjustedToFitAllowedValues(from: double)
|
||||
return numberFormatter.string(from: validValue)
|
||||
}
|
||||
|
||||
/// Returns a double from a string that represents a numerical value.
|
||||
///
|
||||
/// - Parameter string: string that describes the numerical value.
|
||||
/// - Returns: the value as a Double.
|
||||
public func double(from string: String) -> Double? {
|
||||
Double(string)
|
||||
}
|
||||
|
||||
/// Receives a currency formatted string and returns its
|
||||
/// numerical/unformatted representation.
|
||||
///
|
||||
/// - Parameter string: currency formatted string
|
||||
/// - Returns: numerical representation
|
||||
public func unformatted(string: String) -> String? {
|
||||
string.numeralFormat()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Currency adjusting conformance
|
||||
|
||||
extension CurrencyFormatter: CurrencyAdjusting {
|
||||
|
||||
/// Receives a currency formatted String, and returns it with its decimal separator adjusted.
|
||||
///
|
||||
/// _Note_: Useful when appending values to a currency formatted String.
|
||||
/// E.g. "$ 23.24" after users taps an additional number, is equal = "$ 23.247".
|
||||
/// Which gets updated to "$ 232.47".
|
||||
///
|
||||
/// - Parameter string: The currency formatted String
|
||||
/// - Returns: The currency formatted received String with its decimal separator adjusted
|
||||
public func formattedStringWithAdjustedDecimalSeparator(from string: String) -> String? {
|
||||
let adjustedString = numeralStringWithAdjustedDecimalSeparator(from: string)
|
||||
guard let value = double(from: adjustedString) else { return nil }
|
||||
|
||||
return self.numberFormatter.string(from: value)
|
||||
}
|
||||
|
||||
/// Receives a currency formatted String, and returns it to fit the formatter's min and max values, when needed.
|
||||
///
|
||||
/// - Parameter string: The currency formatted String
|
||||
/// - Returns: The currency formatted String, or the formatted version of its closes allowed value, min or max, depending on the closest boundary.
|
||||
public func formattedStringAdjustedToFitAllowedValues(from string: String) -> String? {
|
||||
let adjustedString = numeralStringWithAdjustedDecimalSeparator(from: string)
|
||||
guard let originalValue = double(from: adjustedString) else { return nil }
|
||||
|
||||
return self.string(from: originalValue)
|
||||
}
|
||||
|
||||
/// Receives a currency formatted String, and returns a numeral version of it with its decimal separator adjusted.
|
||||
///
|
||||
/// E.g. "$ 23.24", after users taps an additional number, get equal as "$ 23.247". The returned value would be "232.47".
|
||||
///
|
||||
/// - Parameter string: The currency formatted String
|
||||
/// - Returns: The received String with numeral format and with its decimal separator adjusted
|
||||
private func numeralStringWithAdjustedDecimalSeparator(from string: String) -> String {
|
||||
var updatedString = string.numeralFormat()
|
||||
let isNegative: Bool = string.contains(String.negativeSymbol)
|
||||
|
||||
updatedString = isNegative ? .negativeSymbol + updatedString : updatedString
|
||||
updatedString.updateDecimalSeparator(decimalDigits: decimalDigits)
|
||||
|
||||
return updatedString
|
||||
}
|
||||
|
||||
/// Receives a Double value, and returns it adjusted to fit min and max allowed values, when needed.
|
||||
/// If the value respect number formatter's min and max, it will be returned without changes.
|
||||
///
|
||||
/// - Parameter value: The value to be adjusted if needed
|
||||
/// - Returns: The value updated or not, depending on the formatter's settings
|
||||
private func valueAdjustedToFitAllowedValues(from value: Double) -> Double {
|
||||
if let minValue = minValue, value < minValue {
|
||||
return minValue
|
||||
} else if let maxValue = maxValue, value > maxValue {
|
||||
return maxValue
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
755
submodules/BotPaymentsUI/Sources/Formatter/CurrencyLocale.swift
Normal file
755
submodules/BotPaymentsUI/Sources/Formatter/CurrencyLocale.swift
Normal file
@ -0,0 +1,755 @@
|
||||
//
|
||||
// CurrencyLocale.swift
|
||||
// CurrencyText
|
||||
//
|
||||
// Created by Felipe Lefèvre Marino on 1/26/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// All locales were extracted from:
|
||||
/// jacobbubu/ioslocaleidentifiers.csv - https://gist.github.com/jacobbubu/1836273
|
||||
|
||||
/// The LocaleConvertible pattern is inspired in SwiftDate by malcommac
|
||||
/// https://github.com/malcommac/SwiftDate
|
||||
|
||||
/// LocaleConvertible defines the behavior to convert locale info to system Locale type
|
||||
public protocol LocaleConvertible {
|
||||
var locale: Locale { get }
|
||||
}
|
||||
|
||||
extension Locale: LocaleConvertible {
|
||||
public var locale: Locale { return self }
|
||||
}
|
||||
|
||||
/// Defines locales available in system
|
||||
public enum CurrencyLocale: String, LocaleConvertible {
|
||||
|
||||
case current = "current"
|
||||
case autoUpdating = "currentAutoUpdating"
|
||||
|
||||
case afrikaans = "af"
|
||||
case afrikaansNamibia = "af_NA"
|
||||
case afrikaansSouthAfrica = "af_ZA"
|
||||
case aghem = "agq"
|
||||
case aghemCameroon = "agq_CM"
|
||||
case akan = "ak"
|
||||
case akanGhana = "ak_GH"
|
||||
case albanian = "sq"
|
||||
case albanianAlbania = "sq_AL"
|
||||
case albanianKosovo = "sq_XK"
|
||||
case albanianMacedonia = "sq_MK"
|
||||
case amharic = "am"
|
||||
case amharicEthiopia = "am_ET"
|
||||
case arabic = "ar"
|
||||
case arabicAlgeria = "ar_DZ"
|
||||
case arabicBahrain = "ar_BH"
|
||||
case arabicChad = "ar_TD"
|
||||
case arabicComoros = "ar_KM"
|
||||
case arabicDjibouti = "ar_DJ"
|
||||
case arabicEgypt = "ar_EG"
|
||||
case arabicEritrea = "ar_ER"
|
||||
case arabicIraq = "ar_IQ"
|
||||
case arabicIsrael = "ar_IL"
|
||||
case arabicJordan = "ar_JO"
|
||||
case arabicKuwait = "ar_KW"
|
||||
case arabicLebanon = "ar_LB"
|
||||
case arabicLibya = "ar_LY"
|
||||
case arabicMauritania = "ar_MR"
|
||||
case arabicMorocco = "ar_MA"
|
||||
case arabicOman = "ar_OM"
|
||||
case arabicPalestinianTerritories = "ar_PS"
|
||||
case arabicQatar = "ar_QA"
|
||||
case arabicSaudiArabia = "ar_SA"
|
||||
case arabicSomalia = "ar_SO"
|
||||
case arabicSouthSudan = "ar_SS"
|
||||
case arabicSudan = "ar_SD"
|
||||
case arabicSyria = "ar_SY"
|
||||
case arabicTunisia = "ar_TN"
|
||||
case arabicUnitedArabEmirates = "ar_AE"
|
||||
case arabicWesternSahara = "ar_EH"
|
||||
case arabicWorld = "ar_001"
|
||||
case arabicYemen = "ar_YE"
|
||||
case armenian = "hy"
|
||||
case armenianArmenia = "hy_AM"
|
||||
case assamese = "as"
|
||||
case assameseIndia = "as_IN"
|
||||
case asu = "asa"
|
||||
case asuTanzania = "asa_TZ"
|
||||
case azerbaijani = "az_Latn"
|
||||
case azerbaijaniAzerbaijan = "az_Latn_AZ"
|
||||
case azerbaijaniCyrillic = "az_Cyrl"
|
||||
case azerbaijaniCyrillicAzerbaijan = "az_Cyrl_AZ"
|
||||
case bafia = "ksf"
|
||||
case bafiaCameroon = "ksf_CM"
|
||||
case bambara = "bm_Latn"
|
||||
case bambaraMali = "bm_Latn_ML"
|
||||
case basaa = "bas"
|
||||
case basaaCameroon = "bas_CM"
|
||||
case basque = "eu"
|
||||
case basqueSpain = "eu_ES"
|
||||
case belarusian = "be"
|
||||
case belarusianBelarus = "be_BY"
|
||||
case bemba = "bem"
|
||||
case bembaZambia = "bem_ZM"
|
||||
case bena = "bez"
|
||||
case benaTanzania = "bez_TZ"
|
||||
case bengali = "bn"
|
||||
case bengaliBangladesh = "bn_BD"
|
||||
case engaliIndia = "bn_IN"
|
||||
case bodo = "brx"
|
||||
case bodoIndia = "brx_IN"
|
||||
case bosnian = "bs_Latn"
|
||||
case bosnianBosniaHerzegovina = "bs_Latn_BA"
|
||||
case bosnianCyrillic = "bs_Cyrl"
|
||||
case bosnianCyrillicBosniaHerzegovina = "bs_Cyrl_BA"
|
||||
case breton = "br"
|
||||
case bretonFrance = "br_FR"
|
||||
case bulgarian = "bg"
|
||||
case bulgarianBulgaria = "bg_BG"
|
||||
case burmese = "my"
|
||||
case burmeseMyanmarBurma = "my_MM"
|
||||
case catalan = "ca"
|
||||
case catalanAndorra = "ca_AD"
|
||||
case catalanFrance = "ca_FR"
|
||||
case catalanItaly = "ca_IT"
|
||||
case catalanSpain = "ca_ES"
|
||||
case centralAtlasTamazight = "tzm_Latn"
|
||||
case centralAtlasTamazightMorocco = "tzm_Latn_MA"
|
||||
case centralKurdish = "ckb"
|
||||
case centralKurdishIran = "ckb_IR"
|
||||
case centralKurdishIraq = "ckb_IQ"
|
||||
case cherokee = "chr"
|
||||
case cherokeeUnitedStates = "chr_US"
|
||||
case chiga = "cgg"
|
||||
case chigaUganda = "cgg_UG"
|
||||
case chinese = "zh"
|
||||
case chineseChina = "zh_Hans_CN"
|
||||
case chineseHongKongSarChina = "zh_Hant_HK"
|
||||
case chineseMacauSarChina = "zh_Hant_MO"
|
||||
case chineseSimplified = "zh_Hans"
|
||||
case chineseSimplifiedHongKongSarChina = "zh_Hans_HK"
|
||||
case chineseSimplifiedMacauSarChina = "zh_Hans_MO"
|
||||
case chineseSingapore = "zh_Hans_SG"
|
||||
case chineseTaiwan = "zh_Hant_TW"
|
||||
case chineseTraditional = "zh_Hant"
|
||||
case colognian = "ksh"
|
||||
case colognianGermany = "ksh_DE"
|
||||
case cornish = "kw"
|
||||
case cornishUnitedKingdom = "kw_GB"
|
||||
case croatian = "hr"
|
||||
case croatianBosniaHerzegovina = "hr_BA"
|
||||
case croatianCroatia = "hr_HR"
|
||||
case czech = "cs"
|
||||
case czechCzechRepublic = "cs_CZ"
|
||||
case danish = "da"
|
||||
case danishDenmark = "da_DK"
|
||||
case danishGreenland = "da_GL"
|
||||
case duala = "dua"
|
||||
case dualaCameroon = "dua_CM"
|
||||
case dutch = "nl"
|
||||
case dutchAruba = "nl_AW"
|
||||
case dutchBelgium = "nl_BE"
|
||||
case dutchCaribbeanNetherlands = "nl_BQ"
|
||||
case dutchCuraao = "nl_CW"
|
||||
case dutchNetherlands = "nl_NL"
|
||||
case dutchSintMaarten = "nl_SX"
|
||||
case dutchSuriname = "nl_SR"
|
||||
case dzongkha = "dz"
|
||||
case dzongkhaBhutan = "dz_BT"
|
||||
case embu = "ebu"
|
||||
case embuKenya = "ebu_KE"
|
||||
case english = "en"
|
||||
case englishAlbania = "en_AL"
|
||||
case englishAmericanSamoa = "en_AS"
|
||||
case englishAndorra = "en_AD"
|
||||
case englishAnguilla = "en_AI"
|
||||
case englishAntiguaBarbuda = "en_AG"
|
||||
case englishAustralia = "en_AU"
|
||||
case englishAustria = "en_AT"
|
||||
case englishBahamas = "en_BS"
|
||||
case englishBarbados = "en_BB"
|
||||
case englishBelgium = "en_BE"
|
||||
case englishBelize = "en_BZ"
|
||||
case englishBermuda = "en_BM"
|
||||
case englishBosniaHerzegovina = "en_BA"
|
||||
case englishBotswana = "en_BW"
|
||||
case englishBritishIndianOceanTerritory = "en_IO"
|
||||
case englishBritishVirginIslands = "en_VG"
|
||||
case englishCameroon = "en_CM"
|
||||
case englishCanada = "en_CA"
|
||||
case englishCaymanIslands = "en_KY"
|
||||
case englishChristmasIsland = "en_CX"
|
||||
case englishCocosKeelingIslands = "en_CC"
|
||||
case englishCookIslands = "en_CK"
|
||||
case englishCroatia = "en_HR"
|
||||
case englishCyprus = "en_CY"
|
||||
case englishCzechRepublic = "en_CZ"
|
||||
case englishDenmark = "en_DK"
|
||||
case englishDiegoGarcia = "en_DG"
|
||||
case englishDominica = "en_DM"
|
||||
case englishEritrea = "en_ER"
|
||||
case englishEstonia = "en_EE"
|
||||
case englishEurope = "en_150"
|
||||
case englishFalklandIslands = "en_FK"
|
||||
case englishFiji = "en_FJ"
|
||||
case englishFinland = "en_FI"
|
||||
case englishFrance = "en_FR"
|
||||
case englishGambia = "en_GM"
|
||||
case englishGermany = "en_DE"
|
||||
case englishGhana = "en_GH"
|
||||
case englishGibraltar = "en_GI"
|
||||
case englishGreece = "en_GR"
|
||||
case englishGrenada = "en_GD"
|
||||
case englishGuam = "en_GU"
|
||||
case englishGuernsey = "en_GG"
|
||||
case englishGuyana = "en_GY"
|
||||
case englishHongKongSarChina = "en_HK"
|
||||
case englishHungary = "en_HU"
|
||||
case englishIceland = "en_IS"
|
||||
case englishIndia = "en_IN"
|
||||
case englishIreland = "en_IE"
|
||||
case englishIsleOfMan = "en_IM"
|
||||
case englishIsrael = "en_IL"
|
||||
case englishItaly = "en_IT"
|
||||
case englishJamaica = "en_JM"
|
||||
case englishJersey = "en_JE"
|
||||
case englishKenya = "en_KE"
|
||||
case englishKiribati = "en_KI"
|
||||
case englishLatvia = "en_LV"
|
||||
case englishLesotho = "en_LS"
|
||||
case englishLiberia = "en_LR"
|
||||
case englishLithuania = "en_LT"
|
||||
case englishLuxembourg = "en_LU"
|
||||
case englishMacauSarChina = "en_MO"
|
||||
case englishMadagascar = "en_MG"
|
||||
case englishMalawi = "en_MW"
|
||||
case englishMalaysia = "en_MY"
|
||||
case englishMalta = "en_MT"
|
||||
case englishMarshallIslands = "en_MH"
|
||||
case englishMauritius = "en_MU"
|
||||
case englishMicronesia = "en_FM"
|
||||
case englishMontenegro = "en_ME"
|
||||
case englishMontserrat = "en_MS"
|
||||
case englishNamibia = "en_NA"
|
||||
case englishNauru = "en_NR"
|
||||
case englishNetherlands = "en_NL"
|
||||
case englishNewZealand = "en_NZ"
|
||||
case englishNigeria = "en_NG"
|
||||
case englishNiue = "en_NU"
|
||||
case englishNorfolkIsland = "en_NF"
|
||||
case englishNorthernMarianaIslands = "en_MP"
|
||||
case englishNorway = "en_NO"
|
||||
case englishPakistan = "en_PK"
|
||||
case englishPalau = "en_PW"
|
||||
case englishPapuaNewGuinea = "en_PG"
|
||||
case englishPhilippines = "en_PH"
|
||||
case englishPitcairnIslands = "en_PN"
|
||||
case englishPoland = "en_PL"
|
||||
case englishPortugal = "en_PT"
|
||||
case englishPuertoRico = "en_PR"
|
||||
case englishRomania = "en_RO"
|
||||
case englishRussia = "en_RU"
|
||||
case englishRwanda = "en_RW"
|
||||
case englishSamoa = "en_WS"
|
||||
case englishSeychelles = "en_SC"
|
||||
case englishSierraLeone = "en_SL"
|
||||
case englishSingapore = "en_SG"
|
||||
case englishSintMaarten = "en_SX"
|
||||
case englishSlovakia = "en_SK"
|
||||
case englishSlovenia = "en_SI"
|
||||
case englishSolomonIslands = "en_SB"
|
||||
case englishSouthAfrica = "en_ZA"
|
||||
case englishSouthSudan = "en_SS"
|
||||
case englishSpain = "en_ES"
|
||||
case englishStHelena = "en_SH"
|
||||
case englishStKittsNevis = "en_KN"
|
||||
case englishStLucia = "en_LC"
|
||||
case englishStVincentGrenadines = "en_VC"
|
||||
case englishSudan = "en_SD"
|
||||
case englishSwaziland = "en_SZ"
|
||||
case englishSweden = "en_SE"
|
||||
case englishSwitzerland = "en_CH"
|
||||
case englishTanzania = "en_TZ"
|
||||
case englishTokelau = "en_TK"
|
||||
case englishTonga = "en_TO"
|
||||
case englishTrinidadTobago = "en_TT"
|
||||
case englishTurkey = "en_TR"
|
||||
case englishTurksCaicosIslands = "en_TC"
|
||||
case englishTuvalu = "en_TV"
|
||||
case englishUSOutlyingIslands = "en_UM"
|
||||
case englishUSVirginIslands = "en_VI"
|
||||
case englishUganda = "en_UG"
|
||||
case englishUnitedKingdom = "en_GB"
|
||||
case englishUnitedStates = "en_US"
|
||||
case englishUnitedStatesComputer = "en_US_POSIX"
|
||||
case englishVanuatu = "en_VU"
|
||||
case englishWorld = "en_001"
|
||||
case englishZambia = "en_ZM"
|
||||
case englishZimbabwe = "en_ZW"
|
||||
case esperanto = "eo"
|
||||
case estonian = "et"
|
||||
case estonianEstonia = "et_EE"
|
||||
case ewe = "ee"
|
||||
case eweGhana = "ee_GH"
|
||||
case eweTogo = "ee_TG"
|
||||
case ewondo = "ewo"
|
||||
case ewondoCameroon = "ewo_CM"
|
||||
case faroese = "fo"
|
||||
case faroeseFaroeIslands = "fo_FO"
|
||||
case filipino = "fil"
|
||||
case filipinoPhilippines = "fil_PH"
|
||||
case finnish = "fi"
|
||||
case finnishFinland = "fi_FI"
|
||||
case french = "fr"
|
||||
case frenchAlgeria = "fr_DZ"
|
||||
case frenchBelgium = "fr_BE"
|
||||
case frenchBenin = "fr_BJ"
|
||||
case frenchBurkinaFaso = "fr_BF"
|
||||
case frenchBurundi = "fr_BI"
|
||||
case frenchCameroon = "fr_CM"
|
||||
case frenchCanada = "fr_CA"
|
||||
case frenchCentralAfricanRepublic = "fr_CF"
|
||||
case frenchChad = "fr_TD"
|
||||
case frenchComoros = "fr_KM"
|
||||
case frenchCongoBrazzaville = "fr_CG"
|
||||
case frenchCongoKinshasa = "fr_CD"
|
||||
case frenchCteDivoire = "fr_CI"
|
||||
case frenchDjibouti = "fr_DJ"
|
||||
case frenchEquatorialGuinea = "fr_GQ"
|
||||
case frenchFrance = "fr_FR"
|
||||
case frenchFrenchGuiana = "fr_GF"
|
||||
case frenchFrenchPolynesia = "fr_PF"
|
||||
case frenchGabon = "fr_GA"
|
||||
case frenchGuadeloupe = "fr_GP"
|
||||
case frenchGuinea = "fr_GN"
|
||||
case frenchHaiti = "fr_HT"
|
||||
case frenchLuxembourg = "fr_LU"
|
||||
case frenchMadagascar = "fr_MG"
|
||||
case frenchMali = "fr_ML"
|
||||
case frenchMartinique = "fr_MQ"
|
||||
case frenchMauritania = "fr_MR"
|
||||
case frenchMauritius = "fr_MU"
|
||||
case frenchMayotte = "fr_YT"
|
||||
case frenchMonaco = "fr_MC"
|
||||
case frenchMorocco = "fr_MA"
|
||||
case frenchNewCaledonia = "fr_NC"
|
||||
case frenchNiger = "fr_NE"
|
||||
case frenchRunion = "fr_RE"
|
||||
case frenchRwanda = "fr_RW"
|
||||
case frenchSenegal = "fr_SN"
|
||||
case frenchSeychelles = "fr_SC"
|
||||
case frenchStBarthlemy = "fr_BL"
|
||||
case frenchStMartin = "fr_MF"
|
||||
case frenchStPierreMiquelon = "fr_PM"
|
||||
case frenchSwitzerland = "fr_CH"
|
||||
case frenchSyria = "fr_SY"
|
||||
case frenchTogo = "fr_TG"
|
||||
case frenchTunisia = "fr_TN"
|
||||
case frenchVanuatu = "fr_VU"
|
||||
case frenchWallisFutuna = "fr_WF"
|
||||
case friulian = "fur"
|
||||
case friulianItaly = "fur_IT"
|
||||
case fulah = "ff"
|
||||
case fulahCameroon = "ff_CM"
|
||||
case fulahGuinea = "ff_GN"
|
||||
case fulahMauritania = "ff_MR"
|
||||
case fulahSenegal = "ff_SN"
|
||||
case galician = "gl"
|
||||
case galicianSpain = "gl_ES"
|
||||
case ganda = "lg"
|
||||
case gandaUganda = "lg_UG"
|
||||
case georgian = "ka"
|
||||
case georgianGeorgia = "ka_GE"
|
||||
case german = "de"
|
||||
case germanAustria = "de_AT"
|
||||
case germanBelgium = "de_BE"
|
||||
case germanGermany = "de_DE"
|
||||
case germanLiechtenstein = "de_LI"
|
||||
case germanLuxembourg = "de_LU"
|
||||
case germanSwitzerland = "de_CH"
|
||||
case greek = "el"
|
||||
case greekCyprus = "el_CY"
|
||||
case greekGreece = "el_GR"
|
||||
case gujarati = "gu"
|
||||
case gujaratiIndia = "gu_IN"
|
||||
case gusii = "guz"
|
||||
case gusiiKenya = "guz_KE"
|
||||
case hausa = "ha_Latn"
|
||||
case hausaGhana = "ha_Latn_GH"
|
||||
case hausaNiger = "ha_Latn_NE"
|
||||
case hausaNigeria = "ha_Latn_NG"
|
||||
case hawaiian = "haw"
|
||||
case hawaiianUnitedStates = "haw_US"
|
||||
case hebrew = "he"
|
||||
case hebrewIsrael = "he_IL"
|
||||
case hindi = "hi"
|
||||
case hindiIndia = "hi_IN"
|
||||
case hungarian = "hu"
|
||||
case hungarianHungary = "hu_HU"
|
||||
case icelandic = "is"
|
||||
case icelandicIceland = "is_IS"
|
||||
case igbo = "ig"
|
||||
case igboNigeria = "ig_NG"
|
||||
case inariSami = "smn"
|
||||
case inariSamiFinland = "smn_FI"
|
||||
case indonesian = "id"
|
||||
case indonesianIndonesia = "id_ID"
|
||||
case inuktitut = "iu"
|
||||
case inuktitutUnifiedCanadianAboriginalSyllabics = "iu_Cans"
|
||||
case inuktitutUnifiedCanadianAboriginalSyllabicsCanada = "iu_Cans_CA"
|
||||
case irish = "ga"
|
||||
case irishIreland = "ga_IE"
|
||||
case italian = "it"
|
||||
case italianItaly = "it_IT"
|
||||
case italianSanMarino = "it_SM"
|
||||
case italianSwitzerland = "it_CH"
|
||||
case japanese = "ja"
|
||||
case japaneseJapan = "ja_JP"
|
||||
case jolaFonyi = "dyo"
|
||||
case jolaFonyiSenegal = "dyo_SN"
|
||||
case kabuverdianu = "kea"
|
||||
case kabuverdianuCapeVerde = "kea_CV"
|
||||
case kabyle = "kab"
|
||||
case kabyleAlgeria = "kab_DZ"
|
||||
case kako = "kkj"
|
||||
case kakoCameroon = "kkj_CM"
|
||||
case kalaallisut = "kl"
|
||||
case kalaallisutGreenland = "kl_GL"
|
||||
case kalenjin = "kln"
|
||||
case kalenjinKenya = "kln_KE"
|
||||
case kamba = "kam"
|
||||
case kambaKenya = "kam_KE"
|
||||
case kannada = "kn"
|
||||
case kannadaIndia = "kn_IN"
|
||||
case kashmiri = "ks"
|
||||
case kashmiriArabic = "ks_Arab"
|
||||
case kashmiriArabicIndia = "ks_Arab_IN"
|
||||
case kazakh = "kk_Cyrl"
|
||||
case kazakhKazakhstan = "kk_Cyrl_KZ"
|
||||
case khmer = "km"
|
||||
case khmerCambodia = "km_KH"
|
||||
case kikuyu = "ki"
|
||||
case kikuyuKenya = "ki_KE"
|
||||
case kinyarwanda = "rw"
|
||||
case kinyarwandaRwanda = "rw_RW"
|
||||
case konkani = "kok"
|
||||
case konkaniIndia = "kok_IN"
|
||||
case korean = "ko"
|
||||
case koreanNorthKorea = "ko_KP"
|
||||
case koreanSouthKorea = "ko_KR"
|
||||
case koyraChiini = "khq"
|
||||
case koyraChiiniMali = "khq_ML"
|
||||
case koyraboroSenni = "ses"
|
||||
case koyraboroSenniMali = "ses_ML"
|
||||
case kwasio = "nmg"
|
||||
case kwasioCameroon = "nmg_CM"
|
||||
case kyrgyz = "ky_Cyrl"
|
||||
case kyrgyzKyrgyzstan = "ky_Cyrl_KG"
|
||||
case lakota = "lkt"
|
||||
case lakotaUnitedStates = "lkt_US"
|
||||
case langi = "lag"
|
||||
case langiTanzania = "lag_TZ"
|
||||
case lao = "lo"
|
||||
case laoLaos = "lo_LA"
|
||||
case latvian = "lv"
|
||||
case latvianLatvia = "lv_LV"
|
||||
case lingala = "ln"
|
||||
case lingalaAngola = "ln_AO"
|
||||
case lingalaCentralAfricanRepublic = "ln_CF"
|
||||
case lingalaCongoBrazzaville = "ln_CG"
|
||||
case lingalaCongoKinshasa = "ln_CD"
|
||||
case lithuanian = "lt"
|
||||
case lithuanianLithuania = "lt_LT"
|
||||
case lowerSorbian = "dsb"
|
||||
case lowerSorbianGermany = "dsb_DE"
|
||||
case lubaKatanga = "lu"
|
||||
case lubaKatangaCongoKinshasa = "lu_CD"
|
||||
case luo = "luo"
|
||||
case luoKenya = "luo_KE"
|
||||
case luxembourgish = "lb"
|
||||
case luxembourgishLuxembourg = "lb_LU"
|
||||
case luyia = "luy"
|
||||
case luyiaKenya = "luy_KE"
|
||||
case macedonian = "mk"
|
||||
case macedonianMacedonia = "mk_MK"
|
||||
case machame = "jmc"
|
||||
case machameTanzania = "jmc_TZ"
|
||||
case makhuwaMeetto = "mgh"
|
||||
case makhuwaMeettoMozambique = "mgh_MZ"
|
||||
case makonde = "kde"
|
||||
case makondeTanzania = "kde_TZ"
|
||||
case malagasy = "mg"
|
||||
case malagasyMadagascar = "mg_MG"
|
||||
case malay = "ms_Latn"
|
||||
case malayArabic = "ms_Arab"
|
||||
case malayArabicBrunei = "ms_Arab_BN"
|
||||
case malayArabicMalaysia = "ms_Arab_MY"
|
||||
case malayBrunei = "ms_Latn_BN"
|
||||
case malayMalaysia = "ms_Latn_MY"
|
||||
case malaySingapore = "ms_Latn_SG"
|
||||
case malayalam = "ml"
|
||||
case malayalamIndia = "ml_IN"
|
||||
case maltese = "mt"
|
||||
case malteseMalta = "mt_MT"
|
||||
case manx = "gv"
|
||||
case manxIsleOfMan = "gv_IM"
|
||||
case marathi = "mr"
|
||||
case marathiIndia = "mr_IN"
|
||||
case masai = "mas"
|
||||
case masaiKenya = "mas_KE"
|
||||
case masaiTanzania = "mas_TZ"
|
||||
case meru = "mer"
|
||||
case meruKenya = "mer_KE"
|
||||
case meta = "mgo"
|
||||
case metaCameroon = "mgo_CM"
|
||||
case mongolian = "mn_Cyrl"
|
||||
case mongolianMongolia = "mn_Cyrl_MN"
|
||||
case morisyen = "mfe"
|
||||
case morisyenMauritius = "mfe_MU"
|
||||
case mundang = "mua"
|
||||
case mundangCameroon = "mua_CM"
|
||||
case nama = "naq"
|
||||
case namaNamibia = "naq_NA"
|
||||
case nepali = "ne"
|
||||
case nepaliIndia = "ne_IN"
|
||||
case nepaliNepal = "ne_NP"
|
||||
case ngiemboon = "nnh"
|
||||
case ngiemboonCameroon = "nnh_CM"
|
||||
case ngomba = "jgo"
|
||||
case ngombaCameroon = "jgo_CM"
|
||||
case northNdebele = "nd"
|
||||
case northNdebeleZimbabwe = "nd_ZW"
|
||||
case northernSami = "se"
|
||||
case northernSamiFinland = "se_FI"
|
||||
case northernSamiNorway = "se_NO"
|
||||
case northernSamiSweden = "se_SE"
|
||||
case norwegianBokml = "nb"
|
||||
case norwegianBokmlNorway = "nb_NO"
|
||||
case norwegianBokmlSvalbardJanMayen = "nb_SJ"
|
||||
case norwegianNynorsk = "nn"
|
||||
case norwegianNynorskNorway = "nn_NO"
|
||||
case nuer = "nus"
|
||||
case nuerSudan = "nus_SD"
|
||||
case nyankole = "nyn"
|
||||
case nyankoleUganda = "nyn_UG"
|
||||
case oriya = "or"
|
||||
case oriyaIndia = "or_IN"
|
||||
case oromo = "om"
|
||||
case oromoEthiopia = "om_ET"
|
||||
case oromoKenya = "om_KE"
|
||||
case ossetic = "os"
|
||||
case osseticGeorgia = "os_GE"
|
||||
case osseticRussia = "os_RU"
|
||||
case pashto = "ps"
|
||||
case pashtoAfghanistan = "ps_AF"
|
||||
case persian = "fa"
|
||||
case persianAfghanistan = "fa_AF"
|
||||
case persianIran = "fa_IR"
|
||||
case polish = "pl"
|
||||
case polishPoland = "pl_PL"
|
||||
case portuguese = "pt"
|
||||
case portugueseAngola = "pt_AO"
|
||||
case portugueseBrazil = "pt_BR"
|
||||
case portugueseCapeVerde = "pt_CV"
|
||||
case portugueseGuineaBissau = "pt_GW"
|
||||
case portugueseMacauSarChina = "pt_MO"
|
||||
case portugueseMozambique = "pt_MZ"
|
||||
case portuguesePortugal = "pt_PT"
|
||||
case portugueseSoTomPrncipe = "pt_ST"
|
||||
case portugueseTimorLeste = "pt_TL"
|
||||
case punjabi = "pa_Guru"
|
||||
case punjabiArabic = "pa_Arab"
|
||||
case punjabiArabicPakistan = "pa_Arab_PK"
|
||||
case punjabiIndia = "pa_Guru_IN"
|
||||
case quechua = "qu"
|
||||
case quechuaBolivia = "qu_BO"
|
||||
case quechuaEcuador = "qu_EC"
|
||||
case quechuaPeru = "qu_PE"
|
||||
case romanian = "ro"
|
||||
case romanianMoldova = "ro_MD"
|
||||
case romanianRomania = "ro_RO"
|
||||
case romansh = "rm"
|
||||
case romanshSwitzerland = "rm_CH"
|
||||
case rombo = "rof"
|
||||
case romboTanzania = "rof_TZ"
|
||||
case rundi = "rn"
|
||||
case rundiBurundi = "rn_BI"
|
||||
case russian = "ru"
|
||||
case russianBelarus = "ru_BY"
|
||||
case russianKazakhstan = "ru_KZ"
|
||||
case russianKyrgyzstan = "ru_KG"
|
||||
case russianMoldova = "ru_MD"
|
||||
case russianRussia = "ru_RU"
|
||||
case russianUkraine = "ru_UA"
|
||||
case rwa = "rwk"
|
||||
case rwaTanzania = "rwk_TZ"
|
||||
case sakha = "sah"
|
||||
case sakhaRussia = "sah_RU"
|
||||
case samburu = "saq"
|
||||
case samburuKenya = "saq_KE"
|
||||
case sango = "sg"
|
||||
case sangoCentralAfricanRepublic = "sg_CF"
|
||||
case sangu = "sbp"
|
||||
case sanguTanzania = "sbp_TZ"
|
||||
case scottishGaelic = "gd"
|
||||
case scottishGaelicUnitedKingdom = "gd_GB"
|
||||
case sena = "seh"
|
||||
case senaMozambique = "seh_MZ"
|
||||
case serbian = "sr_Cyrl"
|
||||
case serbianBosniaHerzegovina = "sr_Cyrl_BA"
|
||||
case serbianKosovo = "sr_Cyrl_XK"
|
||||
case serbianLatin = "sr_Latn"
|
||||
case serbianLatinBosniaHerzegovina = "sr_Latn_BA"
|
||||
case serbianLatinKosovo = "sr_Latn_XK"
|
||||
case serbianLatinMontenegro = "sr_Latn_ME"
|
||||
case serbianLatinSerbia = "sr_Latn_RS"
|
||||
case serbianMontenegro = "sr_Cyrl_ME"
|
||||
case serbianSerbia = "sr_Cyrl_RS"
|
||||
case shambala = "ksb"
|
||||
case shambalaTanzania = "ksb_TZ"
|
||||
case shona = "sn"
|
||||
case shonaZimbabwe = "sn_ZW"
|
||||
case sichuanYi = "ii"
|
||||
case sichuanYiChina = "ii_CN"
|
||||
case sinhala = "si"
|
||||
case sinhalaSriLanka = "si_LK"
|
||||
case slovak = "sk"
|
||||
case slovakSlovakia = "sk_SK"
|
||||
case slovenian = "sl"
|
||||
case slovenianSlovenia = "sl_SI"
|
||||
case soga = "xog"
|
||||
case sogaUganda = "xog_UG"
|
||||
case somali = "so"
|
||||
case somaliDjibouti = "so_DJ"
|
||||
case somaliEthiopia = "so_ET"
|
||||
case somaliKenya = "so_KE"
|
||||
case somaliSomalia = "so_SO"
|
||||
case spanish = "es"
|
||||
case spanishArgentina = "es_AR"
|
||||
case spanishBolivia = "es_BO"
|
||||
case spanishCanaryIslands = "es_IC"
|
||||
case spanishCeutaMelilla = "es_EA"
|
||||
case spanishChile = "es_CL"
|
||||
case spanishColombia = "es_CO"
|
||||
case spanishCostaRica = "es_CR"
|
||||
case spanishCuba = "es_CU"
|
||||
case spanishDominicanRepublic = "es_DO"
|
||||
case spanishEcuador = "es_EC"
|
||||
case spanishElSalvador = "es_SV"
|
||||
case spanishEquatorialGuinea = "es_GQ"
|
||||
case spanishGuatemala = "es_GT"
|
||||
case spanishHonduras = "es_HN"
|
||||
case spanishLatinAmerica = "es_419"
|
||||
case spanishMexico = "es_MX"
|
||||
case spanishNicaragua = "es_NI"
|
||||
case spanishPanama = "es_PA"
|
||||
case spanishParaguay = "es_PY"
|
||||
case spanishPeru = "es_PE"
|
||||
case spanishPhilippines = "es_PH"
|
||||
case spanishPuertoRico = "es_PR"
|
||||
case spanishSpain = "es_ES"
|
||||
case spanishUnitedStates = "es_US"
|
||||
case spanishUruguay = "es_UY"
|
||||
case spanishVenezuela = "es_VE"
|
||||
case standardMoroccanTamazight = "zgh"
|
||||
case standardMoroccanTamazightMorocco = "zgh_MA"
|
||||
case swahili = "sw"
|
||||
case swahiliCongoKinshasa = "sw_CD"
|
||||
case swahiliKenya = "sw_KE"
|
||||
case swahiliTanzania = "sw_TZ"
|
||||
case swahiliUganda = "sw_UG"
|
||||
case swedish = "sv"
|
||||
case swedishlandIslands = "sv_AX"
|
||||
case swedishFinland = "sv_FI"
|
||||
case swedishSweden = "sv_SE"
|
||||
case swissGerman = "gsw"
|
||||
case swissGermanFrance = "gsw_FR"
|
||||
case swissGermanLiechtenstein = "gsw_LI"
|
||||
case swissGermanSwitzerland = "gsw_CH"
|
||||
case tachelhit = "shi_Latn"
|
||||
case tachelhitMorocco = "shi_Latn_MA"
|
||||
case tachelhitTifinagh = "shi_Tfng"
|
||||
case tachelhitTifinaghMorocco = "shi_Tfng_MA"
|
||||
case taita = "dav"
|
||||
case taitaKenya = "dav_KE"
|
||||
case tajik = "tg_Cyrl"
|
||||
case tajikTajikistan = "tg_Cyrl_TJ"
|
||||
case tamil = "ta"
|
||||
case tamilIndia = "ta_IN"
|
||||
case tamilMalaysia = "ta_MY"
|
||||
case tamilSingapore = "ta_SG"
|
||||
case tamilSriLanka = "ta_LK"
|
||||
case tasawaq = "twq"
|
||||
case tasawaqNiger = "twq_NE"
|
||||
case telugu = "te"
|
||||
case teluguIndia = "te_IN"
|
||||
case teso = "teo"
|
||||
case tesoKenya = "teo_KE"
|
||||
case tesoUganda = "teo_UG"
|
||||
case thai = "th"
|
||||
case thaiThailand = "th_TH"
|
||||
case tibetan = "bo"
|
||||
case tibetanChina = "bo_CN"
|
||||
case tibetanIndia = "bo_IN"
|
||||
case tigrinya = "ti"
|
||||
case tigrinyaEritrea = "ti_ER"
|
||||
case tigrinyaEthiopia = "ti_ET"
|
||||
case tongan = "to"
|
||||
case tonganTonga = "to_TO"
|
||||
case turkish = "tr"
|
||||
case turkishCyprus = "tr_CY"
|
||||
case turkishTurkey = "tr_TR"
|
||||
case turkmen = "tk_Latn"
|
||||
case turkmenTurkmenistan = "tk_Latn_TM"
|
||||
case ukrainian = "uk"
|
||||
case ukrainianUkraine = "uk_UA"
|
||||
case upperSorbian = "hsb"
|
||||
case upperSorbianGermany = "hsb_DE"
|
||||
case urdu = "ur"
|
||||
case urduIndia = "ur_IN"
|
||||
case urduPakistan = "ur_PK"
|
||||
case uyghur = "ug"
|
||||
case uyghurArabic = "ug_Arab"
|
||||
case uyghurArabicChina = "ug_Arab_CN"
|
||||
case uzbek = "uz_Cyrl"
|
||||
case uzbekArabic = "uz_Arab"
|
||||
case uzbekArabicAfghanistan = "uz_Arab_AF"
|
||||
case uzbekLatin = "uz_Latn"
|
||||
case uzbekLatinUzbekistan = "uz_Latn_UZ"
|
||||
case uzbekUzbekistan = "uz_Cyrl_UZ"
|
||||
case vai = "vai_Vaii"
|
||||
case vaiLatin = "vai_Latn"
|
||||
case vaiLatinLiberia = "vai_Latn_LR"
|
||||
case vaiLiberia = "vai_Vaii_LR"
|
||||
case vietnamese = "vi"
|
||||
case vietnameseVietnam = "vi_VN"
|
||||
case vunjo = "vun"
|
||||
case vunjoTanzania = "vun_TZ"
|
||||
case walser = "wae"
|
||||
case walserSwitzerland = "wae_CH"
|
||||
case welsh = "cy"
|
||||
case welshUnitedKingdom = "cy_GB"
|
||||
case westernFrisian = "fy"
|
||||
case westernFrisianNetherlands = "fy_NL"
|
||||
case yangben = "yav"
|
||||
case yangbenCameroon = "yav_CM"
|
||||
case yiddish = "yi"
|
||||
case yiddishWorld = "yi_001"
|
||||
case yoruba = "yo"
|
||||
case yorubaBenin = "yo_BJ"
|
||||
case yorubaNigeria = "yo_NG"
|
||||
case zarma = "dje"
|
||||
case zarmaNiger = "dje_NE"
|
||||
case zulu = "zu"
|
||||
case zuluSouthAfrica = "zu_ZA"
|
||||
|
||||
/// Return a valid `Locale` instance from currency locale enum
|
||||
public var locale: Locale {
|
||||
switch self {
|
||||
case .current: return Locale.current
|
||||
case .autoUpdating: return Locale.autoupdatingCurrent
|
||||
default: return Locale(identifier: rawValue)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
//
|
||||
// NumberFormatter.swift
|
||||
// CurrencyText
|
||||
//
|
||||
// Created by Felipe Lefèvre Marino on 12/27/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension NumberFormatter {
|
||||
|
||||
func string(from doubleValue: Double?) -> String? {
|
||||
if let doubleValue = doubleValue {
|
||||
return string(from: NSNumber(value: doubleValue))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
69
submodules/BotPaymentsUI/Sources/Formatter/String.swift
Normal file
69
submodules/BotPaymentsUI/Sources/Formatter/String.swift
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// String.swift
|
||||
// CurrencyText
|
||||
//
|
||||
// Created by Felipe Lefèvre Marino on 4/3/18.
|
||||
// Copyright © 2018 Felipe Lefèvre Marino. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol CurrencyString {
|
||||
var representsZero: Bool { get }
|
||||
var hasNumbers: Bool { get }
|
||||
var lastNumberOffsetFromEnd: Int? { get }
|
||||
func numeralFormat() -> String
|
||||
mutating func updateDecimalSeparator(decimalDigits: Int)
|
||||
}
|
||||
|
||||
//Currency String Extension
|
||||
extension String: CurrencyString {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// Informs with the string represents the value of zero
|
||||
public var representsZero: Bool {
|
||||
return numeralFormat().replacingOccurrences(of: "0", with: "").count == 0
|
||||
}
|
||||
|
||||
/// Returns if the string does have any character that represents numbers
|
||||
public var hasNumbers: Bool {
|
||||
return numeralFormat().count > 0
|
||||
}
|
||||
|
||||
/// The offset from end index to the index _right after_ the last number in the String.
|
||||
/// e.g. For the String "123some", the last number position is 4, because from the _end index_ to the index of _3_
|
||||
/// there is an offset of 4, "e, m, o and s".
|
||||
public var lastNumberOffsetFromEnd: Int? {
|
||||
guard let indexOfLastNumber = lastIndex(where: { $0.isNumber }) else { return nil }
|
||||
let indexAfterLastNumber = index(after: indexOfLastNumber)
|
||||
return distance(from: endIndex, to: indexAfterLastNumber)
|
||||
}
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
/// Updates a currency string decimal separator position based on
|
||||
/// the amount of decimal digits desired
|
||||
///
|
||||
/// - Parameter decimalDigits: The amount of decimal digits of the currency formatted string
|
||||
public mutating func updateDecimalSeparator(decimalDigits: Int) {
|
||||
guard decimalDigits != 0 && count >= decimalDigits else { return }
|
||||
let decimalsRange = index(endIndex, offsetBy: -decimalDigits)..<endIndex
|
||||
|
||||
let decimalChars = self[decimalsRange]
|
||||
replaceSubrange(decimalsRange, with: "." + decimalChars)
|
||||
}
|
||||
|
||||
/// The numeral format of a string - remove all non numerical ocurrences
|
||||
///
|
||||
/// - Returns: itself without the non numerical characters ocurrences
|
||||
public func numeralFormat() -> String {
|
||||
return replacingOccurrences(of:"[^0-9]", with: "", options: .regularExpression)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Static constants
|
||||
|
||||
extension String {
|
||||
public static let negativeSymbol = "-"
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
//
|
||||
// CurrencyUITextFieldDelegate.swift
|
||||
// CurrencyText
|
||||
//
|
||||
// Created by Felipe Lefèvre Marino on 12/26/18.
|
||||
// Copyright © 2018 Felipe Lefèvre Marino. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Custom text field delegate, that formats user inputs based on a given currency formatter.
|
||||
public class CurrencyUITextFieldDelegate: NSObject {
|
||||
|
||||
public var formatter: (CurrencyFormatting & CurrencyAdjusting)!
|
||||
|
||||
public var textUpdated: (() -> Void)?
|
||||
|
||||
/// Text field clears its text when value value is equal to zero.
|
||||
public var clearsWhenValueIsZero: Bool = false
|
||||
|
||||
/// A delegate object to receive and potentially handle `UITextFieldDelegate events` that are sent to `CurrencyUITextFieldDelegate`.
|
||||
///
|
||||
/// Note: Make sure the implementation of this object does not wrongly interfere with currency formatting.
|
||||
///
|
||||
/// By returning `false` on`textField(textField:shouldChangeCharactersIn:replacementString:)` no currency formatting is done.
|
||||
public var passthroughDelegate: UITextFieldDelegate? {
|
||||
get { return _passthroughDelegate }
|
||||
set {
|
||||
guard newValue !== self else { return }
|
||||
_passthroughDelegate = newValue
|
||||
}
|
||||
}
|
||||
weak private(set) var _passthroughDelegate: UITextFieldDelegate?
|
||||
|
||||
public init(formatter: CurrencyFormatter) {
|
||||
self.formatter = formatter
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITextFieldDelegate
|
||||
|
||||
extension CurrencyUITextFieldDelegate: UITextFieldDelegate {
|
||||
|
||||
@discardableResult
|
||||
open func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
||||
return passthroughDelegate?.textFieldShouldBeginEditing?(textField) ?? true
|
||||
}
|
||||
|
||||
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
textField.setInitialSelectedTextRange()
|
||||
passthroughDelegate?.textFieldDidBeginEditing?(textField)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
|
||||
if let text = textField.text, text.representsZero && clearsWhenValueIsZero {
|
||||
textField.text = ""
|
||||
}
|
||||
else if let text = textField.text, let updated = formatter.formattedStringAdjustedToFitAllowedValues(from: text), updated != text {
|
||||
textField.text = updated
|
||||
}
|
||||
return passthroughDelegate?.textFieldShouldEndEditing?(textField) ?? true
|
||||
}
|
||||
|
||||
open func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
passthroughDelegate?.textFieldDidEndEditing?(textField)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func textFieldShouldClear(_ textField: UITextField) -> Bool {
|
||||
return passthroughDelegate?.textFieldShouldClear?(textField) ?? true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
return passthroughDelegate?.textFieldShouldReturn?(textField) ?? true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
let shouldChangeCharactersInRange = passthroughDelegate?.textField?(textField,
|
||||
shouldChangeCharactersIn: range,
|
||||
replacementString: string) ?? true
|
||||
guard shouldChangeCharactersInRange else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Store selected text range offset from end, before updating and reformatting the currency string.
|
||||
let lastSelectedTextRangeOffsetFromEnd = textField.selectedTextRangeOffsetFromEnd
|
||||
|
||||
// Before leaving the scope, update selected text range,
|
||||
// respecting previous selected text range offset from end.
|
||||
defer {
|
||||
textField.updateSelectedTextRange(lastOffsetFromEnd: lastSelectedTextRangeOffsetFromEnd)
|
||||
textUpdated?()
|
||||
}
|
||||
|
||||
guard !string.isEmpty else {
|
||||
handleDeletion(in: textField, at: range)
|
||||
return false
|
||||
}
|
||||
guard string.hasNumbers else {
|
||||
addNegativeSymbolIfNeeded(in: textField, at: range, replacementString: string)
|
||||
return false
|
||||
}
|
||||
|
||||
setFormattedText(in: textField, inputString: string, range: range)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
extension CurrencyUITextFieldDelegate {
|
||||
|
||||
/// Verifies if user inputed a negative symbol at the first lowest
|
||||
/// bound of the text field and add it.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - textField: text field that user interacted with
|
||||
/// - range: user input range
|
||||
/// - string: user input string
|
||||
private func addNegativeSymbolIfNeeded(in textField: UITextField, at range: NSRange, replacementString string: String) {
|
||||
guard textField.keyboardType == .numbersAndPunctuation else { return }
|
||||
|
||||
if string == .negativeSymbol && textField.text?.isEmpty == true {
|
||||
textField.text = .negativeSymbol
|
||||
} else if range.lowerBound == 0 && string == .negativeSymbol &&
|
||||
textField.text?.contains(String.negativeSymbol) == false {
|
||||
|
||||
textField.text = .negativeSymbol + (textField.text ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
/// Correctly delete characters when user taps remove key.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - textField: text field that user interacted with
|
||||
/// - range: range to be removed
|
||||
private func handleDeletion(in textField: UITextField, at range: NSRange) {
|
||||
if var text = textField.text {
|
||||
if let textRange = Range(range, in: text) {
|
||||
text.removeSubrange(textRange)
|
||||
} else {
|
||||
text.removeLast()
|
||||
}
|
||||
|
||||
if text.isEmpty {
|
||||
textField.text = text
|
||||
} else {
|
||||
textField.text = formatter.formattedStringWithAdjustedDecimalSeparator(from: text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats text field's text with new input string and changed range
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - textField: text field that user interacted with
|
||||
/// - inputString: typed string
|
||||
/// - range: range where the string should be added
|
||||
private func setFormattedText(in textField: UITextField, inputString: String, range: NSRange) {
|
||||
var updatedText = ""
|
||||
|
||||
if let text = textField.text {
|
||||
if text.isEmpty {
|
||||
updatedText = formatter.initialText + inputString
|
||||
} else if let range = Range(range, in: text) {
|
||||
updatedText = text.replacingCharacters(in: range, with: inputString)
|
||||
} else {
|
||||
updatedText = text.appending(inputString)
|
||||
}
|
||||
}
|
||||
|
||||
if updatedText.numeralFormat().count > formatter.maxDigitsCount {
|
||||
updatedText.removeLast()
|
||||
}
|
||||
|
||||
textField.text = formatter.formattedStringWithAdjustedDecimalSeparator(from: updatedText)
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
//
|
||||
// UITextField.swift
|
||||
// CurrencyText
|
||||
//
|
||||
// Created by Felipe Lefèvre Marino on 12/26/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UITextField {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var selectedTextRangeOffsetFromEnd: Int {
|
||||
return offset(from: endOfDocument, to: selectedTextRange?.end ?? endOfDocument)
|
||||
}
|
||||
|
||||
/// Sets the selected text range when the text field is starting to be edited.
|
||||
/// _Should_ be called when text field start to be the first responder.
|
||||
func setInitialSelectedTextRange() {
|
||||
// update selected text range if needed
|
||||
adjustSelectedTextRange(lastOffsetFromEnd: 0) // at the end when first selected
|
||||
}
|
||||
|
||||
/// Interface to update the selected text range as expected.
|
||||
/// - Parameter lastOffsetFromEnd: The last stored selected text range offset from end. Used to keep it concise with pre-formatting.
|
||||
func updateSelectedTextRange(lastOffsetFromEnd: Int) {
|
||||
adjustSelectedTextRange(lastOffsetFromEnd: lastOffsetFromEnd)
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
/// Adjust the selected text range to match the best position.
|
||||
private func adjustSelectedTextRange(lastOffsetFromEnd: Int) {
|
||||
/// If text is empty the offset is set to zero, the selected text range does need to be changed.
|
||||
if let text = text, text.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
var offsetFromEnd = lastOffsetFromEnd
|
||||
|
||||
/// Adjust offset if needed. When the last number character offset from end is less than the current offset,
|
||||
/// or in other words, is more distant to the end of the string, the offset is readjusted to it,
|
||||
/// so the selected text range is correctly set to the last index with a number.
|
||||
if let lastNumberOffsetFromEnd = text?.lastNumberOffsetFromEnd,
|
||||
case let shouldOffsetBeAdjusted = lastNumberOffsetFromEnd < offsetFromEnd,
|
||||
shouldOffsetBeAdjusted {
|
||||
|
||||
offsetFromEnd = lastNumberOffsetFromEnd
|
||||
}
|
||||
|
||||
updateSelectedTextRange(offsetFromEnd: offsetFromEnd)
|
||||
}
|
||||
|
||||
/// Update the selected text range with given offset from end.
|
||||
private func updateSelectedTextRange(offsetFromEnd: Int) {
|
||||
if let updatedCursorPosition = position(from: endOfDocument, offset: offsetFromEnd) {
|
||||
selectedTextRange = textRange(from: updatedCursorPosition, to: updatedCursorPosition)
|
||||
}
|
||||
}
|
||||
}
|
@ -46,6 +46,55 @@ private func loadCurrencyFormatterEntries() -> [String: CurrencyFormatterEntry]
|
||||
|
||||
private let currencyFormatterEntries = loadCurrencyFormatterEntries()
|
||||
|
||||
public func setupCurrencyNumberFormatter(currency: String) -> NumberFormatter {
|
||||
guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
var result = ""
|
||||
if entry.symbolOnLeft {
|
||||
result.append("¤")
|
||||
if entry.spaceBetweenAmountAndSymbol {
|
||||
result.append(" ")
|
||||
}
|
||||
}
|
||||
|
||||
result.append("#")
|
||||
|
||||
result.append(entry.decimalSeparator)
|
||||
|
||||
for _ in 0 ..< entry.decimalDigits {
|
||||
result.append("#")
|
||||
}
|
||||
if entry.decimalDigits != 0 {
|
||||
result.append("0")
|
||||
}
|
||||
|
||||
if !entry.symbolOnLeft {
|
||||
if entry.spaceBetweenAmountAndSymbol {
|
||||
result.append(" ")
|
||||
}
|
||||
result.append("¤")
|
||||
}
|
||||
|
||||
let numberFormatter = NumberFormatter()
|
||||
|
||||
numberFormatter.numberStyle = .currency
|
||||
|
||||
numberFormatter.positiveFormat = result
|
||||
numberFormatter.negativeFormat = "-\(result)"
|
||||
|
||||
numberFormatter.currencySymbol = entry.symbol
|
||||
numberFormatter.currencyDecimalSeparator = entry.decimalSeparator
|
||||
numberFormatter.currencyGroupingSeparator = entry.thousandsSeparator
|
||||
|
||||
numberFormatter.minimumFractionDigits = entry.decimalDigits
|
||||
numberFormatter.maximumFractionDigits = entry.decimalDigits
|
||||
numberFormatter.minimumIntegerDigits = 1
|
||||
|
||||
return numberFormatter
|
||||
}
|
||||
|
||||
public func fractionalToCurrencyAmount(value: Double, currency: String) -> Int64? {
|
||||
guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
|
||||
return nil
|
||||
@ -54,7 +103,11 @@ public func fractionalToCurrencyAmount(value: Double, currency: String) -> Int64
|
||||
for _ in 0 ..< entry.decimalDigits {
|
||||
factor *= 10.0
|
||||
}
|
||||
return Int64(value * factor)
|
||||
if value > Double(Int64.max) / factor {
|
||||
return nil
|
||||
} else {
|
||||
return Int64(value * factor)
|
||||
}
|
||||
}
|
||||
|
||||
public func currencyToFractionalAmount(value: Int64, currency: String) -> Double? {
|
||||
|
Loading…
x
Reference in New Issue
Block a user