From 02e7d451fee096a0426e824857caa230063c0bed Mon Sep 17 00:00:00 2001 From: tikhop Date: Fri, 17 Feb 2023 23:46:52 +0700 Subject: [PATCH 1/8] feat: Create new instance of local formatter when changing currency and properly update input items --- .../Sources/UI/Payments/Amount/Model/BaseAmountModel.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift index 9d64c4377..713294cbb 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift +++ b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift @@ -162,12 +162,13 @@ class BaseAmountModel { func setupCurrencyCode(_ code: String) { guard let price = try? CurrencyExchanger.shared.rate(for: code) else { return } - localFormatter.currencyCode = code + localFormatter = NumberFormatter.fiatFormatter(currencyCode: code) localCurrencyCode = code - currentInputItem = currentInputItem.currencyCode == kDashCurrency ? .dash : .app + let newInputItem = AmountInputItem.custom(currencyName: localCurrencyCode, currencyCode: localCurrencyCode) + currentInputItem = currentInputItem.currencyCode == kDashCurrency ? .dash : newInputItem inputItems = [ - .custom(currencyName: localCurrencyCode, currencyCode: localCurrencyCode), + newInputItem, .dash, ] From 9604a37327da47989f0d24f95f7e2772e0634347 Mon Sep 17 00:00:00 2001 From: tikhop Date: Sun, 19 Feb 2023 00:24:27 +0700 Subject: [PATCH 2/8] feat: Set up number formatter with proper settings when validating input --- .../Payments/Amount/Validation/DWAmountInputValidator.m | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m b/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m index 0665ac482..ff9716d9a 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m +++ b/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m @@ -65,7 +65,6 @@ - (instancetype)initWithType:(DWAmountInputValidatorType)type locale:(nullable N } case DWAmountInputValidatorTypeLocalCurrency: { numberFormatter.maximumFractionDigits = 2; - break; } } @@ -107,6 +106,10 @@ - (nullable NSString *)validatedAmountStringFromNumberString:(NSString *)validNu NSNumberFormatter *nf = [numberFormatter copy]; nf.numberStyle = NSNumberFormatterNoStyle; + nf.roundingMode = NSNumberFormatterRoundDown; + nf.minimumIntegerDigits = 1; + nf.maximumFractionDigits = 0; + nf.maximumFractionDigits = numberFormatter.maximumFractionDigits; NSNumber *number = [nf numberFromString:validNumberString]; if (number == nil) { @@ -126,8 +129,8 @@ - (nullable NSString *)validatedAmountStringFromNumberString:(NSString *)validNu } NSString *fractionPart = [validNumberString substringFromIndex:separatorIndex + 1]; - if (fractionPart.length > numberFormatter.maximumFractionDigits || - (fractionPart.length == numberFormatter.maximumFractionDigits && fractionPart.integerValue == 0)) { + if (fractionPart.length > nf.maximumFractionDigits || + (fractionPart.length == nf.maximumFractionDigits && fractionPart.integerValue == 0)) { return nil; } From 7e174424a08974bd621e75cf4954319205ac9159 Mon Sep 17 00:00:00 2001 From: tikhop Date: Sun, 19 Feb 2023 00:25:47 +0700 Subject: [PATCH 3/8] feat: Set up number formatter with proper settings when swapping inputs --- .../Sources/UI/Payments/Amount/Model/AmountObject.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift b/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift index a487682f4..0171714a0 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift +++ b/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift @@ -119,8 +119,9 @@ extension AmountObject { if amountType == .supplementary { return self } let numberFormatter = localFormatter.copy() as! NumberFormatter - numberFormatter.numberStyle = .decimal - numberFormatter.minimumFractionDigits = localFormatter.minimumFractionDigits + numberFormatter.numberStyle = .none + numberFormatter.minimumIntegerDigits = 1 + numberFormatter.minimumFractionDigits = 0 numberFormatter.maximumFractionDigits = localFormatter.maximumFractionDigits guard let amountInternalRepresentation = numberFormatter.string(from: supplementaryAmount as NSDecimalNumber) else { From 52b987daddc9e7e16918b20e61a4f989fb1013e8 Mon Sep 17 00:00:00 2001 From: tikhop Date: Sun, 19 Feb 2023 00:33:36 +0700 Subject: [PATCH 4/8] refactor: Use 'isMain' property of 'AmountInputItem' instead of comparing item to 'kDashCurrency' constant --- .../Sources/UI/Payments/Amount/Model/BaseAmountModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift index 713294cbb..d8c17f30c 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift +++ b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift @@ -166,7 +166,7 @@ class BaseAmountModel { localCurrencyCode = code let newInputItem = AmountInputItem.custom(currencyName: localCurrencyCode, currencyCode: localCurrencyCode) - currentInputItem = currentInputItem.currencyCode == kDashCurrency ? .dash : newInputItem + currentInputItem = currentInputItem.isMain ? .dash : newInputItem inputItems = [ newInputItem, .dash, From 2f33f3c66efb8121955f41bf37e14e0e451608b2 Mon Sep 17 00:00:00 2001 From: tikhop Date: Sun, 19 Feb 2023 01:28:40 +0700 Subject: [PATCH 5/8] ci: Update tests for AmountObject --- DashWalletTests/AmountObjectTests.swift | 41 ++++++++++++++++++------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/DashWalletTests/AmountObjectTests.swift b/DashWalletTests/AmountObjectTests.swift index 7d80a7e8a..e85efc3ee 100644 --- a/DashWalletTests/AmountObjectTests.swift +++ b/DashWalletTests/AmountObjectTests.swift @@ -24,7 +24,7 @@ class MockRatesProvider: RatesProvider { var updateHandler: (([DSCurrencyPriceObject]) -> Void)? func startExchangeRateFetching() { - if let path = Bundle.main.path(forResource: "rates", ofType: "json") { + if let path = Bundle(for: Self.self).path(forResource: "rates", ofType: "json") { do { let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) let rates = try JSONDecoder().decode(BaseDataResponse.self, from: data).data.rates! @@ -53,6 +53,7 @@ final class AmountObjectTests: XCTestCase { private var currencyExchanger = CurrencyExchanger(dataProvider: MockRatesProvider()) override func setUpWithError() throws { + currencyExchanger.startExchangeRateFetching() // Put setup code here. This method is called before the invocation of each test method in the class. } @@ -108,6 +109,7 @@ final class AmountObjectTests: XCTestCase { "56%@00", "123%@45", "34%@70", + "3412323234%@70", ] let weirdLocaleIdentifiers: Set = ["fr_CH", "kea_CV", "pt_CV", "en_CV"] @@ -119,34 +121,51 @@ final class AmountObjectTests: XCTestCase { if locale.isNonArabicDigitsLocale { continue } guard let currencyCode = locale.currencyCode else { continue } + let localFormatter = NumberFormatter.fiatFormatter(currencyCode: currencyCode) for inputFormat in inputFormats { let input = String(format: inputFormat, locale.decimalSeparator!) let inputNumber = Decimal(string: input, locale: locale)! - let mainAmount = AmountObject(plainAmount: inputNumber.plainDashAmount, - fiatCurrencyCode: currencyCode, - localFormatter: NumberFormatter.fiatFormatter(currencyCode: currencyCode), - currencyExchanger: currencyExchanger) - XCTAssert(mainAmount.localAmount.dashAmount.plainAmount == mainAmount.plainAmount) + let amount = AmountObject(plainAmount: inputNumber.plainDashAmount, + fiatCurrencyCode: currencyCode, + localFormatter: localFormatter, + currencyExchanger: currencyExchanger) + + let numberFormatter = localFormatter.copy() as! NumberFormatter + numberFormatter.numberStyle = .none + numberFormatter.minimumFractionDigits = localFormatter.minimumFractionDigits + numberFormatter.maximumFractionDigits = localFormatter.maximumFractionDigits + let inputValue = numberFormatter.string(from: amount.supplementaryAmount as NSDecimalNumber)! + + XCTAssert(amount.localAmount.amountInternalRepresentation == inputValue) } } } - func testSwaping() { - let numbers: [String] = ["0", "2", "300", "0.1", "0.09", "1.0", "1.0003", "10.79", "0.00054321"] + func testSwapingFromMainToLocal() { + let numbers = ["1234.43"] let currencyCode = "USD" - for item in numbers { + for (i, item) in numbers.enumerated() { + let localFormatter = NumberFormatter.fiatFormatter(currencyCode: currencyCode) + let amount = AmountObject(dashAmountString: item, fiatCurrencyCode: currencyCode, - localFormatter: NumberFormatter.fiatFormatter(currencyCode: currencyCode), + localFormatter: localFormatter, currencyExchanger: currencyExchanger) XCTAssert(amount.plainAmount == Decimal(string: item)!.plainDashAmount) let localAmount = amount.localAmount XCTAssert(amount.plainAmount == localAmount.plainAmount) - XCTAssert(amount.supplementaryFormatted == localAmount.supplementaryFormatted) + + let numberFormatter = localFormatter.copy() as! NumberFormatter + numberFormatter.numberStyle = .none + numberFormatter.minimumFractionDigits = localFormatter.minimumFractionDigits + numberFormatter.maximumFractionDigits = localFormatter.maximumFractionDigits + let input = numberFormatter.string(from: amount.supplementaryAmount as NSDecimalNumber)! + + XCTAssert(localAmount.amountInternalRepresentation == input) } } } From 5685d8f77f21278d5052e40543510180bc497d39 Mon Sep 17 00:00:00 2001 From: tikhop Date: Sun, 19 Feb 2023 14:12:17 +0700 Subject: [PATCH 6/8] feat: Don't assert if currency rate isn't available at the moment, instead show 'Updating Price' message --- DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift | 2 +- .../Sources/UI/Payments/Amount/Model/BaseAmountModel.swift | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift b/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift index 0171714a0..b642f0cc8 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift +++ b/DashWallet/Sources/UI/Payments/Amount/Model/AmountObject.swift @@ -93,7 +93,7 @@ struct AmountObject { mainFormatted = str } else { plainAmount = 0 - mainFormatted = "Error" + mainFormatted = NSLocalizedString("Updating Price", comment: "Updating Price") } } diff --git a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift index d8c17f30c..bdd4cc412 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift +++ b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift @@ -304,7 +304,6 @@ extension BaseAmountModel { } func amountInputControlDidSwapInputs() { - assert(isSwapToLocalCurrencyAllowed, "Switching until price is not fetched is not allowed") assert(inputItems.count == 2, "Swap only if we have two input types") let inputItem = inputItems[0] == currentInputItem ? inputItems[1] : inputItems[0] From ccd8b9cb4d50c8497d229e1f6a1634ee35932f71 Mon Sep 17 00:00:00 2001 From: tikhop Date: Sun, 19 Feb 2023 14:14:24 +0700 Subject: [PATCH 7/8] fix: Pasting amount with group decimal didn't format properly --- .../Amount/Model/BaseAmountModel.swift | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift index bdd4cc412..3eac55235 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift +++ b/DashWallet/Sources/UI/Payments/Amount/Model/BaseAmountModel.swift @@ -311,31 +311,23 @@ extension BaseAmountModel { } func pasteFromClipboard() { - guard var string = UIPasteboard.general.string else { return } - string = string.localizedAmount() + guard let string = UIPasteboard.general.string else { return } - guard let decimal = Decimal(string: string, locale: .current) else { return } - let decimalNumber = NSDecimalNumber(decimal: decimal) + let originalFormatter = currentInputItem.isMain + ? NumberFormatter.dashFormatter + : localFormatter + let formatter = originalFormatter.copy() as! NumberFormatter + formatter.numberStyle = .decimal + formatter.minimumFractionDigits = 0 + formatter.maximumFractionDigits = originalFormatter.maximumFractionDigits - let formattedString: String? + guard let number = formatter.number(from: string) else { return } - var formatter: NumberFormatter + formatter.numberStyle = .none + formatter.minimumFractionDigits = 0 + formatter.maximumFractionDigits = originalFormatter.maximumFractionDigits - if activeAmountType == .main { - formatter = NumberFormatter.dashFormatter.copy() as! NumberFormatter - formatter.numberStyle = .decimal - formatter.minimumFractionDigits = NumberFormatter.dashFormatter.minimumFractionDigits - formatter.maximumFractionDigits = NumberFormatter.dashFormatter.maximumFractionDigits - formattedString = formatter.string(from: decimalNumber) - } else { - formatter = localFormatter.copy() as! NumberFormatter - formatter.numberStyle = .decimal - formatter.minimumFractionDigits = localFormatter.minimumFractionDigits - formatter.maximumFractionDigits = localFormatter.maximumFractionDigits - formattedString = formatter.string(from: decimalNumber) - } - - guard let string = formattedString else { return } + guard let string = formatter.string(from: number) else { return } updateAmountObjects(with: string) } From 87a1eeb93e6b6decb206b60d88dd015a7ec6bbab Mon Sep 17 00:00:00 2001 From: tikhop Date: Sun, 19 Feb 2023 14:38:12 +0700 Subject: [PATCH 8/8] fix: Set 'minimumFractionDigits' instead of 'maximumFractionDigits' to zero --- .../UI/Payments/Amount/Validation/DWAmountInputValidator.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m b/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m index ff9716d9a..e63c58b4a 100644 --- a/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m +++ b/DashWallet/Sources/UI/Payments/Amount/Validation/DWAmountInputValidator.m @@ -108,7 +108,7 @@ - (nullable NSString *)validatedAmountStringFromNumberString:(NSString *)validNu nf.numberStyle = NSNumberFormatterNoStyle; nf.roundingMode = NSNumberFormatterRoundDown; nf.minimumIntegerDigits = 1; - nf.maximumFractionDigits = 0; + nf.minimumFractionDigits = 0; nf.maximumFractionDigits = numberFormatter.maximumFractionDigits; NSNumber *number = [nf numberFromString:validNumberString];