Hinweis beim Oeffnen, wenn eine neuere Fassung bereitliegt
Bisher gab es dafuer nur den schmalen Streifen der Hinweisleiste, der dauerhaft mitlaeuft und leicht uebersehen wird. Ein neuer Stand ist aber ein Ereignis und gehoert einmal nach vorn. Zwei Faelle, zwei Texte: liegt ein passendes OTA-Buendel bereit, traegt das Blatt den Knopf, der die App direkt erneuert (derselbe Weg wie in den Einstellungen). Fehlt eines, hat sich Natives geaendert - dann sagt es das und bietet keinen Knopf an, der nichts bewirken koennte. Nur nativ: im Browser und im Panel laedt jeder Aufruf den aktuellen Stand. Einmal je Fassung: die weggetippte Serverfassung wird gemerkt, erst eine andere bringt das Blatt zurueck. Die Entscheidung steckt in der reinen Funktion hinweisFaellig(), sechs Faelle getestet. 179/179 Tests gruen, tsc sauber.
This commit is contained in:
@@ -8690,3 +8690,40 @@ WebP sichern und noch einmal versuchen." — und auf der Platte entsteht **nicht
|
||||
|
||||
Verifiziert: 5/5 Backend-Tests (und ohne die Sperre nachweislich rot), `tsc --noEmit` sauber,
|
||||
173/173 App-Tests, Panel als Modul geparst, `audi_ha_test` auf `2026.9.3.3` sauber gestartet.
|
||||
|
||||
---
|
||||
|
||||
## AL. Popup beim Öffnen, wenn eine neuere Fassung bereitliegt (2026-09-03)
|
||||
|
||||
Wunsch des Besitzers: die App soll beim Start sagen, dass es eine neue Fassung gibt — so wie andere
|
||||
Apps es tun, nur ohne App Store. Es gab bisher nur den schmalen Streifen der `Hinweisleiste`
|
||||
("Diese App ist älter als der Server"), der dauerhaft mitläuft und deshalb leicht übersehen wird.
|
||||
|
||||
Neu: `companion-app/src/screens/Versionshinweis.tsx`, ein `ActionSheet`, eingehängt in `App.tsx`
|
||||
neben der bestehenden Leiste. Die beiden ersetzen einander **nicht** — ein neuer Stand ist ein
|
||||
Ereignis und gehört einmal nach vorn, danach übernimmt wieder der Streifen für den fortbestehenden
|
||||
Zustand.
|
||||
|
||||
**Zwei Fälle, zwei Texte, weil zwei verschiedene Dinge zu tun sind.** Liegt ein passendes OTA-Bündel
|
||||
bereit (`buendelPasst()`), kann die App sich selbst erneuern — dann trägt das Blatt den Knopf, der
|
||||
genau das auslöst, über denselben `buendelAnwenden()`-Weg wie der Knopf in den Einstellungen. Fehlt
|
||||
eines, hat sich Natives geändert und es hilft nur neu aufspielen; dann sagt das Blatt genau das und
|
||||
bietet keinen Knopf an, der nichts bewirken könnte.
|
||||
|
||||
**Nur nativ.** Im Browser und im HA-Panel lädt jeder Aufruf den aktuellen Stand — dort kann die App
|
||||
gar nicht veralten, dieselbe Begründung wie in `appVersion.ts`.
|
||||
|
||||
**Einmal je Fassung, nicht bei jedem Start.** Weggetippt wird die gesehene Serverfassung in
|
||||
`localStorage` gemerkt; erst eine *andere* bringt das Blatt zurück. Ohne das wäre es binnen einer
|
||||
Woche unsichtbar geworden. Die Entscheidung steckt in der reinen Funktion `hinweisFaellig()` und ist
|
||||
mit sechs Fällen getestet (`Versionshinweis.test.ts`) — ein Fehler dort hat genau zwei Ausprägungen,
|
||||
beide schlecht: eine Meldung bei jedem Start, die niemand mehr liest, oder ein Update, von dem nie
|
||||
jemand erfährt.
|
||||
|
||||
**Geerbte Ungenauigkeit, bewusst nicht angefasst:** der Vergleich ist reine Gleichheit (siehe
|
||||
`VERSIONIERUNG.md`), also meldet das Blatt auch dann "neue Version", wenn die App *neuer* ist als das
|
||||
Backend — etwa direkt nach einem Xcode-Bau, bevor die Integration nachgezogen wurde. Der
|
||||
Hinweisstreifen hat dieselbe Eigenschaft seit jeher. Sortierbar zu vergleichen wäre scheingenau; die
|
||||
Alternative wäre ein zweites Signal vom Backend, und das lohnt für diesen Randfall nicht.
|
||||
|
||||
Verifiziert: `tsc --noEmit` sauber, 179/179 Tests (6 neue).
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { Route, SeitenName } from "./navigation"
|
||||
import { useTabBeschriftung, useTheme } from "./theme"
|
||||
import { Einrichtung } from "./screens/Einrichtung"
|
||||
import { Hinweisleiste } from "./screens/Hinweisleiste"
|
||||
import { Versionshinweis } from "./screens/Versionshinweis"
|
||||
import { SeiteFuer } from "./screens/register"
|
||||
import { zugangMerken } from "./screens/zugang"
|
||||
|
||||
@@ -141,6 +142,13 @@ function AngemeldeteApp({ beiAbmeldung }: { beiAbmeldung: () => void }) {
|
||||
setzeTabBeschriftung={setzeTabBeschriftung}
|
||||
beiAbmeldung={beiAbmeldung}
|
||||
/>
|
||||
{/* Erscheint von selbst, sobald der erste Datenabruf eine neuere
|
||||
Fassung meldet - siehe screens/Versionshinweis.tsx. */}
|
||||
<Versionshinweis
|
||||
versionsstand={daten.versionsstand}
|
||||
serverVersion={daten.serverVersion}
|
||||
otaBuendel={daten.otaBuendel}
|
||||
/>
|
||||
</Shell>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import { hinweisFaellig } from "./Versionshinweis"
|
||||
|
||||
/* hinweisFaellig() entscheidet, ob der Nutzer von einer neuen Fassung
|
||||
erfährt. Ein Fehler hier hat genau zwei Ausprägungen, beide schlecht: eine
|
||||
Meldung bei jedem Start, die niemand mehr liest, oder ein Update, von dem
|
||||
nie jemand erfährt. */
|
||||
describe("hinweisFaellig", () => {
|
||||
it("meldet, wenn App und Server auseinanderlaufen", () => {
|
||||
expect(hinweisFaellig("abweichend", "2026.9.3.3", null)).toBe(true)
|
||||
})
|
||||
|
||||
it("schweigt bei gleichem Stand", () => {
|
||||
expect(hinweisFaellig("gleich", "2026.9.3.3", null)).toBe(false)
|
||||
})
|
||||
|
||||
it("schweigt, solange kein Vergleich möglich ist", () => {
|
||||
// "unbekannt" heißt offline oder altes Backend - kein Grund für eine
|
||||
// Meldung, sonst erschiene sie bei jedem Start ohne Verbindung.
|
||||
expect(hinweisFaellig("unbekannt", null, null)).toBe(false)
|
||||
expect(hinweisFaellig("unbekannt", "2026.9.3.3", null)).toBe(false)
|
||||
})
|
||||
|
||||
it("schweigt ohne Serverfassung, auch wenn abweichend gemeldet wird", () => {
|
||||
expect(hinweisFaellig("abweichend", null, null)).toBe(false)
|
||||
})
|
||||
|
||||
it("schweigt zur bereits weggetippten Fassung", () => {
|
||||
expect(hinweisFaellig("abweichend", "2026.9.3.3", "2026.9.3.3")).toBe(false)
|
||||
})
|
||||
|
||||
it("meldet erneut, sobald eine andere Fassung bereitliegt", () => {
|
||||
// Der Kern der Merkfunktion: weggetippt gilt für genau eine Fassung,
|
||||
// nicht für "Updates" im Allgemeinen.
|
||||
expect(hinweisFaellig("abweichend", "2026.9.4.1", "2026.9.3.3")).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Meldung beim Öffnen, wenn eine neuere Fassung bereitliegt.
|
||||
*
|
||||
* Warum zusätzlich zur Hinweisleiste: die Leiste ist ein schmaler Streifen,
|
||||
* der dauerhaft mitläuft — richtig für einen Zustand, der bestehen bleibt,
|
||||
* aber leicht zu übersehen. Dass eine neue Fassung *neu* da ist, ist dagegen
|
||||
* ein Ereignis und gehört einmal nach vorn. Danach übernimmt wieder die
|
||||
* Leiste; die beiden ersetzen einander nicht.
|
||||
*
|
||||
* Nur in der nativen Hülle. Im Browser und im HA-Panel lädt jeder Aufruf den
|
||||
* aktuellen Stand, dort kann die App gar nicht veralten (dieselbe Begründung
|
||||
* wie in daten/appVersion.ts).
|
||||
*
|
||||
* ZWEI FÄLLE, ZWEI TEXTE
|
||||
* ----------------------
|
||||
* Liegt ein passendes OTA-Bündel bereit, kann die App sich selbst erneuern —
|
||||
* dann steht hier der Knopf, der genau das tut. Fehlt eines, hat sich Natives
|
||||
* geändert (Plugins, Berechtigungen, die Hülle selbst), und dagegen hilft nur
|
||||
* neu aufspielen. Beides zu vermengen wäre die schlechtere Auskunft: ein
|
||||
* "Jetzt aktualisieren", das nichts ändern kann, oder ein "bitte manuell",
|
||||
* obwohl ein Knopf genügt hätte.
|
||||
*
|
||||
* EINMAL, NICHT BEI JEDEM START
|
||||
* -----------------------------
|
||||
* Weggetippt wird die gesehene Serverfassung gemerkt. Erst eine *andere*
|
||||
* bringt die Meldung zurück — sonst wäre sie bei jedem Start dieselbe und
|
||||
* damit binnen einer Woche unsichtbar.
|
||||
*/
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import { ActionSheet } from "@audi-dash/ui"
|
||||
|
||||
import { zugangLesen, type Buendelangabe } from "../api"
|
||||
import { eigeneVersion, type Versionsstand } from "../daten/appVersion"
|
||||
import { OtaFehler, buendelAnwenden, buendelPasst, otaMoeglich } from "../daten/ota"
|
||||
|
||||
const SCHLUESSEL = "dm360.version.gesehen"
|
||||
|
||||
/**
|
||||
* Soll die Meldung erscheinen?
|
||||
*
|
||||
* Bewusst als reine Funktion: sie entscheidet, ob der Nutzer von einer neuen
|
||||
* Fassung erfährt oder nicht, und das soll prüfbar sein, ohne die Oberfläche
|
||||
* zu bauen.
|
||||
*/
|
||||
export function hinweisFaellig(
|
||||
versionsstand: Versionsstand,
|
||||
serverVersion: string | null,
|
||||
gesehen: string | null,
|
||||
): boolean {
|
||||
// "unbekannt" heißt: kein Vergleich möglich (offline, altes Backend). Das
|
||||
// ist kein Grund für eine Meldung — genauso wenig wie "gleich".
|
||||
if (versionsstand !== "abweichend") return false
|
||||
if (!serverVersion) return false
|
||||
return serverVersion !== gesehen
|
||||
}
|
||||
|
||||
function gesehenLesen(): string | null {
|
||||
try {
|
||||
return localStorage.getItem(SCHLUESSEL)
|
||||
} catch {
|
||||
// Privater Modus oder gesperrter Speicher: dann eben jedes Mal fragen.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function gesehenSchreiben(version: string): void {
|
||||
try {
|
||||
localStorage.setItem(SCHLUESSEL, version)
|
||||
} catch {
|
||||
/* siehe oben */
|
||||
}
|
||||
}
|
||||
|
||||
export function Versionshinweis({
|
||||
versionsstand,
|
||||
serverVersion,
|
||||
otaBuendel,
|
||||
}: {
|
||||
versionsstand: Versionsstand
|
||||
serverVersion: string | null
|
||||
otaBuendel: Buendelangabe | null
|
||||
}) {
|
||||
const [weggetippt, setzeWeggetippt] = useState(false)
|
||||
const [laeuft, setzeLaeuft] = useState(false)
|
||||
const [fehler, setzeFehler] = useState<string | null>(null)
|
||||
|
||||
const nativ = otaMoeglich()
|
||||
const offen =
|
||||
nativ && !weggetippt && hinweisFaellig(versionsstand, serverVersion, gesehenLesen())
|
||||
|
||||
const perOta = buendelPasst(otaBuendel, eigeneVersion())
|
||||
|
||||
const schliessen = () => {
|
||||
if (laeuft) return
|
||||
if (serverVersion) gesehenSchreiben(serverVersion)
|
||||
setzeWeggetippt(true)
|
||||
}
|
||||
|
||||
const jetztLaden = async () => {
|
||||
if (!otaBuendel) return
|
||||
setzeLaeuft(true)
|
||||
setzeFehler(null)
|
||||
try {
|
||||
const zugang = await zugangLesen()
|
||||
if (!zugang) throw new OtaFehler("Kein Zugang eingerichtet.")
|
||||
// Kehrt im Erfolgsfall nicht zurück — set() lädt die App neu.
|
||||
await buendelAnwenden(otaBuendel, zugang.basisUrl)
|
||||
} catch (f) {
|
||||
setzeFehler(f instanceof Error ? f.message : "Das Update konnte nicht installiert werden.")
|
||||
setzeLaeuft(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionSheet
|
||||
open={offen}
|
||||
onClose={schliessen}
|
||||
title="Neue Version verfügbar"
|
||||
description={
|
||||
perOta
|
||||
? `Diese App läuft auf ${eigeneVersion() ?? "einer unbekannten Fassung"}, bereit liegt ${serverVersion}. Sie kann sich direkt hier erneuern.`
|
||||
: `Diese App läuft auf ${eigeneVersion() ?? "einer unbekannten Fassung"}, der Server liefert ${serverVersion}. Diese Fassung ändert auch Natives — dafür muss die App neu aufgespielt werden, ein Update in der App genügt nicht.`
|
||||
}
|
||||
actions={
|
||||
perOta
|
||||
? [{ label: laeuft ? "Wird geladen …" : "Jetzt aktualisieren", onClick: () => void jetztLaden(), disabled: laeuft }]
|
||||
: []
|
||||
}
|
||||
cancelLabel={perOta ? "Später" : "Verstanden"}
|
||||
>
|
||||
{fehler ? <p className="dm-einrichtung__fehler">{fehler}</p> : null}
|
||||
</ActionSheet>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user