From cc93c5020d743e832275ca622273c57f506741f8 Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 29 Jul 2026 19:27:30 +0700 Subject: [PATCH] feat(settings): polish search and navigation layout --- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_de.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/app_localizations_es.dart | 4 +- lib/l10n/app_localizations_fr.dart | 2 +- lib/l10n/app_localizations_id.dart | 2 +- lib/l10n/app_localizations_ja.dart | 2 +- lib/l10n/app_localizations_ko.dart | 2 +- lib/l10n/app_localizations_pt.dart | 4 +- lib/l10n/app_localizations_ru.dart | 2 +- lib/l10n/app_localizations_tr.dart | 2 +- lib/l10n/app_localizations_uk.dart | 2 +- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/arb/app_en.arb | 2 +- lib/l10n/arb/app_es.arb | 2 +- lib/l10n/arb/app_es_ES.arb | 2 +- lib/l10n/arb/app_fr.arb | 2 +- lib/l10n/arb/app_id.arb | 2 +- lib/l10n/arb/app_ja.arb | 2 +- lib/l10n/arb/app_ko.arb | 2 +- lib/l10n/arb/app_pt.arb | 2 +- lib/l10n/arb/app_pt_PT.arb | 2 +- lib/l10n/arb/app_ru.arb | 2 +- lib/l10n/arb/app_tr.arb | 2 +- lib/l10n/arb/app_uk.arb | 2 +- lib/screens/repo_tab.dart | 63 +++------------------ lib/screens/settings/settings_tab.dart | 71 +++++++++++------------ lib/widgets/app_search_field.dart | 78 ++++++++++++++++++++++++++ 28 files changed, 147 insertions(+), 119 deletions(-) create mode 100644 lib/widgets/app_search_field.dart diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 6537df2d..da6a27ca 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -7885,7 +7885,7 @@ abstract class AppLocalizations { /// Settings group covering look and feel /// /// In en, this message translates to: - /// **'Interface'** + /// **'Extensions & appearance'** String get settingsGroupInterface; /// Settings group covering the library, metadata and lyrics diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 61b609a4..60e552ac 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -4856,7 +4856,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get settingsGroupInterface => 'Oberfläche'; + String get settingsGroupInterface => 'Erweiterungen & Darstellung'; @override String get settingsGroupContent => 'Inhalte & Metadaten'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index ce736629..078f3ae9 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -4811,7 +4811,7 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get settingsGroupInterface => 'Interface'; + String get settingsGroupInterface => 'Extensions & appearance'; @override String get settingsGroupContent => 'Content & metadata'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index ff581936..db39b8b8 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -4805,7 +4805,7 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get settingsGroupInterface => 'Interfaz'; + String get settingsGroupInterface => 'Extensiones y apariencia'; @override String get settingsGroupContent => 'Contenido y metadatos'; @@ -9531,7 +9531,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { } @override - String get settingsGroupInterface => 'Interfaz'; + String get settingsGroupInterface => 'Extensiones y apariencia'; @override String get settingsGroupContent => 'Contenido y metadatos'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 61bc9419..85d2c004 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -4927,7 +4927,7 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get settingsGroupInterface => 'Interface'; + String get settingsGroupInterface => 'Extensions et apparence'; @override String get settingsGroupContent => 'Contenu et métadonnées'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 6ddba19e..23414ab4 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -4809,7 +4809,7 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get settingsGroupInterface => 'Antarmuka'; + String get settingsGroupInterface => 'Ekstensi & tampilan'; @override String get settingsGroupContent => 'Konten & metadata'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 3e1965d1..df83a1a3 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -4800,7 +4800,7 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get settingsGroupInterface => 'インターフェース'; + String get settingsGroupInterface => '拡張機能と外観'; @override String get settingsGroupContent => 'コンテンツとメタデータ'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 51da112f..6584e9fd 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -4681,7 +4681,7 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get settingsGroupInterface => '인터페이스'; + String get settingsGroupInterface => '확장 기능 및 외관'; @override String get settingsGroupContent => '콘텐츠 및 메타데이터'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index d1ceb208..c328fb90 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -4805,7 +4805,7 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get settingsGroupInterface => 'Interface'; + String get settingsGroupInterface => 'Extensões e aparência'; @override String get settingsGroupContent => 'Conteúdo e metadados'; @@ -9496,7 +9496,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { } @override - String get settingsGroupInterface => 'Interface'; + String get settingsGroupInterface => 'Extensões e aparência'; @override String get settingsGroupContent => 'Conteúdo e metadados'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 81ea728f..b9e691ee 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -4842,7 +4842,7 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get settingsGroupInterface => 'Интерфейс'; + String get settingsGroupInterface => 'Расширения и внешний вид'; @override String get settingsGroupContent => 'Контент и метаданные'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index dae2e78b..6b74d3d6 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -4841,7 +4841,7 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get settingsGroupInterface => 'Arayüz'; + String get settingsGroupInterface => 'Uzantılar ve görünüm'; @override String get settingsGroupContent => 'İçerik ve meta veriler'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 61920fce..09aa482e 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -4859,7 +4859,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get settingsGroupInterface => 'Інтерфейс'; + String get settingsGroupInterface => 'Розширення та вигляд'; @override String get settingsGroupContent => 'Вміст і метадані'; diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 3c3ae364..88ce5e5b 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Resuming after verification...", "settingsSearchHint": "Einstellungen durchsuchen", "settingsSearchNoResults": "Keine Einstellungen für \"{query}\" gefunden", - "settingsGroupInterface": "Oberfläche", + "settingsGroupInterface": "Erweiterungen & Darstellung", "settingsGroupContent": "Inhalte & Metadaten", "settingsGroupDownloads": "Downloads & Dateien", "settingsGroupSystem": "System", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index bc735b7e..b9fbc5db 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -6236,7 +6236,7 @@ } } }, - "settingsGroupInterface": "Interface", + "settingsGroupInterface": "Extensions & appearance", "@settingsGroupInterface": { "description": "Settings group covering look and feel" }, diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index c2fa8a4b..77abaa9d 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -4282,7 +4282,7 @@ }, "settingsSearchHint": "Buscar en los ajustes", "settingsSearchNoResults": "Ningún ajuste coincide con \"{query}\"", - "settingsGroupInterface": "Interfaz", + "settingsGroupInterface": "Extensiones y apariencia", "settingsGroupContent": "Contenido y metadatos", "settingsGroupDownloads": "Descargas y archivos", "settingsGroupSystem": "Sistema", diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index 08761050..eccaa24f 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Resuming after verification...", "settingsSearchHint": "Buscar en los ajustes", "settingsSearchNoResults": "Ningún ajuste coincide con \"{query}\"", - "settingsGroupInterface": "Interfaz", + "settingsGroupInterface": "Extensiones y apariencia", "settingsGroupContent": "Contenido y metadatos", "settingsGroupDownloads": "Descargas y archivos", "settingsGroupSystem": "Sistema", diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 11f83fd6..65ec814a 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Reprise après vérification...", "settingsSearchHint": "Rechercher dans les paramètres", "settingsSearchNoResults": "Aucun paramètre ne correspond à « {query} »", - "settingsGroupInterface": "Interface", + "settingsGroupInterface": "Extensions et apparence", "settingsGroupContent": "Contenu et métadonnées", "settingsGroupDownloads": "Téléchargements et fichiers", "settingsGroupSystem": "Système", diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index e0237761..6a6974f5 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -6062,7 +6062,7 @@ }, "settingsSearchHint": "Cari pengaturan", "settingsSearchNoResults": "Tidak ada pengaturan yang cocok dengan \"{query}\"", - "settingsGroupInterface": "Antarmuka", + "settingsGroupInterface": "Ekstensi & tampilan", "settingsGroupContent": "Konten & metadata", "settingsGroupDownloads": "Unduhan & file", "settingsGroupSystem": "Sistem", diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 02db457f..92837503 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Resuming after verification...", "settingsSearchHint": "設定を検索", "settingsSearchNoResults": "「{query}」に一致する設定はありません", - "settingsGroupInterface": "インターフェース", + "settingsGroupInterface": "拡張機能と外観", "settingsGroupContent": "コンテンツとメタデータ", "settingsGroupDownloads": "ダウンロードとファイル", "settingsGroupSystem": "システム", diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 4596b828..d43aab97 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -6021,7 +6021,7 @@ }, "settingsSearchHint": "설정 검색", "settingsSearchNoResults": "\"{query}\"와(과) 일치하는 설정이 없습니다", - "settingsGroupInterface": "인터페이스", + "settingsGroupInterface": "확장 기능 및 외관", "settingsGroupContent": "콘텐츠 및 메타데이터", "settingsGroupDownloads": "다운로드 및 파일", "settingsGroupSystem": "시스템", diff --git a/lib/l10n/arb/app_pt.arb b/lib/l10n/arb/app_pt.arb index 306b0b85..bc28d947 100644 --- a/lib/l10n/arb/app_pt.arb +++ b/lib/l10n/arb/app_pt.arb @@ -4282,7 +4282,7 @@ }, "settingsSearchHint": "Pesquisar configurações", "settingsSearchNoResults": "Nenhuma configuração corresponde a \"{query}\"", - "settingsGroupInterface": "Interface", + "settingsGroupInterface": "Extensões e aparência", "settingsGroupContent": "Conteúdo e metadados", "settingsGroupDownloads": "Downloads e arquivos", "settingsGroupSystem": "Sistema", diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index 961c9990..d12125f0 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Resuming after verification...", "settingsSearchHint": "Pesquisar definições", "settingsSearchNoResults": "Nenhuma definição corresponde a \"{query}\"", - "settingsGroupInterface": "Interface", + "settingsGroupInterface": "Extensões e aparência", "settingsGroupContent": "Conteúdo e metadados", "settingsGroupDownloads": "Transferências e ficheiros", "settingsGroupSystem": "Sistema", diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index adeea79e..cba00585 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Resuming after verification...", "settingsSearchHint": "Поиск в настройках", "settingsSearchNoResults": "Нет настроек, соответствующих запросу «{query}»", - "settingsGroupInterface": "Интерфейс", + "settingsGroupInterface": "Расширения и внешний вид", "settingsGroupContent": "Контент и метаданные", "settingsGroupDownloads": "Загрузки и файлы", "settingsGroupSystem": "Система", diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index 536cc0e7..3e576e8d 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Resuming after verification...", "settingsSearchHint": "Ayarlarda ara", "settingsSearchNoResults": "\"{query}\" ile eşleşen ayar bulunamadı", - "settingsGroupInterface": "Arayüz", + "settingsGroupInterface": "Uzantılar ve görünüm", "settingsGroupContent": "İçerik ve meta veriler", "settingsGroupDownloads": "İndirmeler ve dosyalar", "settingsGroupSystem": "Sistem", diff --git a/lib/l10n/arb/app_uk.arb b/lib/l10n/arb/app_uk.arb index b3d44182..15bf9e98 100644 --- a/lib/l10n/arb/app_uk.arb +++ b/lib/l10n/arb/app_uk.arb @@ -6021,7 +6021,7 @@ "queueResumingAfterVerification": "Resuming after verification...", "settingsSearchHint": "Пошук у налаштуваннях", "settingsSearchNoResults": "Немає налаштувань, що відповідають запиту «{query}»", - "settingsGroupInterface": "Інтерфейс", + "settingsGroupInterface": "Розширення та вигляд", "settingsGroupContent": "Вміст і метадані", "settingsGroupDownloads": "Завантаження та файли", "settingsGroupSystem": "Система", diff --git a/lib/screens/repo_tab.dart b/lib/screens/repo_tab.dart index 77b35917..5154eb79 100644 --- a/lib/screens/repo_tab.dart +++ b/lib/screens/repo_tab.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:spotiflac_android/widgets/extension_row.dart'; import 'package:spotiflac_android/theme/app_tokens.dart'; +import 'package:spotiflac_android/widgets/app_search_field.dart'; import 'package:spotiflac_android/widgets/app_sliver_header.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; @@ -107,60 +108,14 @@ class _RepoTabState extends ConsumerState { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), - child: ValueListenableBuilder( - valueListenable: _searchController, - builder: (context, value, _) { - return TextField( - controller: _searchController, - decoration: InputDecoration( - hintText: context.l10n.storeSearch, - prefixIcon: const Icon(Icons.search), - suffixIcon: value.text.isNotEmpty - ? IconButton( - tooltip: context.l10n.dialogClear, - icon: const Icon(Icons.clear), - onPressed: () { - _searchController.clear(); - ref - .read(repoProvider.notifier) - .setSearchQuery(''); - }, - ) - : null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(28), - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(28), - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(28), - borderSide: BorderSide( - color: colorScheme.primary, - width: 2, - ), - ), - filled: true, - fillColor: settingsGroupColor(context), - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, - ), - ), - onChanged: (value) { - ref.read(repoProvider.notifier).setSearchQuery(value); - }, - onTapOutside: (_) { - FocusScope.of(context).unfocus(); - }, - ); - }, + child: AppSearchField( + controller: _searchController, + hintText: context.l10n.storeSearch, + clearTooltip: context.l10n.dialogClear, + onChanged: (value) => + ref.read(repoProvider.notifier).setSearchQuery(value), + onClear: () => + ref.read(repoProvider.notifier).setSearchQuery(''), ), ), ), diff --git a/lib/screens/settings/settings_tab.dart b/lib/screens/settings/settings_tab.dart index e8587fea..adbe7c45 100644 --- a/lib/screens/settings/settings_tab.dart +++ b/lib/screens/settings/settings_tab.dart @@ -19,6 +19,7 @@ import 'package:spotiflac_android/theme/app_tokens.dart'; import 'package:spotiflac_android/utils/adaptive_layout.dart'; import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/widgets/animation_utils.dart'; +import 'package:spotiflac_android/widgets/app_search_field.dart'; import 'package:spotiflac_android/widgets/app_sliver_header.dart'; import 'package:spotiflac_android/widgets/settings_group.dart'; @@ -48,11 +49,12 @@ class _Destination { } } -/// A labelled group of destinations. +/// A settings group. The optional label lets priority shortcuts sit directly +/// below search without a misleading section heading. class _Group { - const _Group({required this.label, required this.destinations}); + const _Group({this.label, required this.destinations}); - final String label; + final String? label; final List<_Destination> destinations; } @@ -82,9 +84,27 @@ class _SettingsTabState extends ConsumerState { List<_Group> _groups(BuildContext context) { final l10n = context.l10n; return [ + _Group( + destinations: [ + _Destination( + icon: Icons.favorite_outline, + title: l10n.settingsDonate, + subtitle: l10n.settingsDonateSubtitle, + keywords: const ['support', 'ko-fi', 'sponsor'], + pageBuilder: () => const DonatePage(), + ), + ], + ), _Group( label: l10n.settingsGroupInterface, destinations: [ + _Destination( + icon: Icons.extension_outlined, + title: l10n.settingsExtensions, + subtitle: l10n.settingsExtensionsSubtitle, + keywords: const ['plugin', 'provider', 'priority', 'store'], + pageBuilder: () => const ExtensionsPage(), + ), _Destination( icon: Icons.palette_outlined, title: l10n.settingsAppearance, @@ -168,13 +188,6 @@ class _SettingsTabState extends ConsumerState { ], pageBuilder: () => const FilesSettingsPage(), ), - _Destination( - icon: Icons.extension_outlined, - title: l10n.settingsExtensions, - subtitle: l10n.settingsExtensionsSubtitle, - keywords: const ['plugin', 'provider', 'priority', 'store'], - pageBuilder: () => const ExtensionsPage(), - ), ], ), _Group( @@ -213,13 +226,6 @@ class _SettingsTabState extends ConsumerState { _Group( label: l10n.settingsGroupHelp, destinations: [ - _Destination( - icon: Icons.favorite_outline, - title: l10n.settingsDonate, - subtitle: l10n.settingsDonateSubtitle, - keywords: const ['support', 'ko-fi', 'sponsor'], - pageBuilder: () => const DonatePage(), - ), _Destination( icon: Icons.info_outline, title: l10n.settingsAbout, @@ -266,12 +272,13 @@ class _SettingsTabState extends ConsumerState { if (query.isEmpty) { body = [ for (final group in groups) ...[ - SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.only(left: wideInset, right: wideInset), - child: SettingsSectionHeader(title: group.label), + if (group.label != null) + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only(left: wideInset, right: wideInset), + child: SettingsSectionHeader(title: group.label!), + ), ), - ), SliverToBoxAdapter( child: SettingsGroup( margin: margin, @@ -333,24 +340,12 @@ class _SettingsTabState extends ConsumerState { tokens.gapLg + wideInset, tokens.gapSm, ), - child: TextField( + child: AppSearchField( controller: _searchController, - textInputAction: TextInputAction.search, + hintText: context.l10n.settingsSearchHint, + clearTooltip: context.l10n.dialogClear, onChanged: (value) => setState(() => _query = value), - decoration: InputDecoration( - hintText: context.l10n.settingsSearchHint, - prefixIcon: const Icon(Icons.search), - suffixIcon: query.isEmpty - ? null - : IconButton( - tooltip: context.l10n.dialogClear, - icon: const Icon(Icons.close), - onPressed: () { - _searchController.clear(); - setState(() => _query = ''); - }, - ), - ), + onClear: () => setState(() => _query = ''), ), ), ), diff --git a/lib/widgets/app_search_field.dart b/lib/widgets/app_search_field.dart new file mode 100644 index 00000000..9d7a2a4d --- /dev/null +++ b/lib/widgets/app_search_field.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:spotiflac_android/theme/app_tokens.dart'; +import 'package:spotiflac_android/widgets/settings_group.dart'; + +/// The standard search field used by top-level app surfaces. +/// +/// Keeping the decoration here prevents Settings, Store, and future search +/// surfaces from drifting into slightly different shapes and colors. +class AppSearchField extends StatelessWidget { + const AppSearchField({ + super.key, + required this.controller, + required this.hintText, + required this.clearTooltip, + required this.onChanged, + required this.onClear, + this.focusNode, + this.autofocus = false, + }); + + final TextEditingController controller; + final String hintText; + final String clearTooltip; + final ValueChanged onChanged; + final VoidCallback onClear; + final FocusNode? focusNode; + final bool autofocus; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final radius = BorderRadius.circular(context.tokens.radiusSheet); + + return ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, _) => TextField( + controller: controller, + focusNode: focusNode, + autofocus: autofocus, + textInputAction: TextInputAction.search, + decoration: InputDecoration( + hintText: hintText, + prefixIcon: const Icon(Icons.search), + suffixIcon: value.text.isEmpty + ? null + : IconButton( + tooltip: clearTooltip, + icon: const Icon(Icons.clear), + onPressed: () { + controller.clear(); + onClear(); + }, + ), + border: OutlineInputBorder( + borderRadius: radius, + borderSide: BorderSide(color: colorScheme.outlineVariant), + ), + enabledBorder: OutlineInputBorder( + borderRadius: radius, + borderSide: BorderSide(color: colorScheme.outlineVariant), + ), + focusedBorder: OutlineInputBorder( + borderRadius: radius, + borderSide: BorderSide(color: colorScheme.primary, width: 2), + ), + filled: true, + fillColor: settingsGroupColor(context), + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + ), + onChanged: onChanged, + onTapOutside: (_) => FocusScope.of(context).unfocus(), + ), + ); + } +}