Skip to content

Commit

Permalink
Merge pull request #364 from moneymanagerex/rate
Browse files Browse the repository at this point in the history
Currency: supprt sync rate per currency with limited base currency
  • Loading branch information
guanlisheng authored Feb 25, 2025
2 parents c5d6d60 + c768e4a commit 2e0c57d
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 0 deletions.
4 changes: 4 additions & 0 deletions MMEX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
A3363EEB2C93BF62004696C7 /* CurrencyRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EEA2C93BF62004696C7 /* CurrencyRepository.swift */; };
A33742882CA8E55400698466 /* IncomeExpenseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742872CA8E55400698466 /* IncomeExpenseView.swift */; };
A337428A2CA8E72C00698466 /* InsightsSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742892CA8E72C00698466 /* InsightsSummary.swift */; };
A33BA2AB2D6D8EE00075CE54 /* CurrencyRateFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33BA2AA2D6D8EE00075CE54 /* CurrencyRateFetcher.swift */; };
A3462F642C9426F500F79145 /* InsightsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F632C9426F500F79145 /* InsightsViewModel.swift */; };
A3462F662C94854800F79145 /* ExportableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F652C94854800F79145 /* ExportableEntity.swift */; };
A3462F6A2C948CDB00F79145 /* ExportableEntityDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F692C948CDB00F79145 /* ExportableEntityDocument.swift */; };
Expand Down Expand Up @@ -418,6 +419,7 @@
A3363EEA2C93BF62004696C7 /* CurrencyRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyRepository.swift; sourceTree = "<group>"; };
A33742872CA8E55400698466 /* IncomeExpenseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomeExpenseView.swift; sourceTree = "<group>"; };
A33742892CA8E72C00698466 /* InsightsSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsSummary.swift; sourceTree = "<group>"; };
A33BA2AA2D6D8EE00075CE54 /* CurrencyRateFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyRateFetcher.swift; sourceTree = "<group>"; };
A3462F632C9426F500F79145 /* InsightsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsViewModel.swift; sourceTree = "<group>"; };
A3462F652C94854800F79145 /* ExportableEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableEntity.swift; sourceTree = "<group>"; };
A3462F692C948CDB00F79145 /* ExportableEntityDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableEntityDocument.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -768,6 +770,7 @@
children = (
A3C142A72C906E0A00D3CEC0 /* Keyboard.swift */,
92F6862C2CF27C6B00171A6A /* BadgeCount.swift */,
A33BA2AA2D6D8EE00075CE54 /* CurrencyRateFetcher.swift */,
);
path = Utility;
sourceTree = "<group>";
Expand Down Expand Up @@ -1317,6 +1320,7 @@
A3C142A22C90267F00D3CEC0 /* CategoryRepository.swift in Sources */,
A37E7D862C9AC2D000B4ECFC /* InfoAboutView.swift in Sources */,
9208240C2CA4C31B00388AB2 /* BudgetData.swift in Sources */,
A33BA2AB2D6D8EE00075CE54 /* CurrencyRateFetcher.swift in Sources */,
924353472CCD353A0052E4BC /* ListProtocol.swift in Sources */,
A3C142A02C9025D600D3CEC0 /* CategoryData.swift in Sources */,
92F6862A2CF27AE800171A6A /* EnterPreference.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions MMEX/Data/CurrencyData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,13 @@ struct CurrencyInfo {
let name : String
let baseConvRate : Double
let formatter : CurrencyFormatter
let symbol : String

init(_ data: CurrencyData) {
self.name = data.name
self.baseConvRate = data.baseConvRate
self.formatter = data.formatter
self.symbol = data.symbol
}
}

Expand Down
47 changes: 47 additions & 0 deletions MMEX/View/Manage/Currency/CurrencyFormView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ struct CurrencyFormView: View {

@FocusState var focusState: Int?


var baseCurrencyId: DataId? { vm.infotableList.baseCurrencyId.readyValue }
var baseCurrencySymbol: String? { baseCurrencyId.map { vm.currencyList.info.readyValue?[$0]?.symbol } ?? nil }

@State private var isSyncing: Bool = false // To indicate syncing in progress
@State private var fetcher: CurrencyRateFetcher = .init(baseCurrency: "")

var format: String {
let amount: Double = 12345.67
return amount.formatted(by: data.formatter)
Expand Down Expand Up @@ -73,6 +80,17 @@ struct CurrencyFormView: View {
TextField("Default is 1", value: $data.baseConvRate.defaultOne, format: .number)
.focused($focusState, equals: 5)
.keyboardType(pref.theme.decimalPad)
Button(action: syncCurrencyRate) {
if isSyncing {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
} else {
Image(systemName: "arrow.2.circlepath") // Sync icon
}
}
.disabled(isSyncing || !fetcher.isValid)
.padding(.top)
.buttonStyle(BorderedButtonStyle())
}, showView: {
Text("\(data.baseConvRate)")
} )
Expand Down Expand Up @@ -118,8 +136,37 @@ struct CurrencyFormView: View {
}
}
}
.onAppear {
fetcher.setBaseCurrency(baseCurrencySymbol ?? "")
}
.keyboardState(focus: $focus, focusState: $focusState)
}

func syncCurrencyRate() {
guard fetcher.isValid else {
// Handle invalid base currency here
print("Base currency is invalid. Sync disabled.")
return
}

isSyncing = true

Task {
do {
let updatedRate = try await fetcher.fetchConversionRate(for: data.symbol) // Replace "AED" with actual target currency
data.baseConvRate = updatedRate
} catch {
// Handle error here
print("Error fetching conversion rate: \(error)")
}

isSyncing = false
}
}
}

struct ConversionRate {
var defaultOne: Double
}

#Preview("\(CurrencyData.sampleData[0].symbol) (read)") {
Expand Down
62 changes: 62 additions & 0 deletions MMEX/View/Utility/CurrencyRateFetcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// CurrencyRateFetcher.swift
// MMEX
//
// Created by Lisheng Guan on 2025/2/25.
//

import Foundation

// Define the structure of the response to match the JSON structure
struct CurrencyRateResponse: Decodable {
let result: String
let base_code: String
let conversion_rates: [String: Double] // The conversion rates dictionary
}

struct CurrencyRateFetcher {

private var baseCurrency: String
private let validBaseCurrencies = ["USD", "EUR", "GBP", "CNY", "AUD"]

// Computed property to dynamically check if the base currency is valid
var isValid: Bool {
return validBaseCurrencies.contains(baseCurrency)
}

// Default initializer with "USD" as the base currency
init(baseCurrency: String = "USD") {
self.baseCurrency = baseCurrency
}

// Method to update the base currency
mutating func setBaseCurrency(_ newBaseCurrency: String) {
self.baseCurrency = newBaseCurrency
}

// Function to fetch conversion rate for a given target currency
func fetchConversionRate(for targetCurrency: String) async throws -> Double {
guard isValid else {
throw NSError(domain: "CurrencyRateFetcher", code: 400, userInfo: [NSLocalizedDescriptionKey: "Base currency is invalid."])
}

let urlString = "https://moneymanagerex.org/currency/data/latest_\(baseCurrency).json"
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}

let (data, _) = try await URLSession.shared.data(from: url)

let decoder = JSONDecoder()
let response = try decoder.decode(CurrencyRateResponse.self, from: data)

guard let directRate = response.conversion_rates[targetCurrency] else {
throw NSError(domain: "CurrencyRateFetcher", code: 404, userInfo: [NSLocalizedDescriptionKey: "Currency not found."])
}

// If the base currency is the same as the response, direct rate is already provided
let indirectRate = 1.0 / directRate

return indirectRate
}
}
2 changes: 2 additions & 0 deletions MMEX/ViewModel/List/CurrencyList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ extension ViewModel {
guard currencyList.reloading() else { return }
let ok = await withTaskGroup(of: Bool.self) { taskGroup -> Bool in
let ok = [
load(pref, &taskGroup, keyPath: \Self.infotableList.baseCurrencyId),
load(pref, &taskGroup, keyPath: \Self.currencyList.name),
load(pref, &taskGroup, keyPath: \Self.currencyList.data),
load(pref, &taskGroup, keyPath: \Self.currencyList.used),
load(pref, &taskGroup, keyPath: \Self.currencyList.order),
Expand Down

0 comments on commit 2e0c57d

Please sign in to comment.