import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart' as shimmer; import '../estado/estado_busqueda.dart'; import '../estado/estado_radio.dart'; import '../l10n/gen/app_localizations.dart'; import '../modelos/emisora.dart'; import '../tema/pluri_animate.dart'; import '../widgets/pluri_glass_surface.dart'; import '../widgets/pluri_icon.dart'; import '../widgets/pluri_layout.dart'; import '../widgets/pluri_premium_widgets.dart'; import '../widgets/pluri_push_scaffold.dart'; import 'package:pluriwave/widgets/tarjeta_emisora.dart'; import 'pantalla_paises.dart'; import 'reproducir_minimizado.dart'; const _paises = [ ('countrySpain', 'ES'), ('countryUsa', 'US'), ('countryMexico', 'MX'), ('countryArgentina', 'AR'), ('countryUk', 'GB'), ('countryFrance', 'FR'), ('countryGermany', 'DE'), ('countryItaly', 'IT'), ('countryBrazil', 'BR'), ('countryJapan', 'JP'), ]; const _idiomas = [ ('spanish', 'languageNameSpanish'), ('english', 'languageNameEnglish'), ('french', 'languageNameFrench'), ('german', 'languageNameGerman'), ('portuguese', 'languageNamePortuguese'), ('italian', 'languageNameItalian'), ('japanese', 'languageNameJapanese'), ('arabic', 'languageNameArabic'), ('russian', 'languageNameRussian'), ]; const _calidades = [ ('64 kbps', 64), ('96 kbps', 96), ('128 kbps', 128), ('192 kbps', 192), ('320 kbps', 320), ]; /// WU6, `station-discovery-browse` spec: Buscar now owns BOTH the discovery /// landing state (relocated from `PantallaInicio`, task 6.5 — `_seccionCercanas`, /// `_seccionTendencias`, `_chipGeneros`, `_errorBanner` and the browse grid /// all moved here verbatim and were deleted from `pantalla_inicio.dart`) and /// the free-text/filtered search results view. [_hayBusquedaActiva] is the /// single switch between the two: empty query AND no active filter shows /// discovery content; any of the three flips to the results view. /// /// Design correction, documented like WU3a's own task 3a.1: the spec's /// landing-state scenario also lists a "Países entry point", but no such /// affordance existed anywhere in `pantalla_inicio.dart` to relocate — /// `PantallaPaises` itself doesn't exist until WU7. That entry point is /// added in WU7 alongside the screen it targets (design's own component /// inventory: "Países (WU7)"), not invented here pointing nowhere. class PantallaBuscar extends StatefulWidget { const PantallaBuscar({super.key}); @override State createState() => _PantallaBuscarState(); } class _PantallaBuscarState extends State { final _controller = TextEditingController(); String? _paisSeleccionado; String? _idiomaSeleccionado; int? _calidadMinima; // Relocated from PantallaInicio (task 6.5) — genre-chip selection only // drives the LANDING grid, same as it always did on Inicio; it is // orthogonal to the free-text/filter search flow below. static const _generos = [ 'pop', 'rock', 'jazz', 'classical', 'electronic', 'news', 'talk', 'hip-hop', 'country', 'metal', 'reggae', 'latin', ]; String? _generoSeleccionado; int get _filtrosActivosCount => (_paisSeleccionado != null ? 1 : 0) + (_idiomaSeleccionado != null ? 1 : 0) + (_calidadMinima != null ? 1 : 0); bool get _hayBusquedaActiva => _controller.text.trim().isNotEmpty || _filtrosActivosCount > 0; @override void dispose() { _controller.dispose(); super.dispose(); } void _buscar() { final q = _controller.text.trim(); context.read().buscar( nombre: q.isNotEmpty ? q : null, pais: _paisSeleccionado, idioma: _idiomaSeleccionado, minBitrate: _calidadMinima, ); } void _quitarTodosLosFiltros() { setState(() { _paisSeleccionado = null; _idiomaSeleccionado = null; _calidadMinima = null; }); _buscar(); } @override Widget build(BuildContext context) { // S4-R3/S4-R5: this screen depends only on search state, so it watches // the dedicated notifier — playback events no longer rebuild it. final estado = context.watch(); final theme = Theme.of(context); final l10n = AppLocalizations.of(context); return ListView( padding: PluriLayout.pageListPadding, children: [ PluriScreenHeader( title: l10n.searchScreenTitle, subtitle: l10n.searchScreenSubtitle, glyph: PluriIconGlyph.search, trailing: GestureDetector( onTap: _abrirFiltros, child: PluriStatusPill( icon: Icons.tune_rounded, label: l10n.searchFiltersLabel, ), ), ), Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 10, PluriLayout.horizontal, 0, ), child: PluriGlassSurface( padding: const EdgeInsets.all(10), borderRadius: BorderRadius.circular(999), child: SearchBar( controller: _controller, hintText: l10n.searchHint, leading: const PluriIcon( glyph: PluriIconGlyph.search, variant: PluriIconVariant.filled, ), trailing: [ if (_controller.text.isNotEmpty) IconButton( icon: const Icon(Icons.clear), onPressed: () { _controller.clear(); setState(() {}); _buscar(); }, ), ], onSubmitted: (_) => _buscar(), onChanged: (_) => setState(() {}), ), ), ), if (_hayBusquedaActiva) ...[ _barraFiltrosActivos(context, estado, theme), _resultados(estado, theme), ] else ...[ _seccionCercanas(context, theme, l10n), _seccionTendencias(context, theme, l10n), _chipGeneros(context, theme, l10n), _seccionPaises(context, theme, l10n), if (context.select((e) => e.error) != null) _errorBanner( context, context.select((e) => e.error)!, theme, l10n, ), _gridEmisoras(context, l10n), ], ], ); } // ── Active-filter pills, results counter, sort (task 6.6/6.7) ────────── Widget _barraFiltrosActivos( BuildContext context, EstadoBusqueda estado, ThemeData theme, ) { final l10n = AppLocalizations.of(context); final pills = [ if (_paisSeleccionado != null) _pillFiltro(_paisLabelSeleccionado(l10n) ?? _paisSeleccionado!, () { setState(() => _paisSeleccionado = null); _buscar(); }), if (_idiomaSeleccionado != null) _pillFiltro(_idiomaLabelSeleccionado(l10n) ?? _idiomaSeleccionado!, () { setState(() => _idiomaSeleccionado = null); _buscar(); }), if (_calidadMinima != null) _pillFiltro('≥$_calidadMinima kbps', () { setState(() => _calidadMinima = null); _buscar(); }), ]; if (pills.isEmpty && estado.resultados.isEmpty) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 10, PluriLayout.horizontal, 0, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (pills.isNotEmpty) Wrap(spacing: 8, runSpacing: 8, children: pills), if (pills.isNotEmpty && estado.resultados.isNotEmpty) const SizedBox(height: 10), if (!estado.cargando && estado.resultados.isNotEmpty) Row( children: [ Text( l10n.searchResultsCount(estado.resultados.length), style: theme.textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w800, ), ), const Spacer(), PopupMenuButton( icon: const Icon(Icons.swap_vert_rounded), tooltip: l10n.stationOrderTitle, onSelected: (criterio) => context .read() .cambiarOrdenListas(criterio), itemBuilder: (context) => [ PopupMenuItem( value: OrdenEmisoras.nombre, child: Text(l10n.stationOrderByName), ), PopupMenuItem( value: OrdenEmisoras.calidad, child: Text(l10n.stationOrderByQuality), ), PopupMenuItem( value: OrdenEmisoras.popularidad, child: Text(l10n.stationOrderByPopularity), ), ], ), ], ), ], ), ); } Widget _pillFiltro(String label, VoidCallback onDeleted) { return Chip( label: Text(label), onDeleted: onDeleted, deleteIcon: const Icon(Icons.close, size: 18), visualDensity: VisualDensity.compact, ); } String? _paisLabelSeleccionado(AppLocalizations l10n) { for (final p in _paises) { if (p.$2 == _paisSeleccionado) return _countryLabel(l10n, p.$1); } return null; } String? _idiomaLabelSeleccionado(AppLocalizations l10n) { for (final i in _idiomas) { if (i.$1 == _idiomaSeleccionado) return _languageLabel(l10n, i.$2); } return null; } /// Opens the filter picker as a bottom sheet (task 6.6 — "collapse the 3 /// always-visible FilterChip rows into ... bottom-sheet pickers"). Each /// chip selection applies immediately and closes the sheet, mirroring the /// established "tap once, sheet closes" precedent already used by /// `pantalla_favoritos.dart`'s `_FilaFavorito._asignar` (WU4) — this /// avoids needing a `StatefulBuilder` to keep the sheet's own chip /// selection visually live while it stays open. Future _abrirFiltros() async { final l10n = AppLocalizations.of(context); await showModalBottomSheet( context: context, showDragHandle: true, isScrollControlled: true, builder: (ctx) => SafeArea( child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 4, PluriLayout.horizontal, 0, ), child: Text( l10n.searchFiltersLabel, style: Theme.of(ctx).textTheme.titleLarge, ), ), _seccionFiltro( l10n.searchCountryFilterLabel, _paises .map((p) => (_countryLabel(l10n, p.$1), p.$2)) .toList(), _paisSeleccionado, (v) { setState(() => _paisSeleccionado = v); _buscar(); Navigator.of(ctx).pop(); }, ), _seccionFiltro( l10n.searchLanguageFilterLabel, _idiomas .map((i) => (_languageLabel(l10n, i.$2), i.$1)) .toList(), _idiomaSeleccionado, (v) { setState(() => _idiomaSeleccionado = v); _buscar(); Navigator.of(ctx).pop(); }, ), _seccionFiltroInt( l10n.searchMinQualityFilterLabel, _calidades, _calidadMinima, (v) { setState(() => _calidadMinima = v); _buscar(); Navigator.of(ctx).pop(); }, ), ], ), ), ), ), ); } /// Renders every option as a `Wrap` of `FilterChip`s — deliberately NOT a /// horizontal `ListView` (the shape this used to have when these rows were /// always-visible inline strips, before task 6.6 moved them into this /// bottom sheet). Reason found at apply time: a horizontal `ListView` is /// lazily built by viewport, and nested inside the sheet's /// `SingleChildScrollView` its viewport-based build only ever realised the /// first few chips per row (confirmed empirically — country's 10 options /// built, but quality's 5th option, "320 kbps", silently never did). A /// `Wrap` lays out every child eagerly, has no lazy-build boundary, and /// suits a bottom sheet (vertical room to spare) better than a horizontal /// scroll strip anyway. Widget _seccionFiltro( String titulo, List<(String, String)> opciones, String? seleccionado, void Function(String?) onChanged, ) { final theme = Theme.of(context); return Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 8, PluriLayout.horizontal, 0, ), child: PluriGlassSurface( padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titulo, style: theme.textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w900, ), ), const SizedBox(height: 6), Wrap( spacing: 8, runSpacing: 8, children: [ for (final (label, value) in opciones) FilterChip( label: Text(label), selected: seleccionado == value, visualDensity: VisualDensity.compact, onSelected: (_) => onChanged(seleccionado == value ? null : value), ), ], ), ], ), ), ); } Widget _seccionFiltroInt( String titulo, List<(String, int)> opciones, int? seleccionado, void Function(int?) onChanged, ) { final theme = Theme.of(context); return Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 8, PluriLayout.horizontal, 0, ), child: PluriGlassSurface( padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titulo, style: theme.textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w900, ), ), const SizedBox(height: 6), Wrap( spacing: 8, runSpacing: 8, children: [ for (final (label, value) in opciones) FilterChip( label: Text(label), selected: seleccionado == value, visualDensity: VisualDensity.compact, onSelected: (_) => onChanged(seleccionado == value ? null : value), ), ], ), ], ), ), ); } Widget _resultados(EstadoBusqueda estado, ThemeData theme) { final l10n = AppLocalizations.of(context); if (estado.cargando) { // S5-R6: shimmer placeholders instead of a bare spinner, consistent // with the loading pattern used by the home grid. return Padding( padding: const EdgeInsets.all(PluriLayout.horizontal), child: Column( children: [ for (var i = 0; i < 4; i++) ...[ const TarjetaEmisoraShimmer(esCompacta: true), if (i < 3) const SizedBox(height: 10), ], ], ), ); } final resultados = estado.resultados; if (resultados.isEmpty) { final sinFiltros = _controller.text.isEmpty && _filtrosActivosCount == 0; return Column( children: [ SizedBox( height: 260, child: PluriEmptyState( glyph: PluriIconGlyph.search, title: sinFiltros ? l10n.searchEmptyTitle : l10n.searchNoResultsTitle, subtitle: sinFiltros ? l10n.searchEmptySubtitle : l10n.searchNoResultsSubtitle, ), ), // task 6.6 / spec "One-Tap Clear-All-Filters on Empty Results": // only offered once 1+ pill-filters are active AND the search // came back empty — not merely "no query typed yet". if (_filtrosActivosCount > 0) Padding( padding: const EdgeInsets.only(bottom: 16), child: OutlinedButton( onPressed: _quitarTodosLosFiltros, child: Text( l10n.searchClearFiltersAction(_filtrosActivosCount), ), ), ), ], ); } final total = resultados.length + (estado.hayMas ? 1 : 0); return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(PluriLayout.horizontal), itemCount: total, separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, i) { if (i >= resultados.length) { if (!estado.cargandoMas) { Future.microtask(estado.cargarMas); } return const Padding( padding: EdgeInsets.all(18), child: Center(child: CircularProgressIndicator()), ); } if (i >= resultados.length - 5 && estado.hayMas) { Future.microtask(estado.cargarMas); } return TarjetaEmisora( emisora: resultados[i], esCompacta: true, onTap: () => reproducirMinimizado(context, resultados[i]), ).pluriFadeSlideIn( context, delay: Duration(milliseconds: i.clamp(0, 12) * 20), beginY: 0.08, ); }, ); } String _countryLabel(AppLocalizations l10n, String key) => switch (key) { 'countrySpain' => l10n.countrySpain, 'countryUsa' => l10n.countryUsa, 'countryMexico' => l10n.countryMexico, 'countryArgentina' => l10n.countryArgentina, 'countryUk' => l10n.countryUk, 'countryFrance' => l10n.countryFrance, 'countryGermany' => l10n.countryGermany, 'countryItaly' => l10n.countryItaly, 'countryBrazil' => l10n.countryBrazil, 'countryJapan' => l10n.countryJapan, _ => key, }; String _languageLabel(AppLocalizations l10n, String key) => switch (key) { 'languageNameSpanish' => l10n.languageNameSpanish, 'languageNameEnglish' => l10n.languageNameEnglish, 'languageNameFrench' => l10n.languageNameFrench, 'languageNameGerman' => l10n.languageNameGerman, 'languageNamePortuguese' => l10n.languageNamePortuguese, 'languageNameItalian' => l10n.languageNameItalian, 'languageNameJapanese' => l10n.languageNameJapanese, 'languageNameArabic' => l10n.languageNameArabic, 'languageNameRussian' => l10n.languageNameRussian, _ => key, }; // ── Discovery landing state (relocated from PantallaInicio, task 6.5) ── Widget _seccionCercanas( BuildContext context, ThemeData theme, AppLocalizations l10n, ) { // Nearby stations live in EstadoBusqueda (S4-R3). final busqueda = context.watch(); final pais = busqueda.paisCercanoDetectado; return Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 8, PluriLayout.horizontal, 0, ), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( pais == null ? l10n.nearYou : l10n.nearYouInCountry(pais), style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, ), ), ), TextButton.icon( onPressed: busqueda.cargandoCercanas ? null : busqueda.cargarEmisorasCercanas, icon: busqueda.cargandoCercanas ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.my_location_rounded, size: 18), label: Text(l10n.detectAction), ), ], ), if (busqueda.errorCercanas != null) Text( busqueda.errorCercanas!, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.error, ), ), if (busqueda.cercanas.isNotEmpty) ...[ const SizedBox(height: 8), SizedBox( height: 76, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: busqueda.cercanas.length, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (context, i) { final emisora = busqueda.cercanas[i]; return SizedBox( width: 260, child: TarjetaEmisora( emisora: emisora, esCompacta: true, onTap: () => reproducirMinimizado(context, emisora), ), ); }, ), ), ], ], ), ), ); } Widget _seccionTendencias( BuildContext context, ThemeData theme, AppLocalizations l10n, ) { final cargando = context.select( (e) => e.cargandoPopulares, ); final tendencias = context.select>( (e) => e.tendencias, ); return Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 8, PluriLayout.horizontal, 0, ), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.liveRadar, style: theme.textTheme.titleMedium), const SizedBox(height: 8), SizedBox( height: 56, child: cargando ? ListView.separated( scrollDirection: Axis.horizontal, itemCount: 5, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (_, __) => _ChipShimmer(theme: theme), ) : ListView.separated( scrollDirection: Axis.horizontal, itemCount: tendencias.length, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (context, i) { final e = tendencias[i]; return ActionChip( avatar: const Icon( Icons.graphic_eq_rounded, size: 18, ), label: Text(e.nombre, maxLines: 1), onPressed: () => reproducirMinimizado(context, e), ).pluriFadeIn( context, delay: Duration(milliseconds: i * 50), ); }, ), ), ], ), ), ); } Widget _chipGeneros( BuildContext context, ThemeData theme, AppLocalizations l10n, ) { return Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 16, PluriLayout.horizontal, 8, ), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.genresTitle, style: theme.textTheme.titleMedium), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 4, children: _generos.map((g) { final seleccionado = _generoSeleccionado == g; return FilterChip( label: Text(_genreName(l10n, g)), selected: seleccionado, onSelected: (_) { setState(() { _generoSeleccionado = seleccionado ? null : g; }); if (!seleccionado) { context.read().buscar(tag: g); } else { context.read().cargarPopulares(); } }, ); }).toList(), ), ], ), ), ); } /// WU7, `station-discovery-browse` spec: the "Países entry point" the /// spec's landing-state scenario lists. Deferred from WU6 (this class's /// own doc comment: `PantallaPaises` did not exist yet); added here now /// that it does, alongside the screen it targets — an unreachable screen /// would repeat the WU15/WU15b lesson (a fully-tested screen shipped with /// no navigation path to it). Widget _seccionPaises( BuildContext context, ThemeData theme, AppLocalizations l10n, ) { return Padding( padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 8, PluriLayout.horizontal, 0, ), child: PluriGlassSurface( padding: EdgeInsets.zero, // `PluriGlassSurface` paints via `DecoratedBox`, not `Material` — a // tappable `ListTile` needs its own `Material` ancestor or its ink // splash silently fails to paint (Flutter's own debug assertion). child: Material( type: MaterialType.transparency, child: ListTile( leading: const Icon(Icons.public_rounded), title: Text(l10n.countriesScreenTitle), trailing: const Icon(Icons.chevron_right_rounded), onTap: () => PluriPushScaffold.push( context, (_) => const PantallaPaises(), ), ), ), ), ); } Widget _errorBanner( BuildContext context, String error, ThemeData theme, AppLocalizations l10n, ) { return Padding( padding: const EdgeInsets.all(16), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Row( children: [ Icon(Icons.wifi_off, color: theme.colorScheme.error), const SizedBox(width: 8), Expanded(child: Text(error)), TextButton( onPressed: () => context.read().cargarPopulares(), child: Text(l10n.retryAction), ), ], ), ), ); } Widget _gridEmisoras(BuildContext context, AppLocalizations l10n) { final porGenero = _generoSeleccionado != null; final emisoras = porGenero ? context.select>((b) => b.resultados) : context.select>( (e) => e.emisorasInicio, ); final cargando = context.select((e) => e.cargandoPopulares) || (porGenero && context.select((b) => b.cargando)); if (cargando) { return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 0, PluriLayout.horizontal, PluriLayout.compactGap, ), itemCount: 12, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.78, crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemBuilder: (_, __) => const TarjetaEmisoraShimmer(), ); } if (emisoras.isEmpty) { return SizedBox( height: 260, child: PluriEmptyState( glyph: PluriIconGlyph.home, title: l10n.noStationsAvailable, subtitle: l10n.noStationsAvailableSubtitle, ), ); } return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB( PluriLayout.horizontal, 0, PluriLayout.horizontal, PluriLayout.compactGap, ), itemCount: emisoras.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.78, crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemBuilder: (context, i) => TarjetaEmisora( emisora: emisoras[i], onTap: () => reproducirMinimizado(context, emisoras[i]), ).pluriFadeSlideIn( context, delay: Duration(milliseconds: i * 30), beginY: 0.1, ), ); } } String _genreName(AppLocalizations l10n, String tag) => switch (tag) { 'pop' => l10n.genrePop, 'rock' => l10n.genreRock, 'jazz' => l10n.genreJazz, 'classical' => l10n.genreClassical, 'electronic' => l10n.genreElectronic, 'news' => l10n.genreNews, 'talk' => l10n.genreTalk, 'hip-hop' => l10n.genreHipHop, 'country' => l10n.genreCountry, 'metal' => l10n.genreMetal, 'reggae' => l10n.genreReggae, 'latin' => l10n.genreLatin, _ => tag, }; class _ChipShimmer extends StatelessWidget { final ThemeData theme; const _ChipShimmer({required this.theme}); @override Widget build(BuildContext context) { return shimmer.Shimmer.fromColors( baseColor: theme.colorScheme.surfaceContainerHighest, highlightColor: theme.colorScheme.surface, child: Container( width: 120, height: 56, decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), ), ); } }