fix(tips): abbreviate tip presets that run past three digits - #704
Merged
Conversation
Four chips share a row in the Send a Tip sheet, and a preset is a converted USD tier, so in a small-unit currency every tier is four digits or more: ARS renders $1,400 / $35,000 / $1,400,000, which `Text` then shrinks to half size to fit. `FiatAmount.formattedAbbreviated(minimumFractionDigits:)` keeps the figure to three digits, scaling past 999 with a K/M/B suffix and keeping a decimal only while the scaled figure is a single digit: $1.4K, $35K, $1.4M. It routes through the same `NumberFormatter.fiat` path as `formatted`, so the symbol, grouping, and negative form (-$1.5K) are unchanged. `CompactCurrencyFormatStyle` — the market-cap style in Currency Discovery — was a second abbreviation rule built on ICU's `.compactName`. It is now a `FormatStyle` entry point onto this one. That agreed on every case its tests covered except an exact half: $10.5M reads $11M rather than $10M, because display rounding here is half-up like everywhere else in the app.
Both apps now abbreviate the tip presets, but not to the same string: 12,345 pesos read $12.3K on Android and $12K here, because this rule kept a decimal only while the scaled figure was a single digit instead of spending the whole three-digit budget. Android's is the rule that answers "three digits", so this takes it. formattedAbbreviated(maxDigits:) now mirrors Fiat.abbreviated(maxDigits:) step for step — round to maxDigits significant digits, pick the largest scale the result clears, spend what is left on decimals. That adds the T scale, where 1e12 used to print $1,000B, and carries 999.99 up to $1K rather than showing five digits. formattedDroppingZeroFraction() is Android's FormattingRule.Truncated: it formats the amounts under the first scale and replaces the sheet's local customTitle helper. The custom chip abbreviates too, as Android's shared slot does, and both chips hand VoiceOver the unabbreviated amount the way Android's content description does. Market caps move with the shared rule, since Currency Discovery formats through it: $1,029,331 reads $1.03M rather than $1M.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four chips share a row in the Send a Tip sheet, and a preset is a converted USD tier, so in a small-unit currency every tier is four digits or more. In ARS the three presets render
$1,400/$35,000/$1,400,000, andText'sminimumScaleFactor(0.5)shrinks them to half size to fit the chip.FiatAmount.formattedAbbreviated(maxDigits:)caps the figure at three digits. Under the first scale it is plainformatted(...); past that the value is scaled and suffixedK/M/B/T, keeping only the decimals the cap leaves room for.$1,400$35,000$1,400,000$1.4K$35K$1.4M$97,500$97.5K₫650,000₫650K400,000400K$1$5$25It formats through the same
NumberFormatter.fiatpath asformatted, so the symbol, grouping, and the negative form (-$1.5K) come from one place.The same rule as Android
code-payments/code-android-app#1381 does this on Android, and the first version here did not produce the same strings: it kept a decimal only while the scaled figure was a single digit, so it spent two digits of a three-digit budget —
$1.2Kagainst$1.23K,$12Kagainst$12.3K,26Kagainst25.5Kfor an ARS preset. Android's rule is the one that answers "three digits", so this takes it.The implementation now mirrors
Fiat.abbreviated(maxDigits:)step for step: round tomaxDigitssignificant digits, pick the largest scale the rounded value clears, spend what is left on decimals. Rounding first is what makes 999,999 print$1Minstead of$1,000K, and it is half-up on both sides. Two behaviours came along with it — theTscale, where1e12used to print$1,000B, and999.99, which now carries up to$1Krather than printing five digits.formattedDroppingZeroFraction()is Android'sFormattingRule.Truncated: the fraction is dropped when the amount is whole and kept when it isn't. It formats the amounts under the first scale, and replaces the localcustomTitlehelper the sheet had for the same job.Two more things Android does that this now does:
One abbreviation rule, not two
CompactCurrencyFormatStyle— the market-cap and weekly-delta style in Currency Discovery — was a second abbreviation rule, built on ICU's.compactNamewith its own symbol and sign handling. It is now a two-lineFormatStyleentry point ontoformattedAbbreviated, for theDoublefigures SwiftUI formats inline.Market caps therefore move with the shared rule, and being capped at three digits rather than two they gain a digit:
$1,029,331reads$1.03Mrather than$1M,-12,400reads-$12.4K.$10.5Mno longer reads$10M— ICU compact notation rounds half-even, display rounding in this app is half-up.That the row follows the tip chips is the intent, not a side effect. It is not width-constrained the way a chip is, and getting ICU's shape back would take a second digit budget that caps decimals without capping whole digits —
maxDigits: 2is not it, since it rounds$211Kdown to$210K. That is a third rule to keep in step with Android for the sake of one row.The style's
Int(value)truncation is kept, so a market cap still reads$200rather than$200.17.Two divergences this does not close
Both are older than either PR and need an Android-side call, so they are worth naming rather than papering over:
$; Android's does not, so the same peso preset reads$7.5Khere and7.5Kthere. IDR (bare) and VND (₫) agree.Fiat.abbreviatedfor the tip chips and the uncappedNumber.abbreviatedforTokenMetricsRow, which always prints two decimals. A $690,272 cap reads$690Khere and$690.27Kthere.The parameterized cases in
FiatAmountAbbreviatedTestsfollow Android'sFiatTest— the scale boundaries, the decimal cap, the carry into the next scale, and the ARS/COP/VND/IDR/JPY rows — plus the negatives and the lower-cap case.