Base fixes and test harness
This commit is contained in:
@@ -46,6 +46,19 @@ private func createWidgetContainer() -> NSPersistentContainer? {
|
||||
return container
|
||||
}
|
||||
|
||||
private func fetchCurrencyCode(from container: NSPersistentContainer?) -> String {
|
||||
guard let container = container else { return "EUR" }
|
||||
let context = container.viewContext
|
||||
let request = NSFetchRequest<NSManagedObject>(entityName: "AppSettings")
|
||||
request.fetchLimit = 1
|
||||
if let settings = try? context.fetch(request).first,
|
||||
let code = settings.value(forKey: "currency") as? String,
|
||||
!code.isEmpty {
|
||||
return code
|
||||
}
|
||||
return "EUR"
|
||||
}
|
||||
|
||||
// MARK: - Widget Entry
|
||||
|
||||
struct InvestmentWidgetEntry: TimelineEntry {
|
||||
@@ -60,6 +73,7 @@ struct InvestmentWidgetEntry: TimelineEntry {
|
||||
let categoryEvolution: [CategorySeries]
|
||||
let categoryTotals: [(name: String, value: Decimal, color: String)]
|
||||
let goals: [GoalSummary]
|
||||
let currencyCode: String
|
||||
}
|
||||
|
||||
struct CategorySeries: Identifiable {
|
||||
@@ -124,7 +138,8 @@ struct InvestmentWidgetProvider: TimelineProvider {
|
||||
],
|
||||
goals: [
|
||||
GoalSummary(name: "Target", targetAmount: 75000, targetDate: nil)
|
||||
]
|
||||
],
|
||||
currencyCode: "EUR"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -145,8 +160,10 @@ struct InvestmentWidgetProvider: TimelineProvider {
|
||||
|
||||
private func fetchData() -> InvestmentWidgetEntry {
|
||||
let isPremium = UserDefaults(suiteName: appGroupIdentifier)?.bool(forKey: sharedPremiumKey) ?? false
|
||||
let container = createWidgetContainer()
|
||||
let currencyCode = fetchCurrencyCode(from: container)
|
||||
|
||||
guard let container = createWidgetContainer() else {
|
||||
guard let container = container else {
|
||||
return InvestmentWidgetEntry(
|
||||
date: Date(),
|
||||
isPremium: isPremium,
|
||||
@@ -158,7 +175,8 @@ struct InvestmentWidgetProvider: TimelineProvider {
|
||||
trendLabels: [],
|
||||
categoryEvolution: [],
|
||||
categoryTotals: [],
|
||||
goals: []
|
||||
goals: [],
|
||||
currencyCode: currencyCode
|
||||
)
|
||||
}
|
||||
|
||||
@@ -344,7 +362,8 @@ struct InvestmentWidgetProvider: TimelineProvider {
|
||||
trendLabels: trendLabels,
|
||||
categoryEvolution: categoryEvolution,
|
||||
categoryTotals: categoryTotalsData.map { (name: $0.name, value: $0.value, color: $0.color) },
|
||||
goals: goals
|
||||
goals: goals,
|
||||
currencyCode: currencyCode
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -360,7 +379,7 @@ struct SmallWidgetView: View {
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(entry.totalValue.compactCurrencyString)
|
||||
Text(entry.totalValue.compactCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.title2.weight(.bold))
|
||||
.minimumScaleFactor(0.7)
|
||||
.lineLimit(1)
|
||||
@@ -369,7 +388,7 @@ struct SmallWidgetView: View {
|
||||
Image(systemName: entry.dayChange >= 0 ? "arrow.up.right" : "arrow.down.right")
|
||||
.font(.caption2)
|
||||
|
||||
Text(entry.dayChange.compactCurrencyString)
|
||||
Text(entry.dayChange.compactCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.caption.weight(.medium))
|
||||
|
||||
Text(String(format: "%.1f%% since last", entry.dayChangePercentage))
|
||||
@@ -398,7 +417,7 @@ struct MediumWidgetView: View {
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(entry.totalValue.compactCurrencyString)
|
||||
Text(entry.totalValue.compactCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.title.weight(.bold))
|
||||
.minimumScaleFactor(0.7)
|
||||
.lineLimit(1)
|
||||
@@ -407,7 +426,7 @@ struct MediumWidgetView: View {
|
||||
Image(systemName: entry.dayChange >= 0 ? "arrow.up.right" : "arrow.down.right")
|
||||
.font(.caption2)
|
||||
|
||||
Text(entry.dayChange.compactCurrencyString)
|
||||
Text(entry.dayChange.compactCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.caption.weight(.medium))
|
||||
|
||||
Text("(\(String(format: "%.1f%%", entry.dayChangePercentage)))")
|
||||
@@ -425,7 +444,8 @@ struct MediumWidgetView: View {
|
||||
TrendLineChartView(
|
||||
points: entry.trendPoints,
|
||||
labels: entry.trendLabels,
|
||||
goal: entry.goals.first
|
||||
goal: entry.goals.first,
|
||||
currencyCode: entry.currencyCode
|
||||
)
|
||||
.frame(height: 70)
|
||||
} else {
|
||||
@@ -462,7 +482,7 @@ struct MediumWidgetView: View {
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(source.value.shortCurrencyString)
|
||||
Text(source.value.shortCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.caption.weight(.medium))
|
||||
}
|
||||
}
|
||||
@@ -490,7 +510,7 @@ struct LargeWidgetView: View {
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(entry.totalValue.compactCurrencyString)
|
||||
Text(entry.totalValue.compactCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.title2.weight(.bold))
|
||||
.minimumScaleFactor(0.7)
|
||||
.lineLimit(1)
|
||||
@@ -499,7 +519,7 @@ struct LargeWidgetView: View {
|
||||
Image(systemName: entry.dayChange >= 0 ? "arrow.up.right" : "arrow.down.right")
|
||||
.font(.caption2)
|
||||
|
||||
Text(entry.dayChange.compactCurrencyString)
|
||||
Text(entry.dayChange.compactCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.caption.weight(.medium))
|
||||
|
||||
Text("(\(String(format: "%.1f%%", entry.dayChangePercentage)))")
|
||||
@@ -521,7 +541,8 @@ struct LargeWidgetView: View {
|
||||
CombinedCategoryChartView(
|
||||
series: entry.categoryEvolution,
|
||||
labels: entry.trendLabels,
|
||||
goal: entry.goals.first
|
||||
goal: entry.goals.first,
|
||||
currencyCode: entry.currencyCode
|
||||
)
|
||||
.frame(height: 98)
|
||||
|
||||
@@ -538,7 +559,7 @@ struct LargeWidgetView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(category.value.shortCurrencyString)
|
||||
Text(category.value.shortCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.caption.weight(.medium))
|
||||
}
|
||||
}
|
||||
@@ -601,7 +622,7 @@ struct AccessoryRectangularView: View {
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(entry.totalValue.compactCurrencyString)
|
||||
Text(entry.totalValue.compactCurrencyString(currencyCode: entry.currencyCode))
|
||||
.font(.headline)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
@@ -623,6 +644,7 @@ struct TrendLineChartView: View {
|
||||
let points: [Decimal]
|
||||
let labels: [String]
|
||||
let goal: GoalSummary?
|
||||
var currencyCode: String = "EUR"
|
||||
|
||||
private var values: [Double] {
|
||||
points.map { NSDecimalNumber(decimal: $0).doubleValue }
|
||||
@@ -646,11 +668,11 @@ struct TrendLineChartView: View {
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(Decimal(maxValue).shortCurrencyString)
|
||||
Text(Decimal(maxValue).shortCurrencyString(currencyCode: currencyCode))
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
Text(Decimal(minValue).shortCurrencyString)
|
||||
Text(Decimal(minValue).shortCurrencyString(currencyCode: currencyCode))
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@@ -715,6 +737,7 @@ struct CombinedCategoryChartView: View {
|
||||
let series: [CategorySeries]
|
||||
let labels: [String]
|
||||
let goal: GoalSummary?
|
||||
var currencyCode: String = "EUR"
|
||||
|
||||
private var pointsCount: Int {
|
||||
series.first?.points.count ?? 0
|
||||
@@ -742,11 +765,11 @@ struct CombinedCategoryChartView: View {
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(Decimal(maxValue).shortCurrencyString)
|
||||
Text(Decimal(maxValue).shortCurrencyString(currencyCode: currencyCode))
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
Text(Decimal(0).shortCurrencyString)
|
||||
Text(Decimal(0).shortCurrencyString(currencyCode: currencyCode))
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@@ -889,7 +912,8 @@ struct PortfolioJournalWidgetBundle: WidgetBundle {
|
||||
trendLabels: ["Aug", "Sep", "Oct", "Nov", "Dec", "Jan"],
|
||||
categoryEvolution: [],
|
||||
categoryTotals: [],
|
||||
goals: []
|
||||
goals: [],
|
||||
currencyCode: "EUR"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -911,7 +935,8 @@ struct PortfolioJournalWidgetBundle: WidgetBundle {
|
||||
trendLabels: ["Aug", "Sep", "Oct", "Nov", "Dec", "Jan"],
|
||||
categoryEvolution: [],
|
||||
categoryTotals: [],
|
||||
goals: []
|
||||
goals: [],
|
||||
currencyCode: "EUR"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -961,11 +986,12 @@ struct PortfolioJournalWidgetBundle: WidgetBundle {
|
||||
],
|
||||
goals: [
|
||||
GoalSummary(name: "Target", targetAmount: 120000, targetDate: nil)
|
||||
]
|
||||
],
|
||||
currencyCode: "EUR"
|
||||
)
|
||||
}
|
||||
extension Decimal {
|
||||
var compactCurrencyString: String {
|
||||
func compactCurrencyString(currencyCode: String) -> String {
|
||||
let absValue = (self as NSDecimalNumber).doubleValue.magnitude
|
||||
let sign = (self as NSDecimalNumber).doubleValue < 0 ? -1.0 : 1.0
|
||||
|
||||
@@ -986,14 +1012,13 @@ extension Decimal {
|
||||
let value = (self as NSDecimalNumber).doubleValue / divisor
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .currency
|
||||
formatter.currencyCode = currencyCode
|
||||
formatter.maximumFractionDigits = value < 10 && suffix != "" ? 1 : 0
|
||||
formatter.minimumFractionDigits = 0
|
||||
|
||||
// Use current locale currency symbol
|
||||
let currencySymbol = formatter.currencySymbol ?? "€"
|
||||
let formattedNumber: String
|
||||
do {
|
||||
// Use a plain decimal formatter to better control digits
|
||||
let nf = NumberFormatter()
|
||||
nf.numberStyle = .decimal
|
||||
nf.maximumFractionDigits = formatter.maximumFractionDigits
|
||||
@@ -1004,8 +1029,16 @@ extension Decimal {
|
||||
return "\(currencySymbol)\(formattedNumber)\(suffix)"
|
||||
}
|
||||
|
||||
func shortCurrencyString(currencyCode: String) -> String {
|
||||
return compactCurrencyString(currencyCode: currencyCode)
|
||||
}
|
||||
|
||||
var compactCurrencyString: String {
|
||||
return compactCurrencyString(currencyCode: "EUR")
|
||||
}
|
||||
|
||||
var shortCurrencyString: String {
|
||||
return compactCurrencyString
|
||||
return shortCurrencyString(currencyCode: "EUR")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user