Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
### Performance
- OpenCodex: parse only newly appended usage-log entries instead of rebuilding the entire cache on every refresh, cutting memory use while preserving compatibility with older app versions (#3140). Thanks @olddonkey!

### Usage & Spend
- Added AED (UAE dirham) to the display currency options (#3186). Thanks @samiashi!

### Localization
- Turkish: improve translations throughout settings and provider views, including the previously untranslated iCloud sync section (#3178). Thanks @husodrn46!

Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBar/PreferencesGeneralPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -113,6 +114,7 @@ enum PreferredCurrencyOption: String, CaseIterable, Identifiable {
case .sgd: "SGD ($)"
case .inr: "INR (₹)"
case .chf: "CHF (Fr.)"
case .aed: "AED (د.إ)"
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/CodexBarCore/CurrencyExchange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -36,6 +36,7 @@ public final class CurrencyExchange: @unchecked Sendable {
"SGD": 1.34,
"INR": 84.50,
"CHF": 0.80,
"AED": 3.6725,
]
private var lastFetchTime: Date?

Expand Down
14 changes: 14 additions & 0 deletions Tests/CodexBarTests/SettingsStoreCoverageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,20 @@ struct SettingsStoreCoverageTests {
fresh.preferredCurrencyCode = "GBP"
let reloaded = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)
#expect(reloaded.preferredCurrencyCode == "GBP")

reloaded.preferredCurrencyCode = "AED"
let reloadedAED = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)
#expect(reloadedAED.preferredCurrencyCode == "AED")
}

@Test
func `preferred currency picker matches every supported exchange currency`() {
let pickerCurrencies = PreferredCurrencyOption.allCases
.filter { $0 != .auto }
.map(\.rawValue)

#expect(pickerCurrencies == CurrencyExchange.supportedCurrencies)
#expect(PreferredCurrencyOption.aed.label == "AED (د.إ)")
}

private static func makeSettingsStore(
Expand Down
14 changes: 13 additions & 1 deletion Tests/CodexBarTests/UsageFormatterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ struct UsageFormatterTests {
}

@Test
func `currency exchange converts rates and formats correctly`() {
func `currency exchange converts rates and formats correctly`() throws {
let exchange = CurrencyExchange.shared
let epsilon = 1e-9
// USD → USD is identity
Expand Down Expand Up @@ -523,6 +523,16 @@ struct UsageFormatterTests {
#expect(explicitCZK.contains("CZK"))
#expect(explicitCZK.contains("."))

let aedRate = try #require(exchange.rate(for: "AED"))
#expect(abs((exchange.convert(usdAmount: 10.0, to: "AED") ?? 0) - 10.0 * aedRate) < epsilon)
#expect(abs((exchange.convert(amount: 10.0, from: "AED", to: "USD") ?? 0) - 10.0 / aedRate) < epsilon)
#expect(abs((exchange.convert(amount: 10.0, from: "GBP", to: "AED") ?? 0)
- 10.0 / gbpRate * aedRate) < epsilon)
let explicitAED = UsageFormatter.convertedCostString(10.0, preferredCurrency: "AED", providerCurrency: "USD")
#expect(explicitAED == UsageFormatter.currencyString(10.0 * aedRate, currencyCode: "AED"))
#expect(explicitAED.hasPrefix("AED"))
#expect(explicitAED.range(of: #"\.\d{2}$"#, options: .regularExpression) != nil)

// 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)
Expand Down Expand Up @@ -550,6 +560,8 @@ struct UsageFormatterTests {
#expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: " eur "))
#expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: "KRW"))
#expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: "CZK"))
#expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: "AED"))
#expect(CurrencyExchange.requiresLiveRates(preferredCurrencyCode: " aed "))
}

@Test
Expand Down