refactor: own daily currency calculation lifetime
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import Foundation
|
||||
|
||||
/// Owns shared daily rates and calculation state for one launcher lifetime.
|
||||
@MainActor
|
||||
public final class CurrencyCalculation {
|
||||
public private(set) var result: CalculatorResult?
|
||||
public private(set) var isCurrency = false
|
||||
public var changed: (() -> Void)?
|
||||
|
||||
private let fetch: @MainActor () async throws -> CurrencyRates
|
||||
private let now: () -> Date
|
||||
private let calendar: Calendar
|
||||
private var query = ""
|
||||
private var rates: CurrencyRates?
|
||||
private var fetchedAt: Date?
|
||||
private var task: Task<Void, Never>?
|
||||
private var error: String?
|
||||
private var failureQuery: String?
|
||||
|
||||
public init(
|
||||
now: @escaping () -> Date = Date.init,
|
||||
calendar: Calendar = .current,
|
||||
fetch: @escaping @MainActor () async throws -> CurrencyRates = { try await CurrencyAPI.fetch() }
|
||||
) {
|
||||
self.now = now
|
||||
self.calendar = calendar
|
||||
self.fetch = fetch
|
||||
}
|
||||
|
||||
deinit { task?.cancel() }
|
||||
|
||||
public func update(_ query: String) {
|
||||
self.query = query
|
||||
if failureQuery != query {
|
||||
error = nil
|
||||
failureQuery = nil
|
||||
}
|
||||
evaluate(allowingFetch: true)
|
||||
}
|
||||
|
||||
private func evaluate(allowingFetch: Bool) {
|
||||
isCurrency = Calculator.isCurrencyQuery(query, currencyRates: rates)
|
||||
let stale = fetchedAt.map { !calendar.isDate($0, inSameDayAs: now()) } ?? true
|
||||
if allowingFetch, isCurrency, stale, error == nil, task == nil { refresh() }
|
||||
result = Calculator.evaluate(query, currencyRates: rates)
|
||||
if isCurrency, let error {
|
||||
let cached = rates.map { " Cached snapshot \($0.date) is not used." } ?? ""
|
||||
result = CalculatorResult(displayValue: "Currency rates unavailable", copyValue: nil,
|
||||
detail: "Daily rates: \(error). Change the query to retry.\(cached)")
|
||||
} else if isCurrency, rates != nil, let answer = result {
|
||||
let cacheStatus = stale
|
||||
? (task == nil ? "Stale cached daily rates" : "Stale cache; refreshing daily rates…")
|
||||
: "Cached daily rates"
|
||||
result = CalculatorResult(displayValue: answer.displayValue, copyValue: answer.copyValue,
|
||||
detail: "\(answer.detail) · \(cacheStatus)")
|
||||
}
|
||||
}
|
||||
|
||||
private func refresh() {
|
||||
let fetch = self.fetch
|
||||
task = Task { [weak self] in
|
||||
let response: Result<CurrencyRates, any Error>
|
||||
do { response = .success(try await fetch()) }
|
||||
catch { response = .failure(error) }
|
||||
guard !Task.isCancelled, let self else { return }
|
||||
self.task = nil
|
||||
switch response {
|
||||
case .success(let rates):
|
||||
self.rates = rates
|
||||
self.fetchedAt = self.now()
|
||||
self.error = nil
|
||||
self.failureQuery = nil
|
||||
case .failure(let error):
|
||||
self.error = error.localizedDescription
|
||||
self.failureQuery = self.query
|
||||
}
|
||||
self.evaluate(allowingFetch: false)
|
||||
self.changed?()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user