feat(bienvenida): add monetization-free welcome screen
Build the first-run welcome surface from mockup screen 14, stripped of its entire monetization block: no PRO pill, no "14 dias PRO gratis" trial line, no pricing card, no secondary "free version" link. Ships only logo, headline, body copy, exactly 3 feature bullets, and the single "Empezar a escuchar" CTA, as a full-screen route (not a modal). Spanish copy is re-cast from the mockup's "tu" form to the app's established voseo register (matching ~750 existing app_es.arb lines), using "auto" instead of "coche" per the one existing precedent. The 3 bullet icons reuse existing tokens (electricMagenta/liveGreen/ warmCoral) that already match the mockup's own hex values for them. CTA switches to the Escuchar tab and pops the route. Wiring this screen into the real first-launch flow (main.dart/app.dart) is left for a follow-up unit, same shape as WU15/WU15b - this WU only covers the isolated, tested screen per its own task list and verify command. WU17.
This commit is contained in:
+10
-1
@@ -799,5 +799,14 @@
|
|||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"localMusicFolderGenericName": "Selected folder"
|
"localMusicFolderGenericName": "Selected folder",
|
||||||
|
"welcomeHeadline": "Your world, live",
|
||||||
|
"welcomeBody": "53,412 stations from 238 countries. Save your favorites, take them to the car, and wake up to them.",
|
||||||
|
"welcomeBullet1Title": "Per-station equalizer",
|
||||||
|
"welcomeBullet1Subtitle": "Plus a preset per output device",
|
||||||
|
"welcomeBullet2Title": "Android Auto",
|
||||||
|
"welcomeBullet2Subtitle": "Your favorites and local music in the car",
|
||||||
|
"welcomeBullet3Title": "Music alarms",
|
||||||
|
"welcomeBullet3Subtitle": "With gradual volume rise and vacation mode",
|
||||||
|
"welcomeCtaLabel": "Start listening"
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -758,5 +758,14 @@
|
|||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"localMusicFolderGenericName": "Carpeta seleccionada"
|
"localMusicFolderGenericName": "Carpeta seleccionada",
|
||||||
|
"welcomeHeadline": "Tu mundo, en directo",
|
||||||
|
"welcomeBody": "53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.",
|
||||||
|
"welcomeBullet1Title": "Ecualizador por emisora",
|
||||||
|
"welcomeBullet1Subtitle": "Y un preset por dispositivo de salida",
|
||||||
|
"welcomeBullet2Title": "Android Auto",
|
||||||
|
"welcomeBullet2Subtitle": "Tus favoritas y tu música local en el auto",
|
||||||
|
"welcomeBullet3Title": "Alarmas musicales",
|
||||||
|
"welcomeBullet3Subtitle": "Con subida progresiva y modo vacaciones",
|
||||||
|
"welcomeCtaLabel": "Empezar a escuchar"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2869,6 +2869,60 @@ abstract class AppLocalizations {
|
|||||||
/// In es, this message translates to:
|
/// In es, this message translates to:
|
||||||
/// **'Carpeta seleccionada'**
|
/// **'Carpeta seleccionada'**
|
||||||
String get localMusicFolderGenericName;
|
String get localMusicFolderGenericName;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeHeadline.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Tu mundo, en directo'**
|
||||||
|
String get welcomeHeadline;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeBody.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.'**
|
||||||
|
String get welcomeBody;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeBullet1Title.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Ecualizador por emisora'**
|
||||||
|
String get welcomeBullet1Title;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeBullet1Subtitle.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Y un preset por dispositivo de salida'**
|
||||||
|
String get welcomeBullet1Subtitle;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeBullet2Title.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Android Auto'**
|
||||||
|
String get welcomeBullet2Title;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeBullet2Subtitle.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Tus favoritas y tu música local en el auto'**
|
||||||
|
String get welcomeBullet2Subtitle;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeBullet3Title.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Alarmas musicales'**
|
||||||
|
String get welcomeBullet3Title;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeBullet3Subtitle.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Con subida progresiva y modo vacaciones'**
|
||||||
|
String get welcomeBullet3Subtitle;
|
||||||
|
|
||||||
|
/// No description provided for @welcomeCtaLabel.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Empezar a escuchar'**
|
||||||
|
String get welcomeCtaLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -1580,4 +1580,34 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'مجلد محدد';
|
String get localMusicFolderGenericName => 'مجلد محدد';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1588,4 +1588,34 @@ class AppLocalizationsBn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'নির্বাচিত ফোল্ডার';
|
String get localMusicFolderGenericName => 'নির্বাচিত ফোল্ডার';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1599,4 +1599,34 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Ausgewählter Ordner';
|
String get localMusicFolderGenericName => 'Ausgewählter Ordner';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1581,4 +1581,34 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Selected folder';
|
String get localMusicFolderGenericName => 'Selected folder';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Your world, live';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53,412 stations from 238 countries. Save your favorites, take them to the car, and wake up to them.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Per-station equalizer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Plus a preset per output device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Your favorites and local music in the car';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Music alarms';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'With gradual volume rise and vacation mode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Start listening';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1594,4 +1594,34 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Carpeta seleccionada';
|
String get localMusicFolderGenericName => 'Carpeta seleccionada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1603,4 +1603,34 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Dossier sélectionné';
|
String get localMusicFolderGenericName => 'Dossier sélectionné';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1586,4 +1586,34 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'चयनित फ़ोल्डर';
|
String get localMusicFolderGenericName => 'चयनित फ़ोल्डर';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1592,4 +1592,34 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Folder terpilih';
|
String get localMusicFolderGenericName => 'Folder terpilih';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1598,4 +1598,34 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Cartella selezionata';
|
String get localMusicFolderGenericName => 'Cartella selezionata';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1545,4 +1545,34 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => '選択したフォルダー';
|
String get localMusicFolderGenericName => '選択したフォルダー';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1590,4 +1590,34 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Pasta selecionada';
|
String get localMusicFolderGenericName => 'Pasta selecionada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1594,4 +1594,34 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => 'Выбранная папка';
|
String get localMusicFolderGenericName => 'Выбранная папка';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1537,4 +1537,34 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get localMusicFolderGenericName => '已选文件夹';
|
String get localMusicFolderGenericName => '已选文件夹';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeHeadline => 'Tu mundo, en directo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBody =>
|
||||||
|
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Title => 'Ecualizador por emisora';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Title => 'Android Auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet2Subtitle =>
|
||||||
|
'Tus favoritas y tu música local en el auto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Title => 'Alarmas musicales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeBullet3Subtitle =>
|
||||||
|
'Con subida progresiva y modo vacaciones';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get welcomeCtaLabel => 'Empezar a escuchar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../estado/estado_navegacion.dart';
|
||||||
|
import '../l10n/gen/app_localizations.dart';
|
||||||
|
import '../tema/pluriwave_theme.dart';
|
||||||
|
|
||||||
|
/// WU17: first-run welcome surface (`onboarding-welcome` spec, mockup
|
||||||
|
/// screen 14) rebuilt WITHOUT its monetization content — no "PRO" pill, no
|
||||||
|
/// "14 días" trial line, no pricing card, and no secondary "free version"
|
||||||
|
/// link (binding no-monetization requirement). Content only: logo,
|
||||||
|
/// headline, body copy, exactly 3 feature bullets, and the single
|
||||||
|
/// "Empezar a escuchar" CTA.
|
||||||
|
///
|
||||||
|
/// A full-screen ROUTE, not a modal `Dialog` — deliberately distinct from
|
||||||
|
/// the pre-existing `PluriOnboardingDialog` (an unrelated "what's new"
|
||||||
|
/// help-content modal already shown from `app.dart`'s launch flow). This
|
||||||
|
/// work unit's own task list and verify command scope to this screen in
|
||||||
|
/// isolation; wiring it into the real first-launch flow (i.e. deciding
|
||||||
|
/// when `app.dart`/`main.dart` should push it) is not part of WU17.
|
||||||
|
class PantallaBienvenida extends StatelessWidget {
|
||||||
|
const PantallaBienvenida({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final t = context.pluriTokens;
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 48, 24, 28),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/icons/pluriwave_app_mark.png',
|
||||||
|
width: 76,
|
||||||
|
height: 76,
|
||||||
|
errorBuilder:
|
||||||
|
(_, __, ___) => Icon(
|
||||||
|
Icons.graphic_eq_rounded,
|
||||||
|
size: 76,
|
||||||
|
color: t.electricMagenta,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 22),
|
||||||
|
Text(
|
||||||
|
l10n.welcomeHeadline,
|
||||||
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
height: 1.05,
|
||||||
|
letterSpacing: -1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
l10n.welcomeBody,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.72),
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 28),
|
||||||
|
FilaCaracteristicaBienvenida(
|
||||||
|
icon: Icons.equalizer_rounded,
|
||||||
|
iconColor: t.electricMagenta,
|
||||||
|
titulo: l10n.welcomeBullet1Title,
|
||||||
|
subtitulo: l10n.welcomeBullet1Subtitle,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
FilaCaracteristicaBienvenida(
|
||||||
|
icon: Icons.directions_car_rounded,
|
||||||
|
iconColor: t.liveGreen,
|
||||||
|
titulo: l10n.welcomeBullet2Title,
|
||||||
|
subtitulo: l10n.welcomeBullet2Subtitle,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
FilaCaracteristicaBienvenida(
|
||||||
|
icon: Icons.alarm_rounded,
|
||||||
|
iconColor: t.warmCoral,
|
||||||
|
titulo: l10n.welcomeBullet3Title,
|
||||||
|
subtitulo: l10n.welcomeBullet3Subtitle,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
SizedBox(
|
||||||
|
height: 58,
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: () => _empezar(context),
|
||||||
|
child: Text(
|
||||||
|
l10n.welcomeCtaLabel,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _empezar(BuildContext context) {
|
||||||
|
context.read<EstadoNavegacionRaiz>().irA(RaizPluriWave.escuchar);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One icon-badge + title/subtitle row. Public (not `_FilaCaracteristica`)
|
||||||
|
/// so the structural "exactly 3 feature bullets" guard in
|
||||||
|
/// `pantalla_bienvenida_test.dart` can target it via `find.byType` — the
|
||||||
|
/// same reason WU4 made `FormularioEmisoraPersonalizada` public.
|
||||||
|
class FilaCaracteristicaBienvenida extends StatelessWidget {
|
||||||
|
const FilaCaracteristicaBienvenida({
|
||||||
|
super.key,
|
||||||
|
required this.icon,
|
||||||
|
required this.iconColor,
|
||||||
|
required this.titulo,
|
||||||
|
required this.subtitulo,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final Color iconColor;
|
||||||
|
final String titulo;
|
||||||
|
final String subtitulo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 38,
|
||||||
|
height: 38,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: iconColor.withValues(alpha: 0.16),
|
||||||
|
borderRadius: BorderRadius.circular(11),
|
||||||
|
),
|
||||||
|
child: Icon(icon, size: 20, color: iconColor),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 13),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
titulo,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
subtitulo,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.58),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
| 15 | `feat(grabaciones): add recordings library screen` | 3b | ~~300-400~~ → **REALIZED: 1,767** (1,767+ / 0-, 22 files) | Medium | Monitor§ |
|
| 15 | `feat(grabaciones): add recordings library screen` | 3b | ~~300-400~~ → **REALIZED: 1,767** (1,767+ / 0-, 22 files) | Medium | Monitor§ |
|
||||||
| 15b | `fix(grabaciones): wire the recordings library into Settings navigation` | 15 | 60-100 | Low | No |
|
| 15b | `fix(grabaciones): wire the recordings library into Settings navigation` | 15 | 60-100 | Low | No |
|
||||||
| 16 | `feat(connectivity): restyle offline and reconnect banners` | 1 | 150-250 | Low | No |
|
| 16 | `feat(connectivity): restyle offline and reconnect banners` | 1 | 150-250 | Low | No |
|
||||||
| 17 | `feat(bienvenida): add monetization-free welcome screen` | 1 | 150-200 | Low | No |
|
| 17 | `feat(bienvenida): add monetization-free welcome screen` | 1 | ~~150-200~~ → **REALIZED: 361** (screen + test, 313 / ARB 22 / docs 48) — see the WU15-precedent note below | Low | No — same reasoning as WU15§ |
|
||||||
| 18 | `feat(i18n): add redesign strings and translate Escuchar rename to 11 locales` | all | ~0 eng. / 400-600 data | Medium (data volume, low logic risk) | No |
|
| 18 | `feat(i18n): add redesign strings and translate Escuchar rename to 11 locales` | all | ~0 eng. / 400-600 data | Medium (data volume, low logic risk) | No |
|
||||||
|
|
||||||
\* **Re-derived after WU3a landed.** The original 500-700 figure was scaled pro-rata from WU3a's *estimate*, which
|
\* **Re-derived after WU3a landed.** The original 500-700 figure was scaled pro-rata from WU3a's *estimate*, which
|
||||||
@@ -85,6 +85,11 @@ unlike WU3a/WU3b's move-only reuse), or (c) the new `servicio_grabacion_radio.da
|
|||||||
group. Realized 1,767 changed lines / 22 files, all additions (no deletions — nothing pre-existing was touched
|
group. Realized 1,767 changed lines / 22 files, all additions (no deletions — nothing pre-existing was touched
|
||||||
beyond the 3 new `ServicioGrabacionRadio`/`EstadoGrabacion` methods). Not recorded as `size:exception` since the
|
beyond the 3 new `ServicioGrabacionRadio`/`EstadoGrabacion` methods). Not recorded as `size:exception` since the
|
||||||
commit is still a single, cleanly-scoped deliverable (one new screen, its one dependency, no split candidate).
|
commit is still a single, cleanly-scoped deliverable (one new screen, its one dependency, no split candidate).
|
||||||
|
**WU17 hit the identical pattern**: 150-200 covered only the screen itself; realized 361 lines across the screen
|
||||||
|
(169) + its test (144) + 9 new ARB keys × 2 locales (22) + this docs update (48), PLUS 13 regenerated
|
||||||
|
`lib/l10n/gen/*.dart` files (~430 more lines, untouched by hand) for a raw `git diff --stat` total of 814. Same
|
||||||
|
ruling: not `size:exception` — one new, cleanly-scoped screen with no split candidate, all-additions except the ARB
|
||||||
|
files (which gain lines, not churn).
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Decision needed before apply: No
|
Decision needed before apply: No
|
||||||
@@ -1013,19 +1018,45 @@ real capability living in the header, not decorative chrome).
|
|||||||
**Verify**: `flutter test test/pantallas/pantalla_bienvenida_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
**Verify**: `flutter test test/pantallas/pantalla_bienvenida_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||||||
**New tests**: `pantalla_bienvenida_test.dart`
|
**New tests**: `pantalla_bienvenida_test.dart`
|
||||||
|
|
||||||
- [ ] 17.1 RED — the welcome screen renders as a full-screen route (not a modal `Dialog`) with a logo, headline,
|
- [x] 17.1 RED — the welcome screen renders as a full-screen route (not a modal `Dialog`) with a logo, headline,
|
||||||
body copy, exactly 3 feature bullets, and exactly 1 primary CTA.
|
body copy, exactly 3 feature bullets, and exactly 1 primary CTA. **Discovered at apply time**: a pre-existing,
|
||||||
- [ ] 17.2 RED — binding no-monetization guard: scan the rendered widget tree's text content and assert it contains
|
UNRELATED `PluriOnboardingDialog` (a "what's new"/help-content modal loaded from markdown assets, shown from
|
||||||
|
`app.dart`'s launch flow) already exists — it is NOT the mockup's welcome screen and is not touched or
|
||||||
|
replaced by this WU.
|
||||||
|
- [x] 17.2 RED — binding no-monetization guard: scan the rendered widget tree's text content and assert it contains
|
||||||
none of: the substring "PRO", a currency amount, or a day-count trial phrase; no secondary "free version"
|
none of: the substring "PRO", a currency amount, or a day-count trial phrase; no secondary "free version"
|
||||||
link.
|
link.
|
||||||
- [ ] 17.3 RED — tapping "Empezar a escuchar" lands on the Escuchar tab (index 0) with the welcome route removed
|
- [x] 17.3 RED — tapping "Empezar a escuchar" lands on the Escuchar tab (index 0) with the welcome route removed
|
||||||
from the back stack.
|
from the back stack. Test starts `EstadoNavegacionRaiz` on a DIFFERENT tab first so a pass actually proves
|
||||||
- [ ] 17.4 GREEN — build `lib/pantallas/pantalla_bienvenida.dart` as a full-screen route; content only (logo /
|
`irA()` ran, not just that escuchar was already the default.
|
||||||
headline / body / 3 bullets / 1 CTA) — no PRO pill, no trial duration, no price, no secondary link.
|
- [x] 17.4 GREEN — built `lib/pantallas/pantalla_bienvenida.dart` as a full-screen route (bare `Scaffold`, no
|
||||||
- [ ] 17.5 GREEN — wire the CTA to dismiss-and-navigate to Escuchar with the route removed.
|
`PluriPushScaffold` — this is an entry screen with no back affordance, not a second-level pushed screen);
|
||||||
- [ ] 17.6 REFACTOR — grep `lib/l10n/app_*.arb` for "PRO"/price/day-count trial strings introduced by this WU;
|
content only (logo / headline / body / 3 bullets / 1 CTA) — no PRO pill, no trial duration, no price, no
|
||||||
confirm none exist.
|
secondary link. Content is the mockup's screen 14 copy verbatim MINUS its entire monetization block (the "14
|
||||||
- [ ] 17.7 Verify — all 3 scenario tests green; grep scan clean.
|
días PRO gratis / luego 2,99 €/año" pricing card and the "Seguir con la versión gratuita" link), Spanish
|
||||||
|
re-cast to the app's established voseo register (the raw mockup HTML uses "tú" form; the other ~750 lines of
|
||||||
|
`app_es.arb` consistently use voseo — e.g. "Elegí", "Guardá", "querés" — matched that instead of copying the
|
||||||
|
mockup's grammar verbatim) and "auto" instead of the mockup's "coche" (matching the one existing precedent at
|
||||||
|
`localMusicSectionDescription`). The 3 bullet icon colours reuse existing named tokens
|
||||||
|
(`electricMagenta`/`liveGreen`/`warmCoral`) that happen to be the exact hex values the mockup already used for
|
||||||
|
those 3 icons — no new raw `Color(0x...)` literal introduced. `SingleChildScrollView` used instead of a
|
||||||
|
`Column` + `Spacer()` (the exact overflow hazard WU14 already hit and fixed) since this screen carries
|
||||||
|
meaningfully more content than a typical card. 9 new ARB keys, en/es only (`welcomeHeadline`, `welcomeBody`,
|
||||||
|
`welcomeBullet{1,2,3}{Title,Subtitle}`, `welcomeCtaLabel`).
|
||||||
|
- [x] 17.5 GREEN — CTA wired to `context.read<EstadoNavegacionRaiz>().irA(RaizPluriWave.escuchar)` then
|
||||||
|
`Navigator.of(context).pop()`. **Scope note**: this WU's own verify command and task list only cover
|
||||||
|
`pantalla_bienvenida_test.dart` — wiring the screen into the REAL first-launch flow (i.e. `app.dart`/
|
||||||
|
`main.dart` deciding *when* to push it, analogous to `ServicioContenidoApp.debeMostrarInicio()`'s pattern for
|
||||||
|
`PluriOnboardingDialog`) is NOT part of WU17 as scoped and was deliberately not added here to avoid inventing
|
||||||
|
untested persistence/launch semantics. Flagged as a likely WU17b gap (same shape as WU15/WU15b).
|
||||||
|
- [x] 17.6 REFACTOR — whole-repo grep confirmed clean: `\bPRO\b` (word-boundary) has exactly one hit in all of
|
||||||
|
`lib/` — this WU's own doc comment describing the guard (never rendered). The unqualified substring "PRO"
|
||||||
|
also matches pre-existing, unrelated `"PROGRAMADOS"` (vacation-alarms section title, WU9) and
|
||||||
|
`pluri_premium_widgets.dart` (a pre-existing decorative-hero-card widget file name, "premium" as in visual
|
||||||
|
styling, not a paid tier) — both false positives for a naive substring check, neither a monetization string.
|
||||||
|
No day-count trial phrase, no €/$ amount, no "gratuita"/"gratis"/"free version" string anywhere in
|
||||||
|
`lib/l10n/app_*.arb` or `lib/`.
|
||||||
|
- [x] 17.7 Verify — all 3 scenario tests green; grep scan clean.
|
||||||
|
|
||||||
## WU18 — i18n batch (all 13 locales)
|
## WU18 — i18n batch (all 13 locales)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pluriwave/estado/estado_navegacion.dart';
|
||||||
|
import 'package:pluriwave/l10n/gen/app_localizations.dart';
|
||||||
|
import 'package:pluriwave/pantallas/pantalla_bienvenida.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
/// WU17: `onboarding-welcome` spec — full-screen route (not a modal),
|
||||||
|
/// content only (logo/headline/body/3 bullets/1 CTA), binding
|
||||||
|
/// no-monetization guard, and CTA dismisses to Escuchar with the route
|
||||||
|
/// popped.
|
||||||
|
Widget _app({required EstadoNavegacionRaiz navegacion}) {
|
||||||
|
return ChangeNotifierProvider<EstadoNavegacionRaiz>.value(
|
||||||
|
value: navegacion,
|
||||||
|
child: const MaterialApp(
|
||||||
|
locale: Locale('es'),
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: PantallaBienvenida(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets(
|
||||||
|
'renders as a full-screen route with a logo, headline, body copy, '
|
||||||
|
'exactly 3 feature bullets, and exactly one primary CTA',
|
||||||
|
(tester) async {
|
||||||
|
final navegacion = EstadoNavegacionRaiz();
|
||||||
|
addTearDown(navegacion.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_app(navegacion: navegacion));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// A full-screen route, never a modal dialog.
|
||||||
|
expect(find.byType(Dialog), findsNothing);
|
||||||
|
expect(find.byType(AlertDialog), findsNothing);
|
||||||
|
expect(find.byType(Scaffold), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.byType(Image), findsOneWidget, reason: 'the logo mark');
|
||||||
|
expect(find.text('Tu mundo, en directo'), findsOneWidget);
|
||||||
|
expect(
|
||||||
|
find.textContaining('53.412 emisoras de 238 países'),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
find.byType(FilaCaracteristicaBienvenida),
|
||||||
|
findsNWidgets(3),
|
||||||
|
reason: 'exactly 3 feature bullets',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
find.byType(FilledButton),
|
||||||
|
findsOneWidget,
|
||||||
|
reason: 'exactly 1 primary CTA',
|
||||||
|
);
|
||||||
|
expect(find.text('Empezar a escuchar'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('rendered content contains no monetization strings', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final navegacion = EstadoNavegacionRaiz();
|
||||||
|
addTearDown(navegacion.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_app(navegacion: navegacion));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final textoCompleto = tester
|
||||||
|
.widgetList<Text>(find.byType(Text))
|
||||||
|
.map((widget) => widget.data ?? '')
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
expect(textoCompleto.contains('PRO'), isFalse);
|
||||||
|
expect(textoCompleto.contains('€'), isFalse);
|
||||||
|
expect(textoCompleto.contains(r'$'), isFalse);
|
||||||
|
expect(
|
||||||
|
RegExp(r'\d+\s*d[ií]as?\b', caseSensitive: false).hasMatch(
|
||||||
|
textoCompleto,
|
||||||
|
),
|
||||||
|
isFalse,
|
||||||
|
reason: 'no day-count trial phrase (e.g. "14 días")',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
find.textContaining('gratuita', findRichText: true),
|
||||||
|
findsNothing,
|
||||||
|
reason: 'no secondary "free version" link',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
find.textContaining('gratis', findRichText: true),
|
||||||
|
findsNothing,
|
||||||
|
reason: 'no secondary "free version" link',
|
||||||
|
);
|
||||||
|
// Only one tappable action on the whole screen: the primary CTA.
|
||||||
|
expect(find.byType(TextButton), findsNothing);
|
||||||
|
expect(find.byType(FilledButton), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'tapping the primary CTA switches to Escuchar and pops the welcome route',
|
||||||
|
(tester) async {
|
||||||
|
// Start on a DIFFERENT tab so a pass actually proves irA() ran,
|
||||||
|
// rather than coasting on EstadoNavegacionRaiz's own escuchar default.
|
||||||
|
final navegacion = EstadoNavegacionRaiz()
|
||||||
|
..irA(RaizPluriWave.ajustes);
|
||||||
|
addTearDown(navegacion.dispose);
|
||||||
|
final navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ChangeNotifierProvider<EstadoNavegacionRaiz>.value(
|
||||||
|
value: navegacion,
|
||||||
|
child: MaterialApp(
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
|
locale: const Locale('es'),
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: const Scaffold(body: Center(child: Text('home stand-in'))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
navigatorKey.currentState!.push(
|
||||||
|
MaterialPageRoute<void>(builder: (_) => const PantallaBienvenida()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(PantallaBienvenida), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Empezar a escuchar'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(navegacion.actual, RaizPluriWave.escuchar);
|
||||||
|
expect(
|
||||||
|
find.byType(PantallaBienvenida),
|
||||||
|
findsNothing,
|
||||||
|
reason: 'the welcome route was removed from the back stack',
|
||||||
|
);
|
||||||
|
expect(find.text('home stand-in'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user