- JavaScript 61.5%
- Kotlin 21.7%
- HTML 10.7%
- CSS 4.9%
- Python 1.2%
| app | ||
| Archive | ||
| gradle | ||
| server | ||
| tests | ||
| web | ||
| .gitignore | ||
| build.gradle.kts | ||
| COPYRIGHT | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| icon-fdroid.png | ||
| LICENSE | ||
| make-icon.py | ||
| README.md | ||
| settings.gradle.kts | ||
Ausstellungen
Was man bei einem Ausstellungsbesuch gesehen hat, festgehalten während man davor steht.
Das Problem: Bilder, Saaltexte und Notizen liefen bisher über einen XMPP-Bot in
den consume-Ordner von paperless-ng, wurden dort pro Künstler zu PDFs gebaut
und von Hand zusammengeführt. Eine flache Dokumentablage passt aber nicht auf
Daten, die eine echte Verschachtelung haben: Ausstellung → KünstlerIn → Werk →
Bild + Werkkarte + Notiz.
Erfasst wird deshalb direkt in der App, und zwar vollständig offline — im Saal gibt es kein Netz. Die Bilder liegen als echte Dateien im App-Speicher, die Angaben daneben in einer JSON-Datei.
Was sie kann
- Werk aufnehmen mit einem Griff — die Aufnahmeknöpfe stehen fest im Kopf und scrollen nie weg, auch nicht bei fünfzig erfassten Werken.
- Zu jedem Werk beliebig viele Bilder und getrennt davon Bilder der Werkkarte (Titel, Material, Jahr). Getrennt, weil die Karte Datenmaterial ist und kein Bild des Werks — im Archiv steht sie später daneben, und sie ließe sich ohne Umbau per Texterkennung auswerten.
- Einzel- oder Gruppenausstellung, beim Anlegen gewählt und später änderbar. Bei einer Einzelausstellung verschwindet die KünstlerInnen-Ebene aus der Oberfläche; in den Daten bleibt sie stehen, damit das Archiv überall dieselbe Form sieht. Zurück zu „einzel" geht nur, solange höchstens eine Person Werke hat — sonst würde das Umschalten stillschweigend fremde Werke zusammenwerfen.
- In der Gruppenausstellung merkt sich die App, für wen gerade erfasst wird. Man geht die Werke einer Position durch, ohne jedes Mal den Namen zu wählen, und schaltet erst am nächsten Saal um. Zugeordnet wird immer ausdrücklich per Knopf, nie geraten.
- Text am Eingang und Saaltext je KünstlerIn, jeweils getippt oder abfotografiert.
- Jedes Bild im Vollbild (zwei Finger oder Doppeltipp zum Zoomen, und zwar um den Punkt unter den Fingern) mit eigener Notiz.
- Jedes Bild trägt seine Aufnahmezeit aus dem EXIF, nicht den Zeitpunkt der Übernahme — ein Bild aus der Galerie kann vom Besuch letzte Woche sein.
- Suche über Titel, Ort, Namen der KünstlerInnen, Werktitel und alle Notizen.
- Sicherung als Zip aus allen Angaben und allen Bildern in Originalgröße.
- Löschen lässt sich auf jeder Ebene zurücknehmen.
Das Archiv
Seit v1.1.0 gibt die App ihr Notizbuch ab: unter Menü → Archiv stehen Adresse und Token, danach hat jede Ausstellung in den Angaben eine Zeile mit dem Knopf Zum Archiv hochladen.
Die Gegenstelle ist seit v1.2.0 das gemeinsame Archiv aller Foto-plus-Notizen-
Apps und liegt im Repo thee/Archive unter server/; Ausstellungen ist dort der
Bereich ausstellungen. Was das für diese App bedeutet, steht in
server/UMGEZOGEN.md — kurz: die Adresse in den
Einstellungen ist die des ganzen Archivs, den Bereich hängt die App an. Server
und App v1.2.0 gehören zusammen ausgerollt, die alten Pfade gibt es nicht mehr.
Seit v1.3.0 steht der Weg dorthin im Kopf der Liste: sobald etwas offen ist, erscheint dort „N Ausstellungen noch nicht im Archiv" mit dem Knopf Alles hochladen, der sie nacheinander abarbeitet. Vorher lag die Übergabe nur in der Zeile Archiv innerhalb der Angaben — und die klappen zu, sobald eine Ausstellung Werke hat, also genau dann, wenn es etwas hochzuladen gibt. Der einzelne Knopf in den Angaben bleibt für den gezielten Fall.
Vier Zustände sind unterscheidbar, und der vierte ist der, auf den es ankommt: noch nicht übergeben, unvollständig übergeben, im Archiv (Fassung n) und im Archiv, seither geändert — sonst wüsste man nach dem zweiten Besuchstag nicht, dass noch etwas offen ist. In der Liste steht dasselbe als kleine Marke.
Hochgeladen wird per Vorgabe nur im WLAN; eine Gruppenausstellung sind schnell dreihundert Originale. Ist gerade keins da, fragt das Band unten, ob es über Mobilfunk gehen soll. Der Vorgang ist abbrechbar und fortsetzbar: nach einem Abbruch fängt der nächste Versuch wieder von vorn an, die Fehlliste ist dann einfach kürzer.
Das Telefon ist das Notizbuch, das Archiv die Reinschrift. Hochgeladen wird in eine Richtung; wurde im Archiv schon gearbeitet, weist es den Upload ab, statt fremde Arbeit zu überschreiben.
Was noch nicht
Die Weboberfläche des Archivs (Liste, Ausstellung, Strecke, Werk, Bearbeiten, Suche) steht noch aus. Bis dahin holt das Hochladen die Bilder immerhin schon vom Telefon herunter und legt sie sicher ab.
Alte Ausstellungen aus paperless-ng werden nicht übernommen.
Aufbau
web/ die ganze App (app.html + app.js), im Browser testbar
app/ was an Kotlin übrig ist — 39 Zeilen
Archive/ Submodul thee/Archive: die gemeinsame Schale und der Archivserver
server/ nur noch ein Wegweiser — das Archiv liegt in thee/Archive
tests/ Rauchtest (Playwright)
make-icon.py zeichnet die Launcher-Icons
Die Schale ist seit v1.4.0 nicht mehr Teil dieser App. WebView, Kamera,
Galerie, Vorschaubilder, Aufräumer, Sicherung, Thema und die Übergabe ans
Archiv stehen in Archive/schale und gehören allen Apps des Verbunds; hier
bleiben Dateiname, zwei Farben und der Bereich im Archiv. Nach einem frischen
Klon deshalb einmal:
git submodule update --init
Die Seite unter web/ ist die einzige Quelle; Gradle kopiert sie vor jedem Build
in die Assets — app/src/main/assets nie von Hand pflegen. Geladen wird über den
WebViewAssetLoader als https-Ursprung, weil darüber auch die Bilder aus
filesDir/bilder ausgeliefert werden.
Die Seite hält den gesamten Zustand. Die Schale kann nur, was eine Web-Seite nicht kann: Datei lesen und schreiben, Bild aufnehmen oder aus der Galerie holen, Sicherung teilen und einlesen, und melden ob hell oder dunkel gilt.
Bildplätze werden als Text benannt (w:<werk>:b, w:<werk>:k,
a:<ausstellung>, k:<künstler>). Die Schale reicht diesen Text unverändert
zurück und muss ihn nicht verstehen — ein neuer Bildplatz kommt so ohne Änderung
am Kotlin-Teil aus.
Der Aufräumer läuft rekursiv über den ganzen JSON-Baum und sammelt jedes
datei und thumb, das er findet, statt die bekannten Bildlisten aufzuzählen.
Eine Aufzählung, die einen Platz vergisst, löscht dessen Bilder beim nächsten
Speichern still weg — in der Motive-App ist genau das schon einmal passiert.
Datenmodell
filesDir/ausstellungen.json, atomar über .neu + rename geschrieben.
{ id, art: 'einzel'|'gruppe', titel, ort, url, besucht, notiz,
einfuehrung: { text, bilder[] },
kuenstler: [ { id, name, notiz, einfuehrung: { text, bilder[] } } ],
werke: [ { id, kuenstlerId, titel, notiz, bilder[], karten[] } ],
aktuell, angelegt, geaendert, hochgeladen }
bild = { datei, thumb, zeit, notiz }
werke[] liegt bewusst flach statt in den KünstlerInnen verschachtelt; die
Gruppierung wird beim Zeichnen über kuenstlerId gerechnet. Das hält Zählen,
Umhängen, Löschen und den Aufräum-Scan einfach.
Prüfen
node tests/rauchtest.mjs
99 Prüfungen gegen eine Attrappe der Brücke. Sie bildet zwei Dinge genau nach:
den rekursiven Aufräum-Scan der Schale, damit auffällt, wenn ein Bildplatz beim
Sammeln übersehen wird — und den ganzen Weg des Archivs (Angaben hoch,
Fehlliste, Bilder einzeln, fertig), damit auffällt, wenn die Seite einen der
Rückwege nicht bedient.
Die Gegenprobe läuft auf der anderen Seite: test_telefondaten.py im Repo
thee/Archive fährt eine echte Übergabe mit dem Endstand eines Rauchtestlaufs,
also mit Daten, die wirklich die App erzeugt hat. Auffrischen mit
AUS_DATEN_SCHREIBEN=1 node tests/rauchtest.mjs — das schreibt das Prüfstück in
../Archive/server/tests/, das Nachbarrepo muss also daneben liegen.
Der Alt-WebView-Check läuft statisch. Der Emulator beta30 (Android 11,
WebView 83) verlangt eine angemeldete Desktop-Sitzung, und die macht die
Bedienung der VM zäh, solange sie läuft — für eine Routineprüfung ist das zu
teuer. Geprüft wird deshalb per Grep auf die drei Fallen dieser Altersklasse:
kein inset-Kurzschreiben, keine Syntax jenseits ES2017, und hell/dunkel
rechnet die Schale aus, statt prefers-color-scheme zu fragen. Die Bedienung
wird am Gerät geprüft.
Für eine konkrete Frage, die nur der Emulator beantwortet, lohnt die Sitzung trotzdem — dann aber vorbereitet: APK gebaut, Instrumentierungstest geschrieben, Prüfliste fertig, und hinterher Bescheid sagen, dass sie wieder zu kann.
Bauen
./gradlew assembleRelease