OCR desde imagen del portapapeles + botón copiar diagnóstico iCloud (build 61)

- Quick Update: checkClipboard ahora detecta también una IMAGEN copiada
  (pb.hasImages) y hace OCR automático — te ofrece pegar el importe detectado
  sin pulsar 'Escanear'. Gated por changeCount. El 'Pegar €X' del teclado
  también usa el importe OCR'd de la imagen.
- Settings iCloud: botón 'Copiar diagnóstico de iCloud' SIEMPRE visible (el
  desplegable de detalle solo salía con un error activo, difícil de encontrar).
  Copia versión/build, contadores locales, últimas fechas import/export,
  criticidad, hint y el error completo con códigos internos. Strings ×7.

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-10 21:52:53 +02:00
parent 6bdf2740c5
commit 7c6a29c821
10 changed files with 79 additions and 23 deletions
@@ -24,6 +24,7 @@ struct QuickUpdateView: View {
@FocusState private var focusedSource: NSManagedObjectID?
@State private var clipboardAmount: Decimal?
@State private var lastSuggestedRaw: String?
@State private var lastPasteboardChangeCount = -1
// Per-field paste/scan (feedback #3): keyboard toolbar acts on the focused
// field paste a clipboard amount or OCR a number from a screenshot.
@@ -173,12 +174,14 @@ struct QuickUpdateView: View {
}
}
/// Clipboard amount for the keyboard toolbar (independent of the top banner's
/// one-shot suggestion state).
/// Clipboard amount for the keyboard toolbar a live text amount, or the
/// amount OCR'd from a copied image (held in clipboardAmount).
private var clipboardFieldAmount: Decimal? {
guard let raw = UIPasteboard.general.string,
let parsed = CurrencyFormatter.parseUserInput(raw), parsed > 0 else { return nil }
return parsed
if let raw = UIPasteboard.general.string,
let parsed = CurrencyFormatter.parseUserInput(raw), parsed > 0 {
return parsed
}
return clipboardAmount
}
private func decimalInputString(_ value: Decimal) -> String {
@@ -286,17 +289,30 @@ struct QuickUpdateView: View {
)
}
/// Reads the pasteboard and offers the parsed amount for the next pending source.
/// Skips values already suggested (or already typed) so returning to the app
/// with the same clipboard doesn't nag.
/// Reads the pasteboard and offers a parsed amount for the next pending source.
/// Handles BOTH a copied text value and a copied image (screenshot) the
/// image is OCR'd on-device and its most prominent number offered. Gated by
/// the pasteboard change count so returning with the same clipboard doesn't nag.
private func checkClipboard() {
guard let raw = UIPasteboard.general.string, raw != lastSuggestedRaw,
let parsed = CurrencyFormatter.parseUserInput(raw), parsed > 0,
nextEmptySource != nil else {
let pb = UIPasteboard.general
guard pb.changeCount != lastPasteboardChangeCount, nextEmptySource != nil else { return }
lastPasteboardChangeCount = pb.changeCount
// 1. Text amount (cheap, synchronous).
if let raw = pb.string, let parsed = CurrencyFormatter.parseUserInput(raw), parsed > 0 {
clipboardAmount = parsed
return
}
lastSuggestedRaw = raw
clipboardAmount = parsed
// 2. Copied image OCR the amount automatically.
if pb.hasImages, let image = pb.image {
isScanning = true
ImageAmountScanner.scan(image) { candidates in
DispatchQueue.main.async {
isScanning = false
if let best = candidates.first { clipboardAmount = best.value }
}
}
}
}
private func applyClipboard(_ amount: Decimal, to source: InvestmentSource) {