From c04a3ab3e72f075c211ca008b710d996ea182145 Mon Sep 17 00:00:00 2001 From: Sami El Achi Date: Tue, 25 Aug 2026 10:55:30 +0400 Subject: [PATCH] feat: add AED to the preferred currency picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the UAE dirham to Settings → General → Preferred Currency so AED-billed users see converted spend estimates instead of converting the USD figures by hand. Follows the established shape for adding a currency: three source/test touch points, no new exchange-rate logic. - Sources/CodexBar/PreferencesGeneralPane.swift — PreferredCurrencyOption.aed joins the picker with the label "AED (د.إ)". The 2025 CBUAE national dirham symbol has no Unicode code point on shipping Apple platforms yet (U+20C0 is SOM SIGN, U+20C1 is SAUDI RIYAL SIGN), so the label uses the traditional Arabic abbreviation to stay renderable in system fonts everywhere. - Sources/CodexBarCore/CurrencyExchange.swift — "AED" joins supportedCurrencies, which is what opens the requiresLiveRates gate; selecting it triggers the same daily open.er-api.com live fetch with a 24h cache used by every other non-USD option. Adds the hardcoded fallback "AED": 3.67 (USD peg), read only before the first successful fetch. - Tests/CodexBarTests/UsageFormatterTests.swift — assert requiresLiveRates("AED"), proving selection actually triggers the live-rate path rather than silently sitting on the fallback. Formatting needs no changes: UsageFormatter.currencyString pins Locale(identifier: "en_US"), where ICU renders two-decimal AED, so $10 becomes "AED 10.00", host-locale independent like CZK and KRW. --- Sources/CodexBar/PreferencesGeneralPane.swift | 2 ++ Sources/CodexBarCore/CurrencyExchange.swift | 3 ++- Tests/CodexBarTests/UsageFormatterTests.swift | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Sources/CodexBar/PreferencesGeneralPane.swift b/Sources/CodexBar/PreferencesGeneralPane.swift index b1d07600da..ce2af53b57 100644 --- a/Sources/CodexBar/PreferencesGeneralPane.swift +++ b/Sources/CodexBar/PreferencesGeneralPane.swift @@ -91,6 +91,7 @@ enum PreferredCurrencyOption: String, CaseIterable, Identifiable { case sgd = "SGD" case inr = "INR" case chf = "CHF" + case aed = "AED" var id: String { self.rawValue @@ -113,6 +114,7 @@ enum PreferredCurrencyOption: String, CaseIterable, Identifiable { case .sgd: "SGD ($)" case .inr: "INR (₹)" case .chf: "CHF (Fr.)" + case .aed: "AED (د.إ)" } } } diff --git a/Sources/CodexBarCore/CurrencyExchange.swift b/Sources/CodexBarCore/CurrencyExchange.swift index 1908c2f2c3..6594145a81 100644 --- a/Sources/CodexBarCore/CurrencyExchange.swift +++ b/Sources/CodexBarCore/CurrencyExchange.swift @@ -15,7 +15,7 @@ public final class CurrencyExchange: @unchecked Sendable { /// All currency codes supported by the converter. public static let supportedCurrencies: [String] = [ - "USD", "GBP", "EUR", "CZK", "CNY", "JPY", "KRW", "CAD", "AUD", "HKD", "TWD", "SGD", "INR", "CHF", + "USD", "GBP", "EUR", "CZK", "CNY", "JPY", "KRW", "CAD", "AUD", "HKD", "TWD", "SGD", "INR", "CHF", "AED", ] private let lock = NSLock() @@ -36,6 +36,7 @@ public final class CurrencyExchange: @unchecked Sendable { "SGD": 1.34, "INR": 84.50, "CHF": 0.80, + "AED": 3.67, ] private var lastFetchTime: Date? diff --git a/Tests/CodexBarTests/UsageFormatterTests.swift b/Tests/CodexBarTests/UsageFormatterTests.swift index a64529b175..0de431a77f 100644 --- a/Tests/CodexBarTests/UsageFormatterTests.swift +++ b/Tests/CodexBarTests/UsageFormatterTests.swift @@ -523,6 +523,12 @@ struct UsageFormatterTests { #expect(explicitCZK.contains("CZK")) #expect(explicitCZK.contains(".")) + let aedRate = exchange.rate(for: "AED") ?? 3.67 + #expect(abs((exchange.convert(usdAmount: 10.0, to: "AED") ?? 0) - 10.0 * aedRate) < epsilon) + let explicitAED = UsageFormatter.convertedCostString(10.0, preferredCurrency: "AED", providerCurrency: "USD") + #expect(explicitAED.contains("AED")) + #expect(explicitAED.contains(".")) + // CHF is supported: conversion through the USD pivot works both ways. let chfRate = exchange.rate(for: "CHF") ?? 0.80 #expect(abs((exchange.convert(usdAmount: 10.0, to: "CHF") ?? 0) - 10.0 * chfRate) < epsilon) @@ -550,6 +556,7 @@ struct UsageFormatterTests { #expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: " eur ")) #expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: "KRW")) #expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: "CZK")) + #expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: "AED")) } @Test