Intent paramétrico Log Value + frases de Siri localizadas en 7 idiomas
LogSnapshotIntent: 'Registra el saldo de Indexa' por voz o desde Atajos, sin abrir la app (openAppWhenRun=false). SourceEntity expone las fuentes a Siri/Atajos desde el mirror del App Group; el valor entra por la misma cola que el Share Extension y se materializa como Snapshot al instante (el intent corre en el proceso de la app). Diálogo de confirmación con formato de divisa de la fuente. AppShortcuts.xcstrings: las 7 frases de los 3 shortcuts localizadas a es-ES/de/fr/it/ja/pt-BR — Siri en español ya funciona. Strings de UI de Atajos (títulos, parámetros, diálogos) añadidas a los 7 Localizable.strings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
This commit is contained in:
@@ -33,6 +33,90 @@ struct OpenDashboardIntent: AppIntent {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Parametric logging (headless)
|
||||
|
||||
/// An investment source exposed to Siri/Shortcuts, backed by the App Group
|
||||
/// mirror the Share Extension already uses — no Core Data access needed at
|
||||
/// resolution time, so it works instantly even before the app launches.
|
||||
struct SourceEntity: AppEntity {
|
||||
static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Source")
|
||||
static let defaultQuery = SourceEntityQuery()
|
||||
|
||||
let id: UUID
|
||||
let name: String
|
||||
let currencyCode: String
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(title: "\(name)")
|
||||
}
|
||||
|
||||
init(_ info: SharedSourceInfo) {
|
||||
id = info.id
|
||||
name = info.name
|
||||
currencyCode = info.currencyCode
|
||||
}
|
||||
}
|
||||
|
||||
struct SourceEntityQuery: EntityQuery {
|
||||
func entities(for identifiers: [UUID]) async throws -> [SourceEntity] {
|
||||
SharedQuickUpdateStore.readMirror()
|
||||
.filter { identifiers.contains($0.id) }
|
||||
.map(SourceEntity.init)
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [SourceEntity] {
|
||||
// Pending-first, mirroring the Share Extension's ordering.
|
||||
SharedQuickUpdateStore.readMirror()
|
||||
.sorted { a, b in
|
||||
if a.updatedThisMonth != b.updatedThisMonth { return !a.updatedThisMonth }
|
||||
return a.name.localizedCaseInsensitiveCompare(b.name) == .orderedAscending
|
||||
}
|
||||
.map(SourceEntity.init)
|
||||
}
|
||||
}
|
||||
|
||||
/// "Log 15000 for Indexa" — records a snapshot without opening any UI. Runs in
|
||||
/// the app process in the background: the value goes through the same pending
|
||||
/// queue as the Share Extension and is ingested into Core Data immediately.
|
||||
struct LogSnapshotIntent: AppIntent {
|
||||
static let title: LocalizedStringResource = "Log Portfolio Value"
|
||||
static let description = IntentDescription("Record a value for one of your sources without opening the app.")
|
||||
static let openAppWhenRun = false
|
||||
|
||||
@Parameter(title: "Source")
|
||||
var source: SourceEntity
|
||||
|
||||
@Parameter(title: "Amount", requestValueDialog: "How much?")
|
||||
var amount: Double
|
||||
|
||||
static var parameterSummary: some ParameterSummary {
|
||||
Summary("Log \(\.$amount) for \(\.$source)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult & ProvidesDialog {
|
||||
guard amount > 0 else {
|
||||
throw $amount.needsValueError(IntentDialog(stringLiteral: String(localized: "intent_amount_invalid")))
|
||||
}
|
||||
|
||||
SharedQuickUpdateStore.appendPending(PendingQuickUpdate(
|
||||
sourceId: source.id,
|
||||
amount: amount,
|
||||
capturedAt: Date()
|
||||
))
|
||||
// We're inside the app process: materialize the snapshot right away.
|
||||
SharedQuickUpdateSync.ingestPending()
|
||||
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .currency
|
||||
formatter.currencyCode = source.currencyCode
|
||||
let formatted = formatter.string(from: NSNumber(value: amount)) ?? "\(amount)"
|
||||
|
||||
let text = String(format: String(localized: "intent_logged_dialog"), formatted, source.name)
|
||||
return .result(dialog: IntentDialog(stringLiteral: text))
|
||||
}
|
||||
}
|
||||
|
||||
struct PortfolioJournalShortcuts: AppShortcutsProvider {
|
||||
static var appShortcuts: [AppShortcut] {
|
||||
AppShortcut(
|
||||
@@ -53,5 +137,15 @@ struct PortfolioJournalShortcuts: AppShortcutsProvider {
|
||||
shortTitle: "Check Portfolio",
|
||||
systemImageName: "chart.line.uptrend.xyaxis"
|
||||
)
|
||||
AppShortcut(
|
||||
intent: LogSnapshotIntent(),
|
||||
phrases: [
|
||||
"Log a value in \(.applicationName)",
|
||||
"Log my \(\.$source) balance in \(.applicationName)",
|
||||
"Update \(\.$source) in \(.applicationName)"
|
||||
],
|
||||
shortTitle: "Log Value",
|
||||
systemImageName: "square.and.pencil"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user