goon/mobile/src/lib/appVersion.ts
goon-foss 9872d5e4ca feat(mobile): zakladka Swipe, pionowy feed klipow (etap 4, APK 0.4.0 / runtime 1.4)
Nowa zakladka w dolnym pasku: react-native-pager-view (pion, natywny snap), dwie
instancje createVideoPlayer (aktywna + nastepna w buforze), komorka z posterem,
podpisem tworcy i tagami, kolumna przyciskow, pasek postepu. Gesty bez reanimated:
tap = dzwiek, double-tap = ulubione z wybuchem serca i haptyka, przytrzymanie = 2x,
przesuniecie w lewo = arkusz "show less of this" (tworca / tagi / nisze / seen it,
plus blokada tworcy). Onboarding 3 tapow z top tagow, AutoSwipe (off/5/10 s),
licznik obejrzanych, nowe rozdanie.

Telemetria: SwipeTelemetry zbiera zdarzenia i wysyla paczki co 5 s / 10 zdarzen /
w tle; przy opuszczeniu strony impression z dwell, skip (<2 s) albo complete (petla
lub >=80%). Serwer z tego uczy wagi i uklada 80% nastepnych stron.

Pager to modul natywny, wiec import jest miekki (require w try/catch) i zakladka
pokazuje sie tylko od runtime 1.4; ten sam bundle leci OTA na 1.2/1.3 z wpisem
"pobierz 0.4.0" (onlyBelowRuntime), a wpis o Swipe ma nowe pole minRuntime.
Bump: app.json 0.4.0 / runtime 1.4, versionCode 14, AndroidManifest 1.4,
publish_update RUNTIME_DEFAULT 1.4, strona goon-foss.org pod 0.4.0.

Sprawdzone na emulatorze (APK 0.4.0): zakladka widoczna, onboarding zapisany na
serwerze, feed spersonalizowany 8/2, ExoPlayer dekoduje wideo i audio, swipe daje
zdarzenia complete/impression, double-tap daje like + ulubione + wagi.
2026-09-15 09:24:13 +02:00

63 lines
2.7 KiB
TypeScript

import Constants from 'expo-constants';
import * as Updates from 'expo-updates';
/**
* Aktualna wersja appki, używana do display'u (BugReportFAB, Update dialog)
* i porównań z `/version` z serwera.
*
* Źródła w kolejności preferencji:
* 1. `Constants.expoConfig?.version`, działa gdy OTA manifest ma `extra.expoClient`
* (publish_update.py embeduje tam app.json). W release APK przed pierwszym OTA
* bierze się to z app.json bundled w build.
* 2. Hardcoded fallback, chroni przed OTA-z-pustego-extra (bug 2026-05-12, gdzie
* stara wersja publish_update.py wysyłała `extra: {}` → expoConfig=null po
* reload-zie → version='0.0.0' → fałszywy "Update available" dialog).
*
* **Bump przy każdym APK release** żeby APK fallback pasował do realnej wersji.
* OTA-only releases (silent JS update) NIE bumpują tego, wtedy version w app.json
* też nie idzie do góry, więc consistency jest zachowana.
*/
export const APP_VERSION: string =
(Constants.expoConfig?.version as string | undefined) || '0.4.0';
/**
* Runtime zapisany w APK (`EXPO_RUNTIME_VERSION` w AndroidManifest). JEDYNY numer, po
* którym aplikacja pozna, jaką paczkę natywną ma naprawdę zainstalowaną.
*
* Po co osobno, skoro jest `APP_VERSION`: ta bierze się z `app.json`, a `app.json`
* przyjeżdża do aplikacji RAZEM Z PACZKĄ OTA. Po podbiciu wersji na 0.3.0 i wysłaniu tej
* paczki na stary kanał każdy użytkownik APK 0.2.2 zaczął raportować 0.3.0 i sam siebie
* uznał za aktualnego: nie dostał ani monitu o nowym APK, ani wpisu w changelogu, który
* właśnie do niego był kierowany. Runtime siedzi w natywnym manifeście i OTA go nie rusza.
*/
export const NATIVE_RUNTIME: string | null = (() => {
try {
return Updates.runtimeVersion ?? null;
} catch {
return null;
}
})();
/** „0.3.0" → [0,3,0]. Człony nieliczbowe (np. „0.3.0-rc1") lecą na 0. */
function versionParts(v: string): number[] {
return String(v || '').split('.').map((x) => parseInt(x, 10) || 0);
}
/**
* Czy `candidate` jest NOWSZA niż `current`.
*
* Po co, skoro wcześniej wystarczyło `!==`: serwer trzyma numer najnowszego APK na
* sztywno, a przy wydaniu 0.3.0 nie został podbity od razu. Porównanie „różne" kazało
* wtedy KAŻDEMU użytkownikowi 0.3.0 zainstalować 0.2.2, czyli cofnąć się o wersję.
* Złapane na emulatorze przed publikacją.
*/
export function isNewerVersion(candidate: string, current: string): boolean {
const a = versionParts(candidate);
const b = versionParts(current);
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const d = (a[i] ?? 0) - (b[i] ?? 0);
if (d !== 0) return d > 0;
}
return false;
}