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:
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user