BlueWallet/ios/Widgets/PriceWidget/PriceIntent.swift

137 lines
4.3 KiB
Swift
Raw Normal View History

2024-10-27 18:14:37 -04:00
import AppIntents
import SwiftUI
2024-10-28 13:27:53 -04:00
2024-10-27 18:14:37 -04:00
@available(iOS 16.0, *)
struct PriceIntent: AppIntent {
static var title: LocalizedStringResource = "Market Rate"
static var description = IntentDescription("View the current Bitcoin market rate.")
2024-10-27 19:09:03 -04:00
static var openAppWhenRun: Bool { false }
2024-10-27 18:14:37 -04:00
static var parameterSummary: some ParameterSummary {
Summary("View the current Bitcoin market rate.")
}
@MainActor
2024-10-28 13:27:53 -04:00
func perform() async throws -> some IntentResult & ReturnsValue<Double> & ProvidesDialog & ShowsSnippetView {
2024-10-27 18:14:37 -04:00
let userPreferredCurrency = Currency.getUserPreferredCurrency()
2024-10-27 19:09:03 -04:00
let currencyCode = userPreferredCurrency.uppercased()
let dataSource = fiatUnit(currency: userPreferredCurrency)?.source ?? "Unknown Source"
2024-10-27 18:14:37 -04:00
if userPreferredCurrency != Currency.getLastSelectedCurrency() {
Currency.saveNewSelectedCurrency()
}
private enum Constants {
static let defaultValue = "--"
static let initialValue = 0.0
}
var lastUpdated = Constants.defaultValue
var resultValue: Double = Constants.initialValue
2024-10-27 18:14:37 -04:00
enum PriceIntentError: LocalizedError {
case fetchFailed
case invalidData
var errorDescription: String? {
switch self {
case .fetchFailed:
return "Failed to fetch price data"
case .invalidData:
return "Received invalid price data"
}
}
}
2024-10-27 18:14:37 -04:00
do {
2024-10-27 19:09:03 -04:00
guard let data = try await MarketAPI.fetchPrice(currency: userPreferredCurrency) else {
throw PriceIntentError.fetchFailed
2024-10-27 18:14:37 -04:00
}
2024-10-27 19:09:03 -04:00
2024-10-28 13:27:53 -04:00
resultValue = data.rateDouble
2024-10-27 19:09:03 -04:00
lastUpdated = formattedDate(from: data.lastUpdate)
2024-10-27 18:14:37 -04:00
} catch {
2024-10-28 13:30:38 -04:00
throw PriceIntentError.fetchFailed
2024-10-27 18:14:37 -04:00
}
2024-10-28 13:27:53 -04:00
let formattedPrice = formatPrice(resultValue, currencyCode: currencyCode)
2024-10-27 19:09:03 -04:00
let view = CompactPriceView(
2024-10-28 22:26:07 -04:00
price: resultValue,
2024-10-27 19:09:03 -04:00
lastUpdated: lastUpdated,
currencyCode: currencyCode,
dataSource: dataSource
)
2024-10-27 18:14:37 -04:00
return .result(
2024-10-28 22:42:26 -04:00
value: data.rateDouble,
2024-10-27 18:14:37 -04:00
dialog: "Current Bitcoin Market Rate",
view: view
)
}
2024-10-27 19:09:03 -04:00
private func formattedDate(from isoString: String?) -> String {
guard let isoString = isoString else { return "--" }
2024-10-27 18:14:37 -04:00
let isoFormatter = ISO8601DateFormatter()
if let date = isoFormatter.date(from: isoString) {
let formatter = DateFormatter()
2024-10-28 12:20:40 -04:00
formatter.dateStyle = .medium
2024-10-27 18:14:37 -04:00
formatter.timeStyle = .short
return formatter.string(from: date)
}
return "--"
}
2024-10-28 12:20:40 -04:00
private func formatPrice(_ price: Double, currencyCode: String) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = currencyCode
formatter.locale = Locale.current
2024-10-28 13:27:53 -04:00
// Omit cents if price is a whole number
2024-10-28 12:20:40 -04:00
if price.truncatingRemainder(dividingBy: 1) == 0 {
formatter.maximumFractionDigits = 0
formatter.minimumFractionDigits = 0
} else {
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
}
return formatter.string(from: NSNumber(value: price)) ?? "--"
}
2024-10-27 18:14:37 -04:00
}
2024-10-28 13:27:53 -04:00
2024-10-27 19:09:03 -04:00
@available(iOS 16.0, *)
2024-10-27 18:14:37 -04:00
struct CompactPriceView: View {
2024-10-28 12:20:40 -04:00
let price: String
2024-10-27 18:14:37 -04:00
let lastUpdated: String
2024-10-27 19:09:03 -04:00
let currencyCode: String
let dataSource: String
2024-10-27 18:14:37 -04:00
var body: some View {
2024-10-28 12:20:40 -04:00
VStack(alignment: .center, spacing: 16) {
Text(price)
2024-10-27 18:14:37 -04:00
.font(.title)
2024-10-28 12:20:40 -04:00
.bold()
2024-10-27 19:09:03 -04:00
.multilineTextAlignment(.center)
.dynamicTypeSize(.large ... .accessibility5)
2024-10-28 12:20:40 -04:00
.accessibilityLabel("Bitcoin price: \(price)")
VStack(alignment: .center, spacing: 4) {
Text("Currency: \(currencyCode)")
Text("Updated: \(lastUpdated)")
Text("Source: \(dataSource)")
}
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.accessibilityElement(children: .combine)
2024-10-27 18:14:37 -04:00
}
.padding()
2024-10-28 12:20:40 -04:00
.frame(maxWidth: .infinity)
2024-10-27 18:14:37 -04:00
}
}