netdiag-app/src/routes/+layout.svelte
Eduard Wisch 34356f25ef
All checks were successful
Build APK / build-apk (push) Successful in 1m42s
Updater: APK direkt in App herunterladen und installieren [apk]
Ersetzt den Browser-Umweg (window.open) durch einen echten In-App-Installer:
das native Plugin lädt die APK streamend herunter (Fortschritts-Events
updateProgress 0–100 %), prüft die Installationsberechtigung (Android 8+)
und öffnet den Paketinstaller über den vorhandenen FileProvider.

Versionsvergleich jetzt numerisch (YYYYMMDD-HHMM) statt lexikografisch.
Banner ist schließbar; Einstellungsseite zeigt separaten Fortschrittsbalken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:31:42 +02:00

125 lines
3.8 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import '../app.css';
import { onMount, onDestroy } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { auth } from '$lib/auth.svelte';
import { sync } from '$lib/sync.svelte';
import { toast } from '$lib/toast.svelte';
import { initDb } from '$lib/db';
import { registerBackListener, removeBackListener } from '$lib/backButton.svelte';
import { checkForUpdate, installUpdate, type UpdateInfo } from '$lib/updater';
import { initDebugLog } from '$lib/debuglog.svelte';
import Toast from '$lib/components/Toast.svelte';
let { children } = $props();
let booted = $state(false);
let updateInfo = $state<UpdateInfo | null>(null);
let updateDismissed = $state(false);
let updateBusy = $state(false);
let updatePercent = $state(0);
const HOME = '/auftraege/';
// Update herunterladen und Installer öffnen — Fortschritt im Banner
async function runUpdate() {
if (!updateInfo || updateBusy) return;
updateBusy = true;
updatePercent = 0;
try {
await installUpdate(updateInfo, (p) => (updatePercent = p));
// Erfolg: Android übernimmt die Installation, Banner bleibt bei 100 %
} catch (e) {
toast.show(e instanceof Error ? e.message : 'Update fehlgeschlagen', 'error', 6000);
updateBusy = false;
}
}
onMount(async () => {
initDebugLog(); // zuerst — damit auch Startfehler erfasst werden
await auth.init();
await initDb();
if (auth.loggedIn) await sync.start();
// Hardware-Backbutton (Modul-Scope, Single-Instance — KB #480/#549)
registerBackListener({
handleOverlay: () => false,
isHomeRoute: () => {
const p = $page.url.pathname;
return p === HOME || p === '/' || p === '/login/';
},
goBack: () => history.back(),
showExitHint: () => toast.show('Nochmal drücken zum Beenden'),
});
// Auf neue APK prüfen — beim Start still (kein Toast), nur Banner bei Erfolg
try {
updateInfo = await checkForUpdate();
} catch (e) {
console.warn('Update-Prüfung beim Start fehlgeschlagen:', e);
}
booted = true;
});
onDestroy(() => {
removeBackListener();
sync.stop();
});
// Auth-Gate: nicht angemeldet -> Login
$effect(() => {
if (!booted) return;
const path = $page.url.pathname;
const onLogin = path.startsWith('/login');
if (!auth.loggedIn && !onLogin) {
goto('/login/');
} else if (auth.loggedIn && onLogin) {
goto(HOME);
}
});
</script>
<div class="flex min-h-screen flex-col">
{#if !booted}
<div class="flex flex-1 items-center justify-center text-zinc-500">
<span>NetDiag startet …</span>
</div>
{:else}
{#if updateInfo && !updateDismissed}
<div class="bg-sky-700 text-sm text-white safe-top">
{#if updateBusy}
<div class="px-4 pb-2">
<div class="flex justify-between">
<span>Update wird geladen …</span>
<span class="font-mono">{updatePercent}%</span>
</div>
<div class="mt-1 h-1.5 overflow-hidden rounded bg-sky-900">
<div
class="h-full rounded bg-white transition-all"
style="width:{updatePercent}%"
></div>
</div>
</div>
{:else}
<div class="flex items-center">
<button class="flex-1 px-4 pb-2 text-left" onclick={runUpdate}>
Neue Version {updateInfo.version} verfügbar — tippen zum Aktualisieren
</button>
<button
class="px-4 pb-2 text-lg leading-none"
aria-label="Hinweis ausblenden"
onclick={() => (updateDismissed = true)}
>
×
</button>
</div>
{/if}
</div>
{/if}
{@render children()}
{/if}
</div>
<Toast />