Bump marketing/build a 2.0.10/31, copy EN forgot password e checklist password policy. Co-authored-by: Cursor <cursoragent@cursor.com>
151 lines
6.8 KiB
Markdown
151 lines
6.8 KiB
Markdown
# Gap Android → iOS: allineamento app nativa
|
||
|
||
Documento operativo per continuare su **Mac** lo sviluppo iOS e rilasciare un’app **allineata ad Android**.
|
||
|
||
**Aggiornato:** 8 agosto 2026
|
||
**Riferimento Android (produzione / telefono / Play):** `2.0.10-native` (`versionCode` **31**), API `https://www.matchlivetv.it`
|
||
**Stato iOS attuale:** marketing `2.0.10`, build `31` — i18n **allineato** (Login, hub, sheet/dialog, wizard, broadcast, account/forgot)
|
||
|
||
Obiettivo iOS: **stessa copertura lingua** di Android (Login, hub, sheet/dialog, wizard, broadcast, account) + bump versione, **senza** i bug di sessione/crash già risolti su Android.
|
||
|
||
---
|
||
|
||
## Contesto
|
||
|
||
Su Android (rilasciato) la lingua scelta dall’utente vale su **tutta** l’app usabile:
|
||
|
||
- 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
|
||
- Broadcast overlay, snackbar, dialog fine set/partita
|
||
- ~230+ chiavi × 5 lingue in `res/values*`
|
||
|
||
Su iOS: `AppLanguage` + `L10n` (~225 chiavi × 5 lingue) + `LanguagePickerView`; Login/Matches/Wizard/Broadcast usano `L10n.t(...)`. Refresh UI via `NotificationCenter` + `.id(languageTick)` **senza** reset sessione/nav.
|
||
|
||
API produzione: entrambe le app puntano già a `https://www.matchlivetv.it` — **nessun gap di endpoint**.
|
||
|
||
---
|
||
|
||
## Lezioni Android 2.0.5 (obbligatorie su iOS)
|
||
|
||
Queste sono regressioni/bug già visti su Android; **non ripeterli** su iOS.
|
||
|
||
| Problema | Sintomo | Fix Android (da replicare in spirito) | Stato iOS |
|
||
|----------|---------|----------------------------------------|-----------|
|
||
| Recreate Activity al cambio lingua | Torna al Login / perde nav | **Niente recreate** obbligatorio: aggiorna UI via stato lingua + refresh stringhe | OK — `languageTick` / notification |
|
||
| Context “nudo” per le stringhe | Crash wizard: picker foto | Mantieni **Activity** come host | OK — SwiftUI host invariato |
|
||
| Globe sotto lo scroll del form Login | Tap ignorato | Globe in **toolbar/top bar** | OK — globo in top bar Login |
|
||
| Splash che richiede rete per restare loggato | Dopo cambio lingua → Login se API lenta | Se token locale presente → resta autenticato | OK — `validateOrRefresh` restituisce sessione locale su errore rete |
|
||
|
||
---
|
||
|
||
## Stato a confronto (target = Android 2.0.5)
|
||
|
||
| Area | Android 2.0.5 | iOS oggi | Da fare su iOS |
|
||
|------|---------------|----------|----------------|
|
||
| Persistenza lingua | SharedPreferences + `ProvideAppLocale` | UserDefaults + notification | **Fatto** |
|
||
| Picker Login | Icona globo in **TopAppBar** | Icona globo top bar | **Fatto** |
|
||
| Picker Matches | Icona globo top bar | Icona globo + logout icon | **Fatto** |
|
||
| Login stringhe | Sì | Sì (`L10n`) | **Fatto** |
|
||
| Slogan wordmark | `app_slogan` | `L10n.t("app.slogan")` | **Fatto** |
|
||
| Hub Matches | Sì | `matches.*` | **Fatto** |
|
||
| Sheet/dialog Matches | Sì (`sheet_*`) | `sheet.*` | **Fatto** |
|
||
| Status badge partita | `match_status_*` | `match.status.*` | **Fatto** |
|
||
| Snackbar hub | `matches_msg_*` | `matches.msg.*` | **Fatto** |
|
||
| Wizard 3 step | Sì (`wizard_*`) | `wizard.*` | **Fatto** |
|
||
| Branding / color picker | Sì | `wizard.branding.*` / `wizard.color.hue` | **Fatto** |
|
||
| Broadcast + score dialog | Sì | `broadcast.*` / `score.*` | **Fatto** |
|
||
| Catalogo stringhe | `values*` (~230) | `AppLanguage.swift` L10n (~225, no FGS) | **Fatto** |
|
||
| Logout UI | Icona | Icona SF Symbol | **Fatto** |
|
||
| Versione | `2.0.10-native` / **31** | `2.0.10` / **31** | **Fatto** |
|
||
| RTMP ingest | `RtmpIngestUrl.kt` | `MediaUrl.swift` | OK |
|
||
|
||
### Residui fuori scope Android (opzionali)
|
||
|
||
Nessuno per messaggi utente: tutte le stringhe UI/errori/termiche/API/overlay usano `L10n`.
|
||
Permission dialog di sistema localizzati via `Resources/{it,en,fr,de,es}.lproj/InfoPlist.strings`.
|
||
|
||
Brand fissi lasciati intenzionalmente invariati: `MATCH` / `LIVE` / `TV`, `Match Live TV`, `YouTube Live`.
|
||
|
||
---
|
||
|
||
## File di riferimento
|
||
|
||
### Android (sorgente di verità)
|
||
|
||
| Ruolo | Path |
|
||
|-------|------|
|
||
| Locale + ProvideAppLocale | `native/android/.../core/AppLocale.kt` |
|
||
| Stringhe | `native/android/app/src/main/res/values/strings.xml` + `values-{en,fr,de,es}/` |
|
||
| UI | Login / Matches / MatchSheets / Wizard / Broadcast |
|
||
|
||
### iOS (allineato)
|
||
|
||
| Ruolo | Path |
|
||
|-------|------|
|
||
| Lingua + L10n | `native/ios/MatchLiveTv/Core/AppLanguage.swift` |
|
||
| Login | `native/ios/MatchLiveTv/UI/Login/LoginScreen.swift` |
|
||
| Hub + sheet | `native/ios/MatchLiveTv/UI/Matches/MatchesScreen.swift` |
|
||
| Status badge | `native/ios/MatchLiveTv/Domain/MatchHubFilter.swift` (`MatchPresentation`) |
|
||
| Wizard | `native/ios/MatchLiveTv/UI/Wizard/*.swift` |
|
||
| Broadcast | `native/ios/MatchLiveTv/UI/Broadcast/*.swift` |
|
||
| Wordmark | `native/ios/MatchLiveTv/UI/Components/MatchLiveWordmark.swift` |
|
||
| Versioni | `generate_xcodeproj.py`, `Info.plist`, `project.pbxproj` |
|
||
|
||
---
|
||
|
||
## Convenzione chiavi
|
||
|
||
- Android `matches_hello` → iOS `matches.hello` (ogni `_` → `.`)
|
||
- Placeholder: `%1$s` / `%1$d` → `%1$@` / `%1$d` con `L10n.t("key", args...)`
|
||
- Catalogo generato da `values*/strings.xml` (escluse `streaming_notification_*`, solo Android FGS)
|
||
|
||
---
|
||
|
||
## Verifica manuale su Mac (acceptance)
|
||
|
||
1. `cd native/ios && python3 generate_xcodeproj.py`
|
||
2. Build simulatore; login produzione.
|
||
3. Login: globo → English → label/errori/slogan in EN.
|
||
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`
|
||
|
||
```bash
|
||
cd native/ios
|
||
python3 generate_xcodeproj.py
|
||
xcodebuild -project MatchLiveTv.xcodeproj -scheme MatchLiveTv \
|
||
-destination 'generic/platform=iOS Simulator' \
|
||
-derivedDataPath build/DerivedData \
|
||
ONLY_ACTIVE_ARCH=YES ARCHS=arm64 EXCLUDED_ARCHS=x86_64 \
|
||
build
|
||
```
|
||
|
||
---
|
||
|
||
## Note API / produzione
|
||
|
||
| | Android | iOS |
|
||
|--|---------|-----|
|
||
| Default | BuildConfig / `-PAPI_BASE_URL` | `API_BASE_URL` → Info.plist |
|
||
| Fallback | `https://www.matchlivetv.it` | idem |
|
||
| REST / Cable | `/api/v1`, `wss://…/cable` | idem |
|
||
|
||
Nessuna modifica server richiesta.
|
||
|
||
Versione store target: **`2.0.10` / build `31`** (parità con Android `2.0.10-native` / versionCode `31`).
|
||
|
||
---
|
||
|
||
## Sintesi
|
||
|
||
Fasi A–F del piano originale sono **chiuse**. Tutti i messaggi utente (UI, errori API/RTMP, termica, overlay, permission system) passano da `L10n` / `InfoPlist.strings` in IT/EN/FR/DE/ES.
|