- JavaScript 70.3%
- Kotlin 13.8%
- CSS 11.7%
- HTML 2.4%
- Python 1.8%
| app | ||
| Archive | ||
| gradle | ||
| tests | ||
| web | ||
| .gitignore | ||
| build.gradle.kts | ||
| COPYRIGHT | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| icon-fdroid.png | ||
| LICENSE | ||
| make-icon.py | ||
| README.md | ||
| settings.gradle.kts | ||
Wein
Welche Flaschen mir geschmeckt haben — und wo ich sie gekauft habe.
Kein Verkostungstagebuch, keine Punkte, kein OCR. Ein Eintrag ist eine Flasche, und die Maske sagt in zwei Blöcken, worum es geht:
Was der Wein ist — Name (was auf der Flasche steht), Anbaugebiet, Rebsorten, Jahrgang, Weingut. Wo er herkommt — Laden, dessen Adresse, Preis. Dazu die beiden Etiketten und ein, zwei Zeilen Notiz. Der Zweck ist das Nachkaufen.
Angesehen wird zuerst, bearbeitet auf Wunsch. Ein Griff in die Liste öffnet eine Ansicht, die nur zeigt — kein Feld, keine Tastatur, ein Etikett antippen zoomt es auf. Erst „Bearbeiten" legt die Maske darüber. Eine Flasche wird viel öfter nachgeschlagen als geändert, und wer im Laden davorsteht, will sie nicht aus Versehen umschreiben.
Zwei Dinge, die daraus folgen: Rebsorten sind eine Liste (eine Cuvée ist der
Normalfall, kein Sonderfall), und jede Sorte wird unter allen ihren Namen
gefunden — wer „Pinot Noir" sucht, meint auch seinen Spätburgunder
(web/rebsorten.js, 48 Gruppen). Die Ansicht sagt es auch dazu — „Auch bekannt
als Muscadet", damit niemand eine Traube als zwei Sorten einträgt. Dieselbe
Liste führt das Archiv in Python; node tests/rebsorten-gleich.mjs vergleicht
beide, sie liegen hier im selben Arbeitsbaum.
Daraus folgt das Einzige, was hier technisch zählt: die Etikettfotos wandern
in voller Auflösung ins Archiv. Das Kleingedruckte auf dem Rückenetikett
(Erzeuger, Abfüller, Anbaugebiet) liest man nicht am Telefon ab, sondern später
am großen Bildschirm — dort steht es zoombar im Archiv unter /b/wein.
Woraus die App besteht
app/src/main/java/com/thee/wein/MainActivity.kt |
36 Zeilen — Dateiname, zwei Farben, der Bereich im Archiv |
Archive/schale/ (Submodul) |
WebView, Kamera, Galerie, Vorschaubilder, Aufräumer, Sicherung, Thema, Übergabe ans Archiv |
Archive/web/ (Submodul) |
Blätter, Band, Betrachter, Vorschlagsfeld, Thema, grund.css |
web/app.js, app.css, app.html |
das Fachliche: Flaschen, Etiketten, Läden |
web/rebsorten.js |
Rebsorten unter ihren verschiedenen Namen — eine Suchhilfe, keine Autorität |
web/sortenwiki.js |
Rebsorte → Wikipedia-Artikel (1638 Namen). Nicht von Hand gepflegt, nachziehen mit python3 tests/sortenwiki-bauen.py |
web/ort.js |
Punkt als Text im Eintrag (49.92360,7.06980) — eigenes Modul, damit es gegen die Python-Fassung laufen kann |
Diese App ist die erste, die von Anfang an auf beiden Schalen steht, statt darauf umgezogen zu sein — Ausstellungen und Motive sind Bestand, hier ist der Preis der Schale ohne Umzugskosten zu sehen.
Nach einem frischen Klon einmal:
git submodule update --init
Bauen und prüfen
node tests/probe.mjs # die Seite in Chromium, 157 Prüfungen
node tests/rebsorten-gleich.mjs # dieselbe Sortenliste in App und Archiv?
node tests/farben-gleich.mjs # dieselben sechs Kartentöne?
node tests/sortenwiki-gleich.mjs # dieselbe Wikipedia-Tabelle?
node tests/ort-gleich.mjs # rechnen App und Archiv den Punkt gleich?
./gradlew :app:assembleRelease
Die vier *-gleich.mjs sind Wächter über die Sprachgrenze: App und Archiv
führen dieselbe Liste bzw. dieselbe Rechnung zweimal, in JavaScript und in
Python. Hier ist die einzige Stelle, an der beide Fassungen im selben
Arbeitsbaum liegen (das Archiv ist Submodul) — also wird hier verglichen, und
zwar gegen die echten Fassungen, nicht gegen einen dritten Nachbau. Am
teuersten wäre das Auseinanderlaufen beim Runden: Python rundet die Hälfte zur
geraden Zahl, JavaScript nach oben, und ein Meter Unterschied im Punkt hieße,
dass die Flasche beim nächsten Hochladen als geändert gilt.
Ein Wächter der Probe zielt auf die Naht zwischen Seite und Schale: ein
Verweis geht nicht an den WebView, sondern über shouldOverrideUrlLoading an
oeffneExtern in der Schale, und was dort nicht auf der Liste NACH_DRAUSSEN
steht, fällt lautlos weg. So war die Anschrift des Weinguts bis v1.15.1 tot
(geo: fehlte) — und die Ladenadresse seit v1.7.0 gleich mit, ohne dass ein
Test etwas gemerkt hätte: alle prüften, dass ein Verweis dasteht, keiner, ob
ihn jemand aufmacht. Die Probe liest jetzt die echte Kotlin-Datei und vergleicht
sie mit den Schemata, die die Ansicht wirklich baut.
Der Rauchtest ist hier nicht Beiwerk: die Seite besteht fast nur aus Aufrufen an
die Web-Schale, und ein falscher Modulweg zeigt in einer WebView eine weiße
Seite und sonst nichts. Er fährt dieselbe Ablage wie die APK (Seite oben,
Schale unter schale/) und ruft window.onBild von Hand — genau so, wie es die
native Schale täte.
Was er nicht kann: sagen, ob sich die Bedienung gut anfühlt und ob das Rückenetikett am großen Bildschirm wirklich lesbar ist. Beides sieht der Mensch.
Was in der APK landet
Die eigene Seite und der ganze Ordner Archive/web (ohne tests/,
probe.html, README.md). Ausgeschlossen wird, was nicht mit soll — nicht
aufgezählt, was mit soll: eine Namensliste vergisst irgendwann eine neue .js,
und der Bau bliebe grün.
Die Karte
Der Knopf ◍ im Kopf zeigt, wo die Flaschen herkommen; in der Ansicht einer
Flasche führt auch die Gegend selbst dorthin — die Karte steuert sie dann an
und beschriftet sie. Steht die Gegend nicht im Verzeichnis, bleibt sie Text: ein
Knopf, der eine leere Karte aufmacht, wäre schlimmer als keiner. Die Namen der
Anbaugebiete liegen in der App (web/gebiete.js, 1520 Stück aus den amtlichen
Verzeichnissen) — getippt wird im Laden, oft ohne Netz. Die Flächen holt die
Karte beim Archiv; sie sind vier Megabyte und ändern sich ein-, zweimal im Jahr.
Ohne eingerichtetes Archiv gibt es deshalb keine Karte, und sie sagt das.
Zugeordnet wird über den längsten passenden Namen: „Muscadet Sèvre et Maine sur lie" ist Muscadet Sèvre et Maine — „sur lie" ist der Ausbau. Nachziehen der Namensliste aus dem Archiv:
python3 tests/gebiete-bauen.py
Das Archiv
Adresse und Token stehen im Menü unter „Archiv". Hochgeladen wird in eine Richtung: das Telefon ist das Notizbuch, das Archiv die Reinschrift. Wurde eine Flasche im Archiv geändert, sagt die Liste das und der Upload wird abgelehnt, statt fremde Arbeit zu überschreiben.
Und es gibt einen Weg daran vorbei. Am Blatt steht dann „Trotzdem
übergeben"; nach einer Rückfrage gewinnt die Fassung vom Telefon, und die
bisherige legt der Server nach .abgelegt/ neben den Eintrag. Eine Sperre ohne
Ausweg wäre eine Sackgasse: getippt wird am Telefon, und „arbeite im Archiv
weiter" hilft nicht, wenn dort das Feld gar nicht steht.
Der Rückweg für ganze Flaschen steht im Menü unter „Flaschen holen" (und im Archivblatt daneben). Er zeigt, was im Archiv liegt und hier fehlt, und holt es zurück — einzeln oder alle auf einmal, samt Etiketten. Gedacht ist er für das neue Telefon: dort fehlt jede Flasche, und jede davon einzeln zu bestätigen wäre kein Rückweg. Der andere Weg daneben („Aus dem Archiv holen" an einer Flasche) gleicht nur Texte ab, an einer Flasche, die es hier noch gibt.
Der Bereich wein liegt im Archiv-Server (Repo Archive). Auf der Platte sieht
der Baum so aus: wein/<weingut>/<jahrgang>-<name>/.
Verteilt
Über ein eigenes F-Droid-Repo, Paket com.thee.wein.