Files
audi-app/companion-app/native/BelegZwischenablage.swift
T
tobias 43f25e2e0d Tankstelle zweizeilig und antippbar, Belegkarte ohne toten Knopf (2026.9.4.5)
Sieben Meldungen des Eigentuemers, beide Codebasen.

Karte im Einzelbeleg: "Position laedt nicht" und "der Button funktioniert
nicht" waren dieselbe Stelle - ohne aufloesbaren Ort malte das Panel einen
beliebigen Ausschnitt und liess den Zentrieren-Knopf fuer immer disabled.
Beide zeigen jetzt die Auskunft statt einer Karte und unterscheiden dabei
"steht nicht im Beleg" von "findet niemand". Dazu ein echter Paritaetsmangel:
die App schlug nur station_address nach, das Panel faellt seit jeher auf den
Namen zurueck - von 16 Testbelegen tragen vier eine Anschrift.

Der Suchbegriff darf Name und Anschrift NICHT zusammensetzen: gemessen findet
Nominatim "Pascalstr. 8, 85057 Ingolstadt", mit dem Betreibernamen davor
nichts. Jetzt zwei Kandidaten nacheinander (Anschrift, dann Name); fuer den
Kartendienst dagegen beides zusammen, weil Apple und Google Betriebsnamen
kennen.

Tankstelle steht ueber die volle Kachelbreite, zweizeilig (Tankstelle und
Strasse / Ort) und fuehrt beim Tippen in den Standard-Kartendienst - Apple
Karten auf Apple-Geraeten, sonst Google Maps. Dieselbe Wahl jetzt auch fuer
Fahrzeugstandort und Autohaus.

SmartDeal-Ersparnis brach um, weil die dt-Spalte 150,00px breit war und ihr
Inhalt 150,20px - zwei Zehntel Pixel, Zeile dadurch 44,8 statt 26,4px hoch.

Der Regler "Fahrt beenden" schreibt nicht mehr beim Loslassen: derselbe Wert
stellt den Schlaf-Timeout des Dongles, das soll bewusst ausgeloest werden.
Speichern/Verwerfen erscheinen nur bei echter Aenderung.

Share-Erweiterung: an der ausgelieferten .ipa gemessen trug sie 1.0, die App
2026.9.3.3 - iOS registriert eine Erweiterung mit abweichender Versionsnummer
nicht, sie fehlt dann im Teilen-Blatt. ios-signieren.sh schreibt die Version
jetzt in beide Ziele und liest die fertige .ipa gegen. Zwischenablage: dritter
Weg ueber UIPasteboard.itemProviders, und ein fehlender nativer Teil wird als
solcher gemeldet statt als "Kein Zugriff".

Am Geraet gesetzt: 1003 Network Ping Timeout 0 -> 60 s, 1004 ACK Type
TCP/IP -> AVL. Beide liegen bei flespi als pending bereit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 10:10:55 +02:00

100 lines
4.5 KiB
Swift

//
// BelegZwischenablage.swift
// DataMetric360
//
// Holt ein PDF aus der System-Zwischenablage der Weg, den die Web-API
// nicht anbietet.
//
// WARUM ES DAS BRAUCHT
// --------------------
// `navigator.clipboard.read()` gibt aus Datenschutzgründen nur Text, HTML
// und Bilder heraus; ein kopiertes PDF taucht dort NIE auf. Der Knopf Aus
// Zwischenablage einfügen" endete deshalb auf dem Telefon zwangsläufig mit
// In der Zwischenablage liegt kein PDF" (gemeldet vom Eigentümer,
// 02.09.2026). Das ist keine Lücke in unserem Code, sondern die Grenze der
// Schnittstelle.
//
// `UIPasteboard` kennt diese Grenze nicht: es gibt beliebige Typen heraus,
// auch `com.adobe.pdf`. Der Preis ist derselbe wie im Web iOS zeigt beim
// Zugriff seine eigene Einfügen-Rückfrage, und das ist richtig so.
//
// DREI FORMEN, IN DENEN EIN PDF IN DER ZWISCHENABLAGE LIEGT
// ---------------------------------------------------------
// 1. Als Daten unter `com.adobe.pdf` so legen es die meisten Apps ab.
// 2. Als Datei-URL Mail kopiert einen Anhang oft nur als Verweis auf eine
// temporäre Kopie. Ohne den zweiten Weg bliebe genau der Fall übrig, für
// den der Knopf gedacht ist.
// 3. Als Zusage (`NSItemProvider`) die Datei existiert dann noch gar nicht,
// die kopierende App liefert sie erst auf Anforderung. `data(forPasteboardType:)`
// gibt dabei nil zurück und `urls` bleibt leer; nur der Anbieter kennt sie.
// Das ist Apples eigener, empfohlener Weg und der einzige, der auch eine
// Datei aus einer Sandbox einer anderen App zuverlässig herausgibt Weg 2
// scheitert dort am Lesezugriff. Er ist asynchron, deshalb steht er hier
// zuletzt: die beiden schnellen Wege sollen nicht auf ihn warten.
//
// DIESE DATEI IST VERSIONIERT, ios/ NICHT.
// `npx cap add ios` erzeugt den nativen Ordner neu und würde alles darin
// verlieren. scripts/ios-teilen-einrichten.mjs kopiert sie bei jedem Bau
// wieder hinein und hängt sie an die Sources-Phase des App-Ziels.
//
import Capacitor
import Foundation
import UIKit
import UniformTypeIdentifiers
@objc(BelegZwischenablagePlugin)
public class BelegZwischenablagePlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "BelegZwischenablagePlugin"
public let jsName = "BelegZwischenablage"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "pdfHolen", returnType: CAPPluginReturnPromise)
]
/// Liefert `{ base64, name }`, wenn ein PDF in der Zwischenablage liegt
/// sonst ein leeres Ergebnis. Bewusst kein Fehler: nichts kopiert" ist
/// kein Fehlschlag, sondern eine Antwort, und die Oberfläche sagt dazu
/// ohnehin das Passende (zwischenablage.ts).
@objc func pdfHolen(_ call: CAPPluginCall) {
let ablage = UIPasteboard.general
if let daten = ablage.data(forPasteboardType: UTType.pdf.identifier) {
return call.resolve(["base64": daten.base64EncodedString(), "name": "beleg.pdf"])
}
// Mail legt den Anhang oft nur als Verweis ab.
for url in ablage.urls ?? [] where url.pathExtension.lowercased() == "pdf" {
// Sicherheitsbereich anfordern: die URL zeigt auf eine Datei
// außerhalb des eigenen Sandkastens, sonst schlägt das Lesen fehl.
let erlaubt = url.startAccessingSecurityScopedResource()
defer { if erlaubt { url.stopAccessingSecurityScopedResource() } }
if let daten = try? Data(contentsOf: url) {
return call.resolve([
"base64": daten.base64EncodedString(),
"name": url.lastPathComponent,
])
}
}
// Dritter Weg: die Datei liegt nur als Zusage vor.
let anbieter = ablage.itemProviders.first {
$0.hasItemConformingToTypeIdentifier(UTType.pdf.identifier)
}
guard let anbieter else {
return call.resolve([:])
}
anbieter.loadDataRepresentation(forTypeIdentifier: UTType.pdf.identifier) { daten, _ in
guard let daten, !daten.isEmpty else {
// Kein Fehler: die Oberfläche sagt zu einem leeren Ergebnis
// ohnehin das Passende (zwischenablage.ts).
return call.resolve([:])
}
let name = anbieter.suggestedName.map { $0.hasSuffix(".pdf") ? $0 : $0 + ".pdf" }
call.resolve([
"base64": daten.base64EncodedString(),
"name": name ?? "beleg.pdf",
])
}
}
}