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:
alexandrev-tibco
2026-07-08 16:11:18 +02:00
parent 19b1408c55
commit a830a5f40c
9 changed files with 450 additions and 0 deletions
@@ -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"
)
}
}