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:
2026-07-29 15:04:44 +02:00
parent 862197ab48
commit 9b415e73b0
19 changed files with 820 additions and 14 deletions
+10 -1
View File
@@ -799,5 +799,14 @@
"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
View File
@@ -758,5 +758,14 @@
"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"
}
+54
View File
@@ -2869,6 +2869,60 @@ abstract class AppLocalizations {
/// In es, this message translates to:
/// **'Carpeta seleccionada'**
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
+30
View File
@@ -1580,4 +1580,34 @@ class AppLocalizationsAr extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1588,4 +1588,34 @@ class AppLocalizationsBn extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1599,4 +1599,34 @@ class AppLocalizationsDe extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1581,4 +1581,34 @@ class AppLocalizationsEn extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1594,4 +1594,34 @@ class AppLocalizationsEs extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1603,4 +1603,34 @@ class AppLocalizationsFr extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1586,4 +1586,34 @@ class AppLocalizationsHi extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1592,4 +1592,34 @@ class AppLocalizationsId extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1598,4 +1598,34 @@ class AppLocalizationsIt extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1545,4 +1545,34 @@ class AppLocalizationsJa extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1590,4 +1590,34 @@ class AppLocalizationsPt extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1594,4 +1594,34 @@ class AppLocalizationsRu extends AppLocalizations {
@override
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';
}
+30
View File
@@ -1537,4 +1537,34 @@ class AppLocalizationsZh extends AppLocalizations {
@override
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';
}
+169
View File
@@ -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),
),
),
],
),
),
],
);
}
}