goon/mobile
goon-foss 031c309091 fix(mobile): stary APK po OTA podawal sie za nowa wersje i nie dostawal monitu
Zgloszone przez uzytkownika: po wydaniu 0.3.0 nie dostal propozycji aktualizacji.

Przyczyna jest moja. `APP_VERSION` czyta `Constants.expoConfig.version`, czyli app.json,
a app.json przyjezdza do aplikacji RAZEM Z PACZKA OTA. Podbilem wersje na 0.3.0 i
opublikowalem te paczke rowniez na stary kanal 1.2, wiec kazdy uzytkownik APK 0.2.2
zaczal raportowac 0.3.0 i sam siebie uznal za aktualnego. Skutek podwojny: nie dostal ani
monitu o nowym APK, ani wpisu w changelogu, ktory byl kierowany dokladnie do niego.

Poprzednia poprawka (dad4bbf, porownanie "nowsza" zamiast "rozna") tego NIE naprawiala:
przy dwoch identycznych numerach oba warunki daja falsz.

Jedyny numer odporny na OTA to runtime z AndroidManifest, bo expo-updates uzywa go do
sprawdzania zgodnosci paczek i nie moze brac go z paczki. Stad `NATIVE_RUNTIME`
w `src/lib/appVersion.ts`, a serwer oddaje teraz `runtime_version` obok `version`.
Porownanie leci po runtime, z zapasowym porownaniem wersji, gdy serwer go nie poda.
Bramka wpisow w changelogu przeszla z `onlyBelowVersion` na `onlyBelowRuntime`.

`expo-application` bylby czystszym zrodlem natywnej wersji, ale to modul natywny, wiec
nie wszedlby przez OTA do ludzi na starym APK, czyli dokladnie do tych, ktorych to
dotyczy. Runtime dziala na tym, co juz maja zainstalowane.

Przy okazji tresc dialogu: pokazywala "Server: 0.3.0, App: 0.3.0", czyli dwie identyczne
liczby, bo drugi numer byl z paczki, a nie z APK. Teraz nie pokazujemy numeru
zainstalowanej wersji, bo po OTA nie mamy wiarygodnego.

Zweryfikowane na emulatorze w obie strony, na podpisanych APK:
* APK 0.2.2 (runtime 1.2) po pobraniu paczki: monit jest, tresc czytelna.
* APK 0.3.0 (runtime 1.3): monitu nie ma.
Instalacja 0.3.0 na wierzch 0.2.2 przeszla ponownie.
2026-08-24 14:47:04 +02:00
..
android feat(mobile): Chromecast (0.3.0, runtime 1.3) 2026-08-24 14:27:41 +02:00
assets feat(brand): oo logo — app icons, landing, in-app mark (Gemini-generated) 2026-05-30 22:38:52 +02:00
src fix(mobile): stary APK po OTA podawal sie za nowa wersje i nie dostawal monitu 2026-08-24 14:47:04 +02:00
.gitignore Initial commit 2026-05-20 10:10:22 +02:00
app.json feat(mobile): Chromecast (0.3.0, runtime 1.3) 2026-08-24 14:27:41 +02:00
App.tsx fix(mobile): stary APK po OTA podawal sie za nowa wersje i nie dostawal monitu 2026-08-24 14:47:04 +02:00
babel.config.js Initial commit 2026-05-20 10:10:22 +02:00
package-lock.json feat(mobile): Chromecast (0.3.0, runtime 1.3) 2026-08-24 14:27:41 +02:00
package.json feat(mobile): Chromecast (0.3.0, runtime 1.3) 2026-08-24 14:27:41 +02:00
README.md Initial commit 2026-05-20 10:10:22 +02:00
tsconfig.json Initial commit 2026-05-20 10:10:22 +02:00

goon mobile (Expo / React Native)

Mobile client do self-hosted goon backendu. iOS/Android jeden codebase.

Setup

cd mobile
npm install
npx expo start

Następnie:

  • Expo Go (iOS/Android): zeskanuj QR z terminala
  • Android emulator: npm run android
  • iOS simulator: npm run ios (tylko macOS)
  • Web preview: npm run web

Konfiguracja

Po pierwszym uruchomieniu zobaczysz ekran logowania:

  • Backend URL — adres twojego goon backendu, np.:
    • http://192.168.1.10:8000 (LAN)
    • https://goon.tvojadomena.dev (przez Cloudflare Tunnel/Caddy)
    • http://100.x.x.x:8000 (przez Tailscale)
  • API Key — klucz wygenerowany dla backendu, ustawiony w .env:
    API_KEYS=<klucz>
    
    Generowanie:
    python -c "import secrets; print(secrets.token_urlsafe(32))"
    

Klucz jest trzymany w expo-secure-store (Keychain na iOS, Keystore na Androidzie).

Ekrany

  • Login — backend URL + API key, weryfikacja przez /healthz + /scenes
  • Scenes — lista scen z search; pull-to-refresh
  • Scene detail — performerzy, tagi, źródła, opis
  • Merge queue — pending merge candidates, sortowane po score (desc)
  • Merge detail — side-by-side dwóch scen + reasons + akcje:
    • Merge → keep LEFT (default — left to wcześniejsza/kanoniczna)
    • Merge → keep RIGHT (gdy nowsza wersja ma lepsze metadane)
    • Reject (keep both) — to nie jest duplikat, zostawiamy oddzielnie

Stack

  • Expo SDK 52 + React Native 0.76 (new architecture)
  • TypeScript strict
  • React Navigation 7 (native stack)
  • TanStack Query 5 (cache, optimistic invalidation)
  • expo-secure-store (credentials)

Build .apk (sideload na Androida)

npx eas build --profile preview --platform android

Wymaga konta Expo. Bez niego można użyć expo prebuild + gradlew assembleRelease.

Dlaczego nie PWA

Wybrany RN+Expo zamiast PWA bo:

  • swipe gestures pasują do triage merge queue
  • secure-store jest natywny (Keychain/Keystore) zamiast localStorage
  • pull-to-refresh i FlatList virtualization out-of-the-box
  • Jeden .apk można rozdać testerom bez App Store