Stabilizza i18n Android 2.0.5: lingua live senza perdere sessione né crashare il wizard.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# Gap Android → iOS: allineamento app nativa
|
||||
|
||||
Documento operativo per continuare su **Mac** lo sviluppo iOS e rilasciare un’app **allineata ad Android `2.0.4-native`**.
|
||||
Documento operativo per continuare su **Mac** lo sviluppo iOS e rilasciare un’app **allineata ad Android `2.0.5-native`**.
|
||||
|
||||
**Aggiornato:** 24 luglio 2026
|
||||
**Riferimento Android (produzione / telefono):** `2.0.4-native` (`versionCode` **25**), API `https://www.matchlivetv.it`
|
||||
**Riferimento Android (produzione / telefono / Play):** `2.0.5-native` (`versionCode` **26**), API `https://www.matchlivetv.it`
|
||||
**Stato iOS attuale:** marketing `2.0.0`, build `21` — i18n **solo Login parziale**
|
||||
|
||||
Obiettivo iOS: **stessa copertura lingua** di Android (Login, hub, sheet/dialog, wizard, broadcast) + bump versione.
|
||||
Obiettivo iOS: **stessa copertura lingua** di Android (Login, hub, sheet/dialog, wizard, broadcast) + bump versione, **senza** i bug di sessione/crash già risolti su Android 2.0.5.
|
||||
|
||||
---
|
||||
|
||||
@@ -14,8 +14,8 @@ Obiettivo iOS: **stessa copertura lingua** di Android (Login, hub, sheet/dialog,
|
||||
|
||||
Su Android (rilasciato) la lingua scelta dall’utente vale su **tutta l’app** usabile:
|
||||
|
||||
- Persistenza + apply (`AppLocale` / SharedPreferences `mltv_locale` + recreate)
|
||||
- Icona **globo** su Login e Matches
|
||||
- Persistenza + apply live (`AppLocale` / SharedPreferences `mltv_locale` + `ProvideAppLocale`)
|
||||
- Icona **globo** su Login (TopAppBar) e Matches
|
||||
- Hub Partite localizzato
|
||||
- Bottom sheet e dialog Matches (es. «Choose team», nuova partita, programma, elimina, riprendi)
|
||||
- Wizard 3 step + branding colori
|
||||
@@ -28,12 +28,42 @@ API produzione: entrambe le app puntano già a `https://www.matchlivetv.it` —
|
||||
|
||||
---
|
||||
|
||||
## Stato a confronto (target = Android 2.0.4)
|
||||
## Lezioni Android 2.0.5 (obbligatorie su iOS)
|
||||
|
||||
| Area | Android 2.0.4 | iOS oggi | Da fare su iOS |
|
||||
Queste sono regressioni/bug già visti su Android; **non ripeterli** su iOS.
|
||||
|
||||
| Problema | Sintomo | Fix Android (da replicare in spirito) |
|
||||
|----------|---------|----------------------------------------|
|
||||
| Recreate Activity al cambio lingua | Torna al Login / perde nav | **Niente recreate** obbligatorio: aggiorna UI via stato lingua + refresh stringhe |
|
||||
| Context “nudo” per le stringhe | Crash wizard: `No ActivityResultRegistryOwner` / picker foto | Mantieni **Activity** come host; localizza solo Resources / environment |
|
||||
| `findActivity()` su context configurazione | Cambio lingua non applica (apply silent no-op) | Applica lingua con `applicationContext` / store globale, non dipendere da Activity dal dialog |
|
||||
| Globe sotto lo scroll del form Login | Tap ignorato | Globe in **toolbar/top bar**, non overlay sotto `ScrollView`/`Column` |
|
||||
| Splash che richiede rete per restare loggato | Dopo cambio lingua → Login se API lenta | Se token locale presente → resta autenticato; refresh rete best-effort |
|
||||
|
||||
### Pattern Android di riferimento (Compose)
|
||||
|
||||
1. `AppLocale.tagFlow` + prefs `mltv_locale`
|
||||
2. `ProvideAppLocale`: `LocalConfiguration` + `LocalizedContextWrapper(Activity, localizedResources)`
|
||||
→ stringhe aggiornate **e** `LocalActivityResultRegistryOwner` integro
|
||||
3. `LanguagePickerDialog` chiama `AppLocale.apply(context, tag)` (applicationContext), non `findActivity()` obbligatorio
|
||||
4. Splash: `currentSession() != null` → Matches; `validateOrRefresh()` in background
|
||||
|
||||
### Equivalente SwiftUI consigliato
|
||||
|
||||
1. `AppLanguage` / `@AppStorage` + `NotificationCenter` (già presente) o `environment(\.locale, …)`
|
||||
2. Root: `.environment(\.locale, locale)` **e** `.id(languageTick)` sulle schermate che mostrano testo
|
||||
3. **Non** distruggere `NavigationStack` / session store al cambio lingua
|
||||
4. Photo picker / `PhotosPicker` / `UIImagePicker`: restano agganciati alla `View` host, non a un context “fake”
|
||||
5. Cold start: se token in Keychain/UserDefaults → vai a Matches anche se `/me` fallisce temporaneamente
|
||||
|
||||
---
|
||||
|
||||
## Stato a confronto (target = Android 2.0.5)
|
||||
|
||||
| Area | Android 2.0.5 | iOS oggi | Da fare su iOS |
|
||||
|------|---------------|----------|----------------|
|
||||
| Persistenza lingua | SharedPreferences + wrap + recreate | UserDefaults + notification | Verificare refresh UI su **tutte** le schermate (non solo Login/Matches root) |
|
||||
| Picker Login | Icona globo top-right | Testo / picker presente | Allineare a **icona globo** come Android |
|
||||
| Persistenza lingua | SharedPreferences + `ProvideAppLocale` (no recreate nav) | UserDefaults + notification | Refresh UI su **tutte** le schermate **senza** reset sessione/nav |
|
||||
| Picker Login | Icona globo in **TopAppBar** | Testo / picker presente | Allineare a **icona globo** in toolbar |
|
||||
| Picker Matches | Icona globo top bar | Presente | OK se già icona; altrimenti allineare |
|
||||
| Login stringhe | Sì | Sì (`L10n`) | OK |
|
||||
| Slogan wordmark | `app_slogan` | Chiave `app.slogan` **non usata** | Collegare `L10n.t("app.slogan")` |
|
||||
@@ -46,7 +76,7 @@ API produzione: entrambe le app puntano già a `https://www.matchlivetv.it` —
|
||||
| Broadcast + score dialog | Sì (`broadcast_*`, `score_*`) | IT | Localizzare |
|
||||
| Catalogo stringhe | `values` + `values-en/fr/de/es` (~230 chiavi) | ~14 chiavi in `AppLanguage.swift` | Espandere `L10n` (o migrare a `Localizable.xcstrings`) |
|
||||
| Logout UI | Icona | Testo | Icona SF Symbol |
|
||||
| Versione | `2.0.4-native` / **25** | `2.0.0` / **21** | Bump a **2.0.4** / **25** (o build successiva se 22–25 già usati su TestFlight) |
|
||||
| Versione | `2.0.5-native` / **26** | `2.0.0` / **21** | Bump a **2.0.5** / **26** (o build successiva se conflitto TestFlight) |
|
||||
| RTMP ingest | `RtmpIngestUrl.kt` | `MediaUrl.swift` | OK in prod; allineare solo semantica dev se serve |
|
||||
|
||||
---
|
||||
@@ -57,9 +87,10 @@ API produzione: entrambe le app puntano già a `https://www.matchlivetv.it` —
|
||||
|
||||
| Ruolo | Path |
|
||||
|-------|------|
|
||||
| Locale | `native/android/.../core/AppLocale.kt` |
|
||||
| Locale + ProvideAppLocale | `native/android/.../core/AppLocale.kt` |
|
||||
| Picker | `native/android/.../ui/components/LanguagePickerDialog.kt` |
|
||||
| Activity | `native/android/.../MainActivity.kt` |
|
||||
| Splash (sessione locale) | `native/android/.../ui/splash/SplashScreen.kt` |
|
||||
| Stringhe | `native/android/app/src/main/res/values/strings.xml` + `values-{en,fr,de,es}/` |
|
||||
| Login | `.../ui/login/LoginScreen.kt` |
|
||||
| Hub | `.../ui/matches/MatchesScreen.kt` |
|
||||
@@ -79,6 +110,7 @@ API produzione: entrambe le app puntano già a `https://www.matchlivetv.it` —
|
||||
| Wizard | `UI/Wizard/` (o path equivalente) |
|
||||
| Broadcast | `UI/Broadcast/` (o path equivalente) |
|
||||
| Wordmark | `native/ios/MatchLiveTv/UI/Components/MatchLiveWordmark.swift` |
|
||||
| Auth / splash equivalent | entry root / session restore |
|
||||
| Versioni | `generate_xcodeproj.py`, `project.pbxproj`, `Info.plist` |
|
||||
| Build | `scripts/build_ios_release.sh` |
|
||||
|
||||
@@ -99,9 +131,9 @@ API produzione: entrambe le app puntano già a `https://www.matchlivetv.it` —
|
||||
|
||||
### Fase B — Login + wordmark (rapido)
|
||||
|
||||
5. Login: icona globo in alto a destra (come Android), non solo testo.
|
||||
5. Login: icona globo in toolbar (come Android TopAppBar), non overlay che lo scroll può coprire.
|
||||
6. `MatchLiveWordmark`: `L10n.t("app.slogan")`.
|
||||
7. Verificare che al cambio lingua Login si ricrei/rinfreschi (notification già presente).
|
||||
7. Cambio lingua: aggiorna stringhe **senza** buttare fuori l’utente (se già loggato altrove, stessa regola).
|
||||
|
||||
### Fase C — Hub Matches + sheet/dialog (priorità UX — screenshot «Scegli squadra»)
|
||||
|
||||
@@ -120,17 +152,19 @@ API produzione: entrambe le app puntano già a `https://www.matchlivetv.it` —
|
||||
|
||||
13. Localizzare step Partita / Trasmissione / Test rete + branding editor + color hue (`wizard.*`).
|
||||
14. Errori validazione e CTA AVANTI/INDIETRO/INIZIA.
|
||||
15. Verificare photo/logo picker dopo cambio lingua (su Android crashava senza Activity host).
|
||||
|
||||
### Fase E — Broadcast
|
||||
|
||||
15. Overlay controlli, contentDescription, snackbar pausa/ripresa/regia.
|
||||
16. Dialog termina diretta, set concluso, chiudi set, partita terminata (`broadcast.*`, `score.*`).
|
||||
16. Overlay controlli, contentDescription, snackbar pausa/ripresa/regia.
|
||||
17. Dialog termina diretta, set concluso, chiudi set, partita terminata (`broadcast.*`, `score.*`).
|
||||
|
||||
### Fase F — Versione e release
|
||||
### Fase F — Sessione + versione e release
|
||||
|
||||
17. Bump `MARKETING_VERSION` → `2.0.4`, `CURRENT_PROJECT_VERSION` → `25` (o successivo se conflitto TestFlight).
|
||||
18. Aggiornare `generate_xcodeproj.py` **e** `Info.plist`, poi `python3 generate_xcodeproj.py`.
|
||||
19. `./scripts/build_ios_release.sh` (API default già produzione).
|
||||
18. Cold start / cambio lingua: token locale → resta in Matches (non forzare Login se `/me` fallisce).
|
||||
19. Bump `MARKETING_VERSION` → `2.0.5`, `CURRENT_PROJECT_VERSION` → `26` (o successivo se conflitto TestFlight).
|
||||
20. Aggiornare `generate_xcodeproj.py` **e** `Info.plist`, poi `python3 generate_xcodeproj.py`.
|
||||
21. `./scripts/build_ios_release.sh` (API default già produzione).
|
||||
|
||||
---
|
||||
|
||||
@@ -156,14 +190,15 @@ Non lasciare fuori scope wizard/broadcast: su Android **sono già tradotti**; iO
|
||||
1. `cd native/ios && python3 generate_xcodeproj.py`
|
||||
2. Build simulatore; login produzione.
|
||||
3. Login: globo → English → label/errori/slogan in EN.
|
||||
4. Matches: saluto, CTA, empty state in EN.
|
||||
5. Apri «Change» / scegli squadra: titolo **«Choose team»** (non «Scegli squadra»).
|
||||
6. Nuova partita / programma / elimina: tutto EN.
|
||||
7. Wizard 3 step in EN.
|
||||
8. (Se possibile) overlay diretta: dialog/termina in EN.
|
||||
9. Kill app → riapri: lingua persistita.
|
||||
10. Spot-check FR/DE/ES.
|
||||
11. Release: `./scripts/build_ios_release.sh`
|
||||
4. **Loggato:** cambio lingua da Matches → resti su Matches, testi EN (niente ritorno al Login).
|
||||
5. Matches: saluto, CTA, empty state in EN.
|
||||
6. Apri «Change» / scegli squadra: titolo **«Choose team»** (non «Scegli squadra»).
|
||||
7. Nuova partita → Avvia ora → wizard si apre **senza crash**; step in EN.
|
||||
8. Wizard 3 step in EN; logo picker funziona.
|
||||
9. (Se possibile) overlay diretta: dialog/termina in EN.
|
||||
10. Kill app → riapri: lingua persistita **e** sessione resta se token valido.
|
||||
11. Spot-check FR/DE/ES.
|
||||
12. Release: `./scripts/build_ios_release.sh`
|
||||
|
||||
Test:
|
||||
|
||||
@@ -189,15 +224,28 @@ xcodebuild -project MatchLiveTv.xcodeproj -scheme MatchLiveTv \
|
||||
|
||||
Nessuna modifica server richiesta.
|
||||
|
||||
### Artefatti Android Play (riferimento)
|
||||
|
||||
```bash
|
||||
# APK installazione diretta
|
||||
./scripts/build_native_android_apk_prod.sh
|
||||
|
||||
# AAB per Google Play Console
|
||||
cd native/android && ./gradlew bundleRelease -PAPI_BASE_URL=https://www.matchlivetv.it
|
||||
# → app/build/outputs/bundle/release/app-release.aab
|
||||
```
|
||||
|
||||
Versione corrente store: **`2.0.5-native` / versionCode `26`**.
|
||||
|
||||
---
|
||||
|
||||
## Sintesi priorità (ordine di esecuzione)
|
||||
|
||||
1. **A** Catalogo stringhe completo (copiare da Android `values*`)
|
||||
2. **C** Hub + sheet/dialog (fix «Scegli squadra» in EN)
|
||||
3. **B** Slogan + globo Login
|
||||
4. **D** Wizard
|
||||
3. **B** Slogan + globo Login (toolbar)
|
||||
4. **D** Wizard (+ no crash picker)
|
||||
5. **E** Broadcast
|
||||
6. **F** Versione `2.0.4` / `25` + build release
|
||||
6. **F** Sessione stabile al cambio lingua + versione `2.0.5` / `26` + build release
|
||||
|
||||
Quando A–F sono chiusi, iOS è allineato ad Android **2.0.4-native** per UX multilingua e versioning store.
|
||||
Quando A–F sono chiusi, iOS è allineato ad Android **2.0.5-native** per UX multilingua, stabilità sessione e versioning store.
|
||||
|
||||
Reference in New Issue
Block a user