No description
  • JavaScript 70.3%
  • Kotlin 13.8%
  • CSS 11.7%
  • HTML 2.4%
  • Python 1.8%
Find a file
2026-10-25 13:04:52 +01:00
app Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
Archive Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
gradle Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
tests Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
web Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
.gitignore Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
build.gradle.kts Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
COPYRIGHT Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
gradle.properties Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
gradlew Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
gradlew.bat Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
icon-fdroid.png Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
LICENSE Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
make-icon.py Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
README.md Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00
settings.gradle.kts Stand vom 2026-10-25 2026-10-25 13:04:52 +01:00

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.