b1411625aa
Share-Erweiterung: ein PDF aus Mail landet ueber Teilen -> DataMetric360 als Tankbeleg. Alles Native liegt versioniert unter companion-app/native/, weil ios/ gitignored ist und npx cap add ios es sonst wieder frisst; scripts/ios-teilen-einrichten.mjs haengt es bei jedem Bau ins Xcode-Projekt und ist in ios-signieren.sh eingehaengt. Der pbxproj-Teil ist auf keinem Mac erprobt - er bricht vor dem Schreiben ab, und docs/SHARE_EXTENSION.md beschreibt dieselben Handgriffe fuer Xcode. "In den Kalender uebernehmen" ging in der App nie: WKWebView ignoriert das download-Attribut, der Klick lief ins Leere. Auf dem Geraet jetzt @capacitor/filesystem plus @capacitor/share. useTheme/useBoden legten je Aufrufer eigenen Zustand an - fuenf Kopien. Der Schalter aenderte nur seine eigene, data-theme in App.tsx blieb stehen, die Tag/Nacht-Umschaltung wirkte erst nach einem Neustart. Jetzt ein Speicher je Einstellung ueber useSyncExternalStore. Zwei Regressionstests, gegen den alten Stand als fehlschlagend nachgewiesen. Alle Popups nach Apple HIG: neue Bausteine Sheet und ActionSheet, blickdicht und mit abgedunkeltem Schleier, in beiden Codebasen. Popup portalierte nach document.body - ausserhalb von [data-theme] - und war deshalb im Tagmodus fast unsichtbar; Portal zielt jetzt auf .ads-root. ImagePlaceholder merkte sich "fehlgeschlagen" ohne die Adresse: nach einem leeren Galerieplatz zeigten auch die mit Foto nur noch den Platzhalter. Vier Regressionstests, ebenfalls gegen den alten Stand geprueft. Bildadressen tragen jetzt einen Cache-Brecher - HA liefert /local/ mit 31 Tagen Cache-Vorgabe aus, im Panel eingefuegte Radfotos erschienen in der App deshalb nicht. uebersichtsbild: Panel speichert den Namen, die App verglich gegen den Dateinamen - der Vergleich traf nie zu. Kanonisch ist der Name. Weiter: Navigationspfeil statt gleichschenkligem Dreieck auf der Streckenlinie (die Kerbe unterscheidet Kopf und Ende), CI-Symbol tour-s als Fahrten-Icon, Datumsfelder zeigen ihren Wert ohne erstes Antippen, feste Beispielnamen im Setup, Kopf-Gegengewicht fuer mittige Titel, Standort-Blatt mit festem Fussabstand, NSLocationWhenInUseUsageDescription, watchPosition fuer die Live-Ortung, Art-Pille wieder als Knopf, Zwischenablage fuer Tankbelege. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
4.6 KiB
TypeScript
117 lines
4.6 KiB
TypeScript
/**
|
|
* Fahrzeugfotos. Die Dateinamen sind fest: `bilderverwaltung.py` prüft jeden
|
|
* Upload gegen eine Positivliste und lehnt alles andere ab — hier steht
|
|
* dieselbe Liste, damit die Oberfläche gar nicht erst etwas anbieten kann,
|
|
* das der Server ablehnen würde.
|
|
*/
|
|
|
|
import { zugangLesenSynchron } from "./zugang"
|
|
|
|
export interface Bildplatz {
|
|
name: string
|
|
/**
|
|
* Angezeigte Bezeichnung des Fotos — wortgleich mit `label` in BILDER im
|
|
* Panel. Sie steht als Alternativtext am Bild und als Beschriftung im
|
|
* Platzhalter; hier standen vorher eigene, längere Formulierungen
|
|
* („Auto von der Seite" statt „Auto Seite").
|
|
*/
|
|
beschreibung: string
|
|
datei: string
|
|
}
|
|
|
|
/** Die sieben Plätze der Fotogalerie unter „Mein Audi". */
|
|
export const BILDPLAETZE: Bildplatz[] = [
|
|
{ name: "Seitenansicht", beschreibung: "Auto Seite", datei: "seitenansicht.webp" },
|
|
{ name: "Front schräg", beschreibung: "Auto Front schräg", datei: "front-schraeg.webp" },
|
|
{ name: "Frontansicht", beschreibung: "Auto Front", datei: "frontansicht.webp" },
|
|
{ name: "Heckansicht", beschreibung: "Auto Heck", datei: "heckansicht.webp" },
|
|
{ name: "Cockpit", beschreibung: "Innenraum Cockpit", datei: "cockpit.webp" },
|
|
{ name: "Scheinwerfer", beschreibung: "Scheinwerfer", datei: "scheinwerfer.webp" },
|
|
{ name: "Sitze", beschreibung: "Innenraum Sitze", datei: "sitze.webp" },
|
|
]
|
|
|
|
/**
|
|
* Bei montierten Winterrädern zeigt die Seitenansicht das Winterfoto —
|
|
* `SEITE_WINTER`/`bildInfo()` im Panel. Fehlte hier bisher ganz.
|
|
*/
|
|
export const SEITE_WINTER = {
|
|
beschreibung: "Auto Seite mit Winterrädern",
|
|
datei: "seitenansicht-winter.webp",
|
|
} as const
|
|
|
|
/** Datei und Bezeichnung eines Galerieplatzes, Winterräder berücksichtigt. */
|
|
export function bildInfo(
|
|
platz: Bildplatz,
|
|
winterraeder: boolean,
|
|
): { datei: string; beschreibung: string } {
|
|
if (platz.name === "Seitenansicht" && winterraeder) {
|
|
return { datei: SEITE_WINTER.datei, beschreibung: SEITE_WINTER.beschreibung }
|
|
}
|
|
return { datei: platz.datei, beschreibung: platz.beschreibung }
|
|
}
|
|
|
|
/** Zusätzlich erlaubt, aber außerhalb der Galerie geführt. */
|
|
export const WEITERE_BILDER = [
|
|
"seitenansicht-winter.webp",
|
|
"rad-sommer.webp",
|
|
"rad-winter.webp",
|
|
"draufsicht.webp",
|
|
] as const
|
|
|
|
export const ERLAUBTE_DATEINAMEN: readonly string[] = [
|
|
...BILDPLAETZE.map((b) => b.datei),
|
|
...WEITERE_BILDER,
|
|
]
|
|
|
|
/**
|
|
* Stand der Fahrzeugfotos, als Anhängsel an jede Bildadresse.
|
|
*
|
|
* Home Assistant liefert alles unter `/local/` mit
|
|
* `Cache-Control: public, max-age=2678400` aus — einunddreißig Tage
|
|
* (nachgemessen 2026-08-31). Ohne ein wechselndes Anhängsel zeigt die App
|
|
* deshalb einen Monat lang das Bild, das sie beim ersten Mal geholt hat:
|
|
* ein neu hochgeladenes Foto erscheint nicht, ein ersetztes bleibt das alte,
|
|
* und ein 404 von vorher bleibt ein 404. Genau das ist dem Eigentümer mit
|
|
* den Radfotos passiert — im Panel eingefügt, in der App unsichtbar.
|
|
*
|
|
* Das Panel hängt seit jeher `?v=${bildVersion}` an (`bildInfo()` dort) und
|
|
* setzt den Wert nach jedem Hochladen oder Löschen neu. Hier dasselbe: beim
|
|
* Start der App einmal, danach bei jeder Änderung.
|
|
*
|
|
* Der Preis ist ein erneuter Abruf je App-Start. Der ist gewollt — ein Foto,
|
|
* das der Nutzer nicht sieht, kostet mehr als ein Megabyte über eine
|
|
* Verbindung, die ohnehin gerade das ganze Fahrzeugprofil holt.
|
|
*/
|
|
let bildVersion = Date.now()
|
|
|
|
/** Nach dem Hochladen oder Löschen eines Fotos aufrufen. */
|
|
export function bildVersionErneuern(): void {
|
|
bildVersion = Date.now()
|
|
}
|
|
|
|
/**
|
|
* Adresse eines Fahrzeugfotos auf dem Server. Die Bilder liegen unter
|
|
* `/local/bilder/` — anders als beim alten Panel läuft die App nicht
|
|
* zwangsläufig auf derselben Adresse, deshalb die vollständige URL.
|
|
*/
|
|
export function bildUrl(dateiname: string | null | undefined): string | undefined {
|
|
if (!dateiname) return undefined
|
|
const basis = zugangLesenSynchron()?.basisUrl
|
|
if (!basis) return undefined
|
|
return `${basis}/local/bilder/${dateiname}?v=${bildVersion}`
|
|
}
|
|
|
|
/**
|
|
* Feste Datei des Kraftstoff-Marken-Logos für die SmartDeal-Anzeige -
|
|
* Bestandteil des Integrations-Bündels
|
|
* (`custom_components/audi_dashboard/frontend/bilder/shell-logo.svg`), unter
|
|
* dem eigenen Statik-Pfad der Integration ausgeliefert (`/audi_dashboard_static/`)
|
|
* - anders als die Fahrzeugfotos NICHT unter `/local/bilder/`. Deckungsgleich
|
|
* mit `MARKENLOGO_DATEI`/`STATIK` im Panel.
|
|
*/
|
|
export function markenlogoUrl(): string | undefined {
|
|
const basis = zugangLesenSynchron()?.basisUrl
|
|
if (!basis) return undefined
|
|
return `${basis}/audi_dashboard_static/bilder/shell-logo.svg`
|
|
}
|