feat(flutter): empty-state copy mirrors web design-system voice

Five terse "No X." strings replaced with the longer actionable forms
the web SPA uses on equivalent surfaces:

- Library Artists/Albums tabs: "No artists yet — scan a library folder
  via the server's config." (mirrors web /library/artists, /albums)
- Library Liked tab: "No liked artists, albums, or tracks yet."
  (consolidates web's per-section copy since Flutter shows all three
  in one tab)
- Search no-results: "No matches for that query." (web has per-section
  copy that doesn't fit Flutter's combined view)
- Discover no-results: "Nothing to add for that search yet." (mirrors
  web /discover)

Hidden tab keeps its current actionable hint (web's "Nothing hidden
yet." is terser but lacks the "use a track's menu to flag" guidance).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 16:06:58 -04:00
parent 231f1ddf9a
commit ccebd98735
2 changed files with 6 additions and 5 deletions
@@ -162,8 +162,9 @@ class _DiscoverScreenState extends ConsumerState<DiscoverScreen> {
final rows = snap.data ?? const []; final rows = snap.data ?? const [];
if (rows.isEmpty) { if (rows.isEmpty) {
return Center( return Center(
child: Text('No matches.', child: Text('Nothing to add for that search yet.',
style: TextStyle(color: fs.ash)), style: TextStyle(color: fs.ash),
textAlign: TextAlign.center),
); );
} }
return ListView.separated( return ListView.separated(
@@ -132,7 +132,7 @@ class _ArtistsTab extends ConsumerWidget {
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('$e', style: TextStyle(color: fs.error))), error: (e, _) => Center(child: Text('$e', style: TextStyle(color: fs.error))),
data: (page) => page.items.isEmpty data: (page) => page.items.isEmpty
? Center(child: Text('No artists.', style: TextStyle(color: fs.ash))) ? Center(child: Text("No artists yet — scan a library folder via the server's config.", style: TextStyle(color: fs.ash), textAlign: TextAlign.center))
: RefreshIndicator( : RefreshIndicator(
onRefresh: () async => ref.refresh(_libraryArtistsProvider.future), onRefresh: () async => ref.refresh(_libraryArtistsProvider.future),
child: GridView.builder( child: GridView.builder(
@@ -164,7 +164,7 @@ class _AlbumsTab extends ConsumerWidget {
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('$e', style: TextStyle(color: fs.error))), error: (e, _) => Center(child: Text('$e', style: TextStyle(color: fs.error))),
data: (page) => page.items.isEmpty data: (page) => page.items.isEmpty
? Center(child: Text('No albums.', style: TextStyle(color: fs.ash))) ? Center(child: Text("No albums yet — scan a library folder via the server's config.", style: TextStyle(color: fs.ash), textAlign: TextAlign.center))
: RefreshIndicator( : RefreshIndicator(
onRefresh: () async => ref.refresh(_libraryAlbumsProvider.future), onRefresh: () async => ref.refresh(_libraryAlbumsProvider.future),
child: GridView.builder( child: GridView.builder(
@@ -241,7 +241,7 @@ class _LikedTab extends ConsumerWidget {
return Center(child: Text("Couldn't load liked items.", style: TextStyle(color: fs.error))); return Center(child: Text("Couldn't load liked items.", style: TextStyle(color: fs.error)));
} }
if (t.items.isEmpty && al.items.isEmpty && ar.items.isEmpty) { if (t.items.isEmpty && al.items.isEmpty && ar.items.isEmpty) {
return Center(child: Text('Nothing liked yet.', style: TextStyle(color: fs.ash))); return Center(child: Text('No liked artists, albums, or tracks yet.', style: TextStyle(color: fs.ash), textAlign: TextAlign.center));
} }
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {