Zum Inhalt

Android-App – Technik & Entwicklung

Diese Referenz richtet sich an Betreiber und Entwickler. Die Android-Anleitung erklärt Einrichtung und Bedienung vom ersten Start bis zur Personenverwaltung. Stand: App 0.23.2 (versionCode 51), BearStack 1.15.3.

Bauen und installieren

Öffne apps/android/ in Android Studio als eigenes Projekt. Benötigt werden JDK 17 oder 21 und das Android-SDK mit Plattform 37. Gradle Wrapper, Android Gradle Plugin und Kotlin sind im Projekt festgelegt; verwende den mitgelieferten Wrapper. Setze den SDK-Pfad über ANDROID_HOME oder eine ignorierte apps/android/local.properties mit sdk.dir=….

Vom Repository-Stamm aus:

# JVM-Tests, Lint und Debug-APK
./scripts/check-android.sh

# Auf einem verbundenen Testgerät installieren
adb install -r apps/android/app/build/outputs/apk/debug/app-debug.apk

Für einen reinen Build:

apps/android/gradlew -p apps/android :app:assembleDebug

Die APKs werden lokal gebaut und nicht automatisch veröffentlicht. Go-, Web- und Docker-Builds benötigen kein Android-SDK; apps/android ist aus dem Docker-Buildkontext ausgeschlossen. Die App-ID lautet de.bearstack.people.

Privaten Release signieren

Updates derselben App-ID benötigen denselben Signierschlüssel. Lege einen dauerhaften Schlüssel außerhalb des Repositories an und sichere ihn separat. Passwörter werden interaktiv eingegeben:

keytool -genkeypair -v -keystore /sicherer/pfad/bearstack-android.jks \
  -alias bearstack -keyalg RSA -keysize 4096 -validity 10000

Der Release-Build verwendet diese Umgebungsvariablen:

Variable Inhalt
BEARSTACK_ANDROID_KEYSTORE Absoluter Pfad zur JKS-Datei.
BEARSTACK_ANDROID_KEY_ALIAS Alias, beispielsweise bearstack.
BEARSTACK_ANDROID_STORE_PASSWORD Passwort des Keystores.
BEARSTACK_ANDROID_KEY_PASSWORD Passwort des Schlüssels.

Passwörter beispielsweise mit read -rs einlesen und anschließend exportieren.

apps/android/gradlew -p apps/android :app:assembleRelease
adb install -r apps/android/app/build/outputs/apk/release/app-release.apk

Ohne Signiervariablen entsteht app-release-unsigned.apk zur Buildprüfung. Eine Debug-Installation hat einen anderen Schlüssel und muss vor dem ersten privaten Release deinstalliert werden; dabei gehen ihre lokalen Daten verloren. apps/android/VERSION und versionCode in app/build.gradle.kts werden für APK-Aktualisierungen gepflegt. Die Android-Version ist unabhängig von BearStack.

Verbindung und Zertifikat

Die App verwendet HTTPS einschließlich optionalem Reverse-Proxy-Pfad. HTTP und automatische Weiterleitungen sind ausgeschlossen; einzutragen ist die endgültige HTTPS-Adresse. Vor der Anmeldung werden Serverprotokoll und anschließend die verfügbaren Funktionen und Rechte geprüft.

Öffentlich vertrauenswürdige Zertifikate werden regulär geprüft. Bei einem selbstsignierten Blattzertifikat zeigt die App vor Übermittlung der Zugangsdaten den SHA-256-Fingerabdruck an. Ermittle ihn für die tatsächlich ausgelieferte Zertifikatsdatei:

openssl x509 -in /pfad/zum/server.crt -noout -fingerprint -sha256

Vergleiche alle Hexadezimalpaare. Abgeglichen und vertrauen bindet genau dieses Zertifikat an das Profil. Hostname und Gültigkeitszeitraum werden weiter geprüft. Eine IP-Adresse benötigt einen entsprechenden IP-Eintrag im Subject Alternative Name. Nach einem Zertifikatswechsel wird die Verbindung mit erneutem Abgleich eingerichtet. Private Ketten mit eigener CA werden nicht über diese Freigabe für selbstsignierte Blattzertifikate akzeptiert.

Es gibt ein aktives Profil. Zugangsdaten und bestätigtes Zertifikat werden mit AES-GCM und einem Schlüssel im Android Keystore in noBackupFilesDir gespeichert. Backups und Gerätetransfers sind ausgeschlossen. Screenshots, Bildschirmaufnahmen und die Vorschau im App-Umschalter sind möglich.

Debug-Builds protokollieren unter BearStackConnection nur Phase und Diagnosecode, keine vollständigen URLs, Exception-Texte, Benutzernamen oder Passwörter. Diagnosecodes und erste Prüfschritte stehen in der Anleitung.

API und Datenhaltung

Der verbindliche HTTP-Vertrag steht in openapi.yaml. Die App verwendet HTTP Basic über HTTPS; Galerie und Personenverwaltung teilen sich die Foto-Dienste und Zugriffsregeln des Browsers.

API-Bereich Aufgabe
/api/photos/v1/ Sitzung, Galerie, Datumssprung, Medieninformationen, Karten und Textbeiträge.
/api/photos/labeling/v1/ Personenlisten, Benennen, Zuordnen, Favoriten, Gruppenvergleiche und Aktionsquittungen.
/photos/faces/{id}/suggestions Gesichtsabgleich mit bereits benannten Personen.

Funktionen werden anhand von Serverfähigkeiten eingeblendet, etwa named_people, merge_suggestions, merge_side_actions, merge_naming, named_face_batch, person_folders und people_count_sort. Die Kompatibilitätstabelle nennt die zugehörigen Mindestversionen. Ältere Server behalten die jeweils unterstützten Bedienwege; fehlende Funktionen führen nicht zu einem Protokollwechsel.

Native Ordnerprüfung

GET /api/photos/labeling/v1/people/{id}/folders?page=1 liefert höchstens 40 exakte Ordner und acht Gesichter pro Ordner. Die App hält nur die aktuelle Seite. Anzeigen verwenden ausschließlich display_path; directory bleibt unverändert im Auftrag. Vorschauen enthalten die vorhandenen Original-Cachekennungen, Gesichtsrahmen und Prüfmarkierungen. Die bekannte Bildansicht nutzt dieselben geschützten Thumbnail-/Originalendpunkte und lädt Originale nur im begrenzten bestehenden Cache.

folder_move, folder_unnamed, folder_ignore, folder_exclude und folder_include verwenden den bestehenden people/{id}/actions-Endpunkt und die persistente Room-Auftragsablage. Die gemeinsame Serverimplementierung für Web und Android ändert den ganzen exakten Ordner atomar, ohne eine vollständige Gesichtsliste an den Client zu übertragen. Quittung und Änderung werden zusammen geschrieben; Wiederholungen prüfen die Quittung vor der inzwischen geänderten Quellrevision. Die Neuzuweisung an eine vorhandene Person prüft zusätzlich deren Revision. Die Personenliste fordert include_excluded=1 an, damit benannte Personen mit ausschließlich sichtbaren Sperren nach einem Neustart wieder erreichbar bleiben.

Aktionen, Revisionen und Wiederanlauf

Schreibaktionen tragen eine zufällige Aktions-ID, Datenbestandskennung und Quellrevision. Zuordnungen und Gruppenentscheidungen benötigen zusätzlich die Zielrevision; Gruppenvergleiche enthalten auch die Vorschlags-ID. Der Server speichert Änderung und Quittung in derselben SQLite-Transaktion. Quittungen sind kontogebunden; bestätigte Aktionen werden dadurch auch nach verlorenen Antworten nicht doppelt ausgeführt.

Web- und Hintergrundänderungen erhöhen dieselben Revisionen. Ein Konflikt verlangt eine neue Benutzerentscheidung. Offene Aktion prüfen liest zuerst die Quittung und sendet nur bei fehlender Quittung denselben gespeicherten Auftrag erneut. Die Foto-Datenbank einschließlich Quittungen gemeinsam sichern und wiederherstellen. Das Löschen der Gesichtserkennungsdaten erzeugt eine neue Datenbestandskennung und beendet die Gültigkeit der bisherigen Quittungen.

Room hält die aktuelle Gruppe und Bildseite, Durchgangsgrenze, übersprungene und abgetrennte Gruppen, offene Aktionen und bestätigte Statistikereignisse getrennt nach Instanz, Datenbestand und Konto. Das aktuelle Room-Schema 4 speichert die Warteschlangen als einzelne indizierte Einträge; Migrationen übernehmen vorhandene Reihenfolgen und offene Aktionen. Wiederherstellung liest höchstens 256 Einträge je Block, normales Blättern einzelne Einträge.

Rücknahmetimer existieren nur im Arbeitsspeicher. Noch nicht gesendete Ignorieraktionen kehren bei Hintergrundwechsel oder Neustart in die Warteschlange zurück. Ein offener Namens- oder Duplikatdialog verzögert das Senden abgelaufener Ignorieraktionen, ohne die Rücknahmefrist zu verlängern. Bereits möglicherweise gesendete Aktionen werden über ihre Quittung geklärt. Dies ist keine allgemeine Offline-Warteschlange.

Gesichtsabgleich

Die Lupe fordert NDJSON-Zwischenstände an; ältere passende Server können eine JSON-Antwort liefern. Es werden höchstens 20 Treffer und 64 KiB pro Zwischenstand verarbeitet. Im Speicher liegen nur die aktuelle Rangliste und der neueste wartende Zwischenstand. Ein fehlender Abschluss oder Übertragungsfehler verwirft vorläufige Treffer und erlaubt einen erneuten Versuch.

Beim Vergleich zweier unbenannter Gruppen laufen höchstens zwei Abgleiche nacheinander: zuerst für die erste Gruppe, nur bei leerem Endergebnis für die zweite. Fertige Ergebnisse gelten für das aktuelle Paar; Gruppenwechsel oder bestätigte Einzelaktionen verwerfen sie. Ein Hintergrundwechsel bricht laufende Suchen ab; unvollständige Ergebnisse werden beim Fortsetzen neu ermittelt. Die Zuordnung lädt nur die aktuelle Zielperson nach und verwendet die bestehenden Revisionsprüfungen.

Speicher und Ladeverhalten

Die Fotoframe-Einstellung frame_random wird lokal gespeichert. Die native Session meldet Unterstützung über frame_random_sort; fehlt die Fähigkeit, bleibt die bisherige Wiedergabe aktiv. Der Server verarbeitet sort=random über den bereits indexierten stabilen Zufallswert. Die Reihenfolge bleibt über Seitenwechsel und Wiederholungen hinweg gleich; es gibt keinen vollständigen Metadatenabruf.

Bei Gerätefotos wird für den gewählten Ordner einmal eine primitive LongArray mit Bild-IDs gelesen und gemischt (8 Byte pro Bild, etwa 0,8 MB für 100.000 Fotos). Metadaten werden anschließend nur für die jeweils bis zu 96 IDs einer Seite abgefragt. Das funktioniert auch bei Anbietern ohne native Offset-Pagination. Schließen, Verbindungswechsel und Wechsel der Reihenfolge geben die ID-Liste frei. Der nächste Fotoframe-Start mischt Gerätefotos neu.

Metadatengrenzen

Bereich Seitengröße und Begrenzung
Galerie 96 Medien, 24 Ordner und 20 Textzusammenfassungen je Seite; höchstens drei Seiten je Bereich.
Ordnervorschauen Bis zu vier je normalem Ordner, bei virtuellen Personenordnern bis zu acht.
Rückkehr aus dem Fotoframe Zusätzlich bleibt die vorherige Galerie innerhalb derselben Grenzen erhalten.
Lokale Fotos 96 Fotos beziehungsweise 24 Ordner je Seite; höchstens drei Seiten im Raster.
Personenliste Bis zu 20 Personen je Anfrage; Namenssuche nach 250 ms Eingabepause.
Gesichter einer benannten Person Bis zu 40 je Anfrage über einen Gesichts-ID-Cursor; ältere Server liefern Viererseiten.
Benennungsdurchgang Bis zu 20 Gruppen je Metadatenseite; Anzeige in Viererseiten.
Mehrfachauswahl Höchstens 500 Gesichter pro Aktion.

Entfernte Galerie-Metadaten werden beim Zurückscrollen erneut geladen. Ansichts- und Kontowechsel brechen überholte Anfragen ab. Der native Katalog ergänzt Identität, Inhaltsrevision und Prüfstatus gemeinsam für Medien und Ordnervorschauen in Abfragen mit höchstens 200 unterschiedlichen Pfaden. Normale Ordnerseiten mit Namenssortierung werden direkt in SQLite paginiert; komplexe Suchen, Datumssortierung und unvollständige Sichtbarkeitszähler behalten den bisherigen Pfad.

Der Datumssprung ermittelt Nachbartage über höchstens vier Bereichsabfragen und zählt die Einträge vor der Zielseite. Position und Ziel stammen aus derselben Lesetransaktion. Die App lädt danach die Zielseite, keine vollständige Medienliste.

Der Scrollgriff nutzt die vorhandenen Gesamtzahlen für Ordner und Medien. Ziehen erzeugt höchstens alle 100 ms einen Zielabruf; Loslassen übernimmt die letzte Position sofort. Ein Sprung fragt direkt die betreffende Seite ab, ohne Seiten dazwischen zu laden. Neuere Sprünge brechen ältere Abfragen ab und ignorieren verspätete Antworten. Bei Fehlern bleibt das bisherige Seitenfenster erhalten. Die bestehenden Grenzen von drei Seiten sowie die Mehrfachauswahl bleiben bestehen. Der unbegrenzte Fotostream bietet keinen Scrollgriff.

Lokale Ordner lesen nur Androids MediaStore. Je Ordner bleiben ein Zähler und höchstens zwei Vorschau-IDs erhalten. Abfragen laufen abbrechbar außerhalb des UI-Threads; Anbieter ohne native Seitengrenzen werden über Cursor gelesen. Reihenfolge ist die absteigende Medien-ID. Es gibt keine lokalen Uploads oder Serveranfragen für Gerätefotos.

Die Mehrfachauswahl speichert maximal 100 Medien unabhängig vom weiterhin auf drei Seiten begrenzten Metadatenfenster. ACTION_SEND_MULTIPLE verwendet temporäre Lesegrants und ClipData für jedes Medium. Serverdateien werden sequenziell vorbereitet; Dateien derselben Auswahl bleiben bei der Cachebereinigung geschützt. Datei- und Gesamtlimits gelten auch bei unbekannter oder falscher Größenangabe. Sammeldownloads erstellen neue Dokumente im über ACTION_OPEN_DOCUMENT_TREE freigegebenen Ordner und entfernen bei Fehlern nur die unvollständige Datei.

Löschen ist ausschließlich für validierte MediaStore-Bild-URIs des lokalen Anbieters implementiert. Android 11+ verwendet createDeleteRequest, Android 10 die Einzelzustimmung über RecoverableSecurityException, Android 8/9 eine erst beim Löschen angefragte Schreibberechtigung. Während der Systemzustimmung bleibt der lokale Controller bestehen; anschließend erfolgt der Bestandsabgleich. Der Serveranbieter und die HTTP-API erhalten keine Schreib- oder Löschmethode.

Beim Teilen und bei App-Wechseln bleiben der lokale Controller, das geöffnete Foto und die Rasterposition erhalten. Die Rückkehr prüft weiterhin die Berechtigung und liest den erreichbaren MediaStore-Bestand abbrechbar außerhalb des UI-Threads. Ein SHA-256-Fingerabdruck der gestreamten Metadaten erkennt auch geänderte Teilfreigaben bei gleicher Fotoanzahl und unveränderten Ordnervorschauen. Nur ein geänderter oder nicht mehr lesbarer Bestand ersetzt den Katalog. Der neue Katalog übernimmt das Abfrageergebnis ohne zweiten Scan; gespeichert bleiben weiterhin nur Ordnerzähler, zwei Vorschau-IDs je Ordner und der Fingerabdruck.

Großansicht und Systemleisten

Die Großansicht zeichnet die deckenden Menüflächen vor dem Einrücken ihrer Bedienelemente um die Android-Systemleisten. Oben werden nur obere und seitliche, unten nur untere und seitliche safeDrawing-Insets verwendet. Dadurch bleiben auch die Status- und Navigationsbereiche abgedeckt, einschließlich Querformat.

Bild- und Dateicaches

Cache Grenze und Lebensdauer
Server-Galerie-Thumbnails Einstellbar 64–2048 MiB, Standard 256 MiB; der geschützte Pflichtbestand darf das Budget überschreiten.
Große Vorschauen 16 MiB Arbeitsspeicher; höchstens drei Minuten ab erfolgreichem Laden, ohne Verlängerung bei Zugriff. Kein Disk-Cache.
Lokale Fotos 16 MiB Arbeitsspeicher; beim Verlassen der lokalen Ansicht, geändertem Medienbestand oder geänderter Zugriffsberechtigung geleert. App-Wechsel erhalten den Cache.
Vorbereitete Servermedien zum Teilen Höchstens 256 MiB je Datei sowie 100 Dateien und 512 MiB insgesamt; Bereinigung beim nächsten Teilen, auch für Dateien ab 24 Stunden.
OpenStreetMap-Kartenbilder 64 MiB HTTP-Cache; Cache-Header und bedingte Anfragen werden berücksichtigt.

Galerie-Thumbnails liegen privat in noBackupFilesDir, getrennt nach Server, Instanz, Datenbestand, Konto, Bildversion und Vorschaugröße. Die neuesten 50 ungefilterten Stream-Vorschauen und alle Vorschauen der ersten Ordnerebene sind geschützt. Beim Vorladen werden vorhandene Dateien nur anhand ihrer Metadaten geprüft; unveränderte Schutzlisten werden nicht erneut geschrieben. Die Nutzung beim Anzeigen bestimmt die Verdrängungsreihenfolge ungeschützter Einträge.

Große Gesichtsvorschauen verwenden den serverseitigen Wert large_preview_size (Standard 3840 Pixel längste Kante); die App dekodiert mit einem Ziel von 2048 Pixeln. Ältere Server ohne diesen Modus liefern über denselben Endpunkt das Original. Die optionale Kennung original_key erlaubt verschiedenen Gesichtern desselben Fotos, ein dekodiertes Bild gemeinsam zu verwenden. Rahmen und Zoom werden separat gezeichnet. Bei Speicherdruck können Bilder vor Ablauf der drei Minuten verdrängt werden.

Große Vorschauen werden nur über WLAN vorgeladen: in der Galerie höchstens die nächste Aufnahme, in Personenansichten die angezeigten Portraits nacheinander. Wechsel auf Mobilfunk oder in den Hintergrund bricht dies ab. Das eigenständige Vorladen der geschützten Galerie-Thumbnails darf dagegen mobile Daten nutzen. Ein ausdrücklicher Verbindungswechsel leert die Server-Bildcaches.

Zum Teilen werden Serveroriginale ohne Bilddekodierung mit einem 64-KiB-Puffer übertragen. Fehler und Abbruch entfernen angefangene Dateien. Die empfangende App bekommt vorübergehenden Zugriff über einen FileProvider; lokale Fotos werden über ihre MediaStore-URI ohne zusätzliche Kopie freigegeben.

Karten und Geometrien

Die App hält höchstens 289 Fotomarker je Ausschnitt, fordert bis zu 4.096 Fotoroutenkoordinaten an und zeigt bis zu 256 GPX-Tracks mit gemeinsam höchstens 8.192 Punkten. Die Trackliste hält zusätzlich zu ausgewählten Namen höchstens 96 Einträge. Zwei GPX-Geometrieanfragen laufen gleichzeitig; überholte Anfragen werden abgebrochen. Abschnitte bleiben getrennt, auch an der Datumsgrenze.

GPX-Dateien dürfen serverseitig höchstens 16 MiB und 100.000 Track-/Routenpunkte enthalten; der Parsercache ist auf 32 MiB begrenzt. Fehlende Indexdaten werden beim normalen Indexlauf ergänzt. Foto- und GPX-Routen verwenden gemeinsame Parser, Zugriffsprüfungen und Geometrie-Arbeitsplätze des Servers.

Der Servercache für vollständig gruppierte Fotorouten liegt unter <Cache-Verzeichnis>/photo-routes/v1/: höchstens 256 JSON-Dateien und 512 MiB, zuzüglich temporärer Dateien bei atomarem Ersatz. Ordner, Medientyp, Gruppierungsradius und Sichtbarkeit bestimmen den Schlüssel; Zoomstufen verwenden dieselbe Grundlage. Suchrouten werden berechnet, aber nicht dauerhaft gespeichert. Revisionen invalidieren betroffene Routen bei Änderungen; Zugriffsprüfungen bleiben vor jedem Abruf aktiv. Details beschreibt die Kartenreferenz. Kartenbilder lädt ein eigener Client ohne BearStack-Zugangsdaten.

Tests

Lint-Warnungen brechen den Build ab (warningsAsErrors), sowohl für Debug als auch Release. Mengenangaben verwenden deutsche und englische Pluralressourcen; Tests prüfen null, eins und mehrere Elemente. Das Galerieraster richtet sich nach der Fenstergröße; der Scrollgriff berechnet seinen Fortschritt als abgeleiteten Zustand.

Eng begrenzte, im Quelltext begründete Ausnahmen gelten für mengenunabhängige Beschriftungen, die Sprachwahl ab API 33 und den eigenen TLS-TrustManager. Dieser prüft Systemvertrauen beziehungsweise das ausdrücklich bestätigte, gültige Serverzertifikat; OkHttp prüft weiterhin den Hostnamen. TLS-Tests prüfen auch, dass ein geändertes Zertifikat vor dem Senden von Zugangsdaten abgewiesen wird.

compileSdk ist 37, targetSdk bleibt bewusst 36. Ein Zielwechsel auf API 37 benötigt zuerst den Berechtigungsablauf für lokale Server einschließlich Ablehnung und erneutem Versuch. Deshalb ist nur diese konkrete OldTargetApi-Meldung ausgenommen. Hintergrund ist die neue Android-17-Berechtigung für lokale Netzwerke.

Alle Befehle werden vom Repository-Stamm aus ausgeführt:

# JVM-Tests, Lint und Debug-APK
make test-android

# JVM-Tests, Lint und minimierte Release-APK (R8)
make test-android-release

# Mit gestartetem Emulator: Compose, Gesten, Room, Keystore und Caches
apps/android/gradlew -p apps/android :app:connectedDebugAndroidTest

# Mit genau einem Testemulator und adb im PATH: echter temporärer Go-HTTPS-Server
make test-android-integration

# Bedienung der minimierten App gegen einen temporären Go-Server
make test-android-release-integration

# Serverseitige Verträge, Rechte, Revisionen und Rollback
make test-go

Die Tests decken unter anderem Navigation und große Schrift, Vorschaugesten, Tastatureingabe während Rücknahmefristen, Seitenwechsel, verlorene Antworten, Konflikte, Neustarts, Statistik und die Grenzen der Bildcaches ab. Cachetests prüfen auch echte HTTPS-Anfragezahlen, unveränderte Dateien beim erneuten Vorladen und Reparatur gelöschter oder beschädigter Einträge.

Die HTTPS-Integration verwendet temporäre Daten, 127.0.0.1:18787 und adb reverse; sie greift auf keine installierte BearStack-Serverinstanz zu. Ohne Testadresse wird dieser zusätzliche instrumentierte Test übersprungen.

Der Release-Smoke benötigt Python 3, adb und einen dedizierten Emulator. Er setzt dessen App-Daten vor und nach dem Lauf zurück; physische Geräte werden abgewiesen. Der Bericht liegt unter app/build/reports/release-smoke.xml. Nur -Pbearstack.releaseSmoke=true erlaubt ohne privaten Keystore eine Signierung mit dem lokalen Debug-Schlüssel. Die normale Produktionssignierung bleibt davon getrennt.

Vor privater Verteilung die Bedienung zusätzlich auf dem eigenen Gerät prüfen, insbesondere TalkBack, Namensdialog/Tastatur und die Originalfoto-Vorschau.

Projektstruktur

apps/android/
  VERSION                 # eigenständige App-Version
  gradlew, gradlew.bat     # Build-Einstieg
  app/
    schemas/              # exportierte Room-Schemata
    src/main/java/de/bearstack/people/
      connection/         # Sitzung, TLS, Zertifikatsabgleich, Keystore
      data/local/         # Room-Zustand, Ereignisse, Statistik
      data/remote/        # JSON-Modelle und HTTP-Vertrag
      media/              # Bildcaches und Vorladen
      photos/             # Galerie, Gerätefotos, Karten und Wiedergabe
      people/             # Repository, ViewModel und Gestenregeln
      statistics/         # lokale Gesichts- und Gruppenzähler
      ui/                 # Compose-Oberfläche
    src/test/             # JVM-Tests
    src/androidTest/      # Geräte- und Integrationstests

connection/AppSession.kt verwaltet Anmeldung, Profil, HTTP-Client, Bildcaches und Galerie. PeopleViewModel verwaltet Personenwarteschlange und Aktionen. Die lokale Personendatenbank wird erst für eine Sitzung mit Personenrechten geöffnet. Ein Kontowechsel stoppt zuerst die Feature-Aufgaben und schließt dann die Sitzungsressourcen. Die Personenlogik liegt im Go-Backend unter internal/photos, HTTP-Adapter und Rechte unter internal/server.

Für die Buildpflege: Gradle 9.8.0 und AGP 9.4.1 verwenden Kotlin 2.4.20; der explizite KGP-Classpath hält AGPs integrierten Compiler und das Compose-Plugin auf demselben Stand. KSP 2.3.12 verarbeitet den Room-Compiler. Das exportierte Datenbankschema bleibt unverändert. room-ktx und ui-tooling-preview sind keine zusätzlichen Abhängigkeiten. Release-Builds verkleinern Code und Ressourcen gemeinsam. Compose-Gerätetests verwenden die aktuelle v2-Testregel. Verbindlich sind die Gradle-Dateien im Repository.

OptionsMenu stellt den gemeinsamen Menüanker bereit. PhotosMenu unterscheidet nicht anwendbare Aktionen (ausgeblendet) von vorübergehend gesperrten Aktionen (deaktiviert). Beim Neuladen desselben Ordners bleibt dessen bekannter Personenlink im Controller erhalten; bei einem Ordnerwechsel wird er verworfen. Es entstehen keine zusätzlichen Netzabfragen für Menüs.

PeopleNavigation wechselt exklusiv zwischen Benennen, Personenliste und Gruppenvergleich und beendet nicht mehr benötigte Suchaufgaben. Detailansichten verwenden einen Rückpfeil. Android-Zurück beendet zuerst eine Gesichtsauswahl; der Rückpfeil verlässt die Person und verwirft die Auswahl. Die Statistik bietet keine Aktionen für eine im Hintergrund liegende Person.

AppSettingsDialog wird von Galerie und Personenansichten verwendet. Öffnen und Abbrechen verändern die Sitzung nicht. Erst Abmelden im Bestätigungsdialog führt switchConnection() aus, entfernt die gespeicherte Verbindung und leert den Thumbnail-Cache. Auch Fehlerdialoge öffnen diese Einstellungen statt unmittelbar abzumelden. HTTP-Verträge, Serverberechtigungen und Originaldateien bleiben unverändert.

Zustände der Personenverwaltung

Ab Android 0.22.4 hält PeopleState.kt den gemeinsamen Verbindungs- und Schreibstatus sowie getrennte Daten für Personenliste, Gruppenvergleich und Ordnerprüfung. PeopleRoute legt die aktive Ansicht eindeutig fest; bei der Ordnerprüfung speichert es ausdrücklich die Herkunft aus Benennen oder Personenliste. Servergalerie, Gerätefotos und Personenverwaltung verwenden AppSection statt überlappender Anzeige-Flags.

PersonFolderController übernimmt Laden, Bestätigen, Wiederholen und Verlassen der Ordnerprüfung. PeopleViewModel koordiniert weiterhin Sitzung, den einzelnen Schreibzugriff und dauerhafte Aktionsquittungen. Dadurch bleiben ausstehende Änderungen über Bereichswechsel hinweg gesperrt und wiederholbar. Der abgeschlossene Foto-Abnahmeplan vom September 2026 ist ein historisches Dokument und keine aktuelle technische Referenz.

Bildgruppen und gezielter Ordnersprung

Ab App 0.23.0 wertet die Sitzung image_groups und folder_position aus; fehlende Felder deaktivieren die jeweiligen Aktionen. Gruppen verwenden die bestehenden JSON-Antworten von /photos/image-groups und /photos/image-groups/{id} mit Formular-POST, Basic-Authentifizierung, aktuellem Revisionswert und Accept: application/json. Der konfigurierte Reverse-Proxy-Präfix bleibt erhalten. Die Serverberechtigung photos.edit gilt weiterhin für alle Schreibaktionen. Die Auswahl ist auf 100 Medien beschränkt; Gruppen dürfen serverseitig 500 Mitglieder haben.

GET /api/photos/v1/browse/position?path=… liefert die Seite des sichtbaren Mediums in seinem physischen Ordner (descending_date, 96 Medien pro Seite). Die Ermittlung verwendet eine SQLite-Lesetransaktion und eine indizierte Zählung im Zielordner. Es werden keine vorhergehenden Seiten oder gesamten Medienlisten an Android übertragen. Vor und nach der Abfrage wird die Dateizugriffsberechtigung geprüft; geschützte und in Gruppen ausgeblendete Medien werden nicht lokalisiert. Android validiert das Ziel anhand der geladenen Seite und verwirft überholte Antworten nach Navigation. display_path und folder_name stammen aus der zentralen Serverformatierung; technische Pfade bleiben Navigationsschlüssel.

Bildpipeline ab Android 0.23.1

Coil 3.6.3 verwendet explizit coil-network-okhttp und coil-network-cache-control. networkClient registriert den bestehenden OkHttp-Client samt Authentifizierung, Zertifikatsprüfung und Verbindungspool. Karten behalten ihren eigenen Client ohne BearStack-Zugangsdaten. Der eigene Thumbnail-Fetcher verwendet Coil-3-Quellen; beschädigte Decodierungen entfernen weiterhin den betroffenen Cache-Eintrag. Die Originalvorschauen behalten ihren auf 16 MiB begrenzten Speicher und die feste Lebensdauer von drei Minuten. Die Migration ändert weder den Thumbnail-Cache auf Disk noch die Originaldateien.

Vorschau bei der Gruppierung ab Android 0.23.2

Die Hauptbildauswahl rendert sichtbare Vorschauen in einer begrenzten LazyColumn über den bestehenden authentifizierten ImageLoader und Thumbnail-Cache. Die Großansicht verwendet denselben Viewer mit Zoom und einer auf die ausgewählten Bilder begrenzten Navigation. Die Hauptbildwahl ändert nur den Dialogzustand; erst Speichern führt den bestehenden Gruppenaufruf aus. Auch beim Hinzufügen zu einer Gruppe können die ausgewählten Bilder angesehen werden. Das vorhandene Hauptbild bleibt dabei erhalten. HTTP-Verträge und Originaldateien ändern sich nicht.

Regressionen prüfen das tatsächliche Decodieren beider Vorschauen, die Hauptbildwahl nach dem Blättern, das Schließen ohne Auswahländerung und Abbrechen ohne Schreiben. Der Weg über die Galerie-Auswahlleiste und die Gruppenerweiterung sind ebenfalls abgedeckt. 126 JVM-Tests, 38 Emulatorfälle, Debug-Build und Lint bestehen.