# Gap Android → iOS: allineamento app nativa Documento operativo per continuare su **Mac** lo sviluppo iOS e rilasciare un’app **allineata ad Android**. **Aggiornato:** 12 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) **Nuovo (branch `feature/stream-audio-mute`):** mute microfono in diretta. Codice iOS già nel branch; verifica su Mac in [`IOS_STREAM_AUDIO_MUTE.md`](IOS_STREAM_AUDIO_MUTE.md). 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.