From 3b901461f6b571fbc84af46c6f7a27fe06d96e2a Mon Sep 17 00:00:00 2001 From: zarzet Date: Mon, 27 Jul 2026 02:20:14 +0700 Subject: [PATCH] feat(library): show a trailing fade on the filter chips row Extend ScrollEdgeFade with a horizontal axis and wrap the All/Albums/ Singles/Playlists chips so a right-edge fade signals the row scrolls when chips overflow the screen. --- lib/constants/app_info.dart | 2 +- lib/screens/queue_tab.dart | 86 +++++++++++++++++-------------- lib/widgets/scroll_edge_fade.dart | 37 +++++++++---- 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart index e3f0ce36..5f777d43 100644 --- a/lib/constants/app_info.dart +++ b/lib/constants/app_info.dart @@ -8,7 +8,7 @@ class AppInfo { static String get displayVersion => kDebugMode ? 'Internal' : version; static const String appName = 'SpotiFLAC Mobile'; - static const String copyright = '© 2026 SpotiFLAC'; + static const String copyright = '© 2026 Zarz Eleutherius'; static const String mobileAuthor = 'zarzet'; static const String originalAuthor = 'afkarxyz'; diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 30a67405..e76e0ad9 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -47,6 +47,7 @@ import 'package:spotiflac_android/widgets/animation_utils.dart'; import 'package:spotiflac_android/widgets/selection_action_button.dart'; import 'package:spotiflac_android/widgets/selection_bottom_bar.dart'; import 'package:spotiflac_android/widgets/smoothed_progress.dart'; +import 'package:spotiflac_android/widgets/scroll_edge_fade.dart'; part 'queue_tab_helpers.dart'; part 'queue_tab_widgets.dart'; @@ -1472,46 +1473,51 @@ class _QueueTabState extends ConsumerState { filteredAlbumCount = queueCounts.albumCount; filteredSingleCount = queueCounts.singleTrackCount; - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - _FilterChip( - label: context.l10n.historyFilterAll, - count: filteredAllCount, - isSelected: historyFilterMode == 'all', - onTap: () { - _animateToFilterPage(0); - }, - ), - const SizedBox(width: 8), - _FilterChip( - label: context.l10n.historyFilterAlbums, - count: filteredAlbumCount, - isSelected: historyFilterMode == 'albums', - onTap: () { - _animateToFilterPage(1); - }, - ), - const SizedBox(width: 8), - _FilterChip( - label: context.l10n.historyFilterSingles, - count: filteredSingleCount, - isSelected: historyFilterMode == 'singles', - onTap: () { - _animateToFilterPage(2); - }, - ), - const SizedBox(width: 8), - _FilterChip( - label: context.l10n.searchPlaylists, - count: collectionState.playlists.length, - isSelected: historyFilterMode == 'playlists', - onTap: () { - _animateToFilterPage(3); - }, - ), - ], + return ScrollEdgeFade( + axis: Axis.horizontal, + size: 48, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + _FilterChip( + label: context.l10n.historyFilterAll, + count: filteredAllCount, + isSelected: historyFilterMode == 'all', + onTap: () { + _animateToFilterPage(0); + }, + ), + const SizedBox(width: 8), + _FilterChip( + label: context.l10n.historyFilterAlbums, + count: filteredAlbumCount, + isSelected: historyFilterMode == 'albums', + onTap: () { + _animateToFilterPage(1); + }, + ), + const SizedBox(width: 8), + _FilterChip( + label: context.l10n.historyFilterSingles, + count: filteredSingleCount, + isSelected: historyFilterMode == 'singles', + onTap: () { + _animateToFilterPage(2); + }, + ), + const SizedBox(width: 8), + _FilterChip( + label: context.l10n.searchPlaylists, + count: collectionState.playlists.length, + isSelected: + historyFilterMode == 'playlists', + onTap: () { + _animateToFilterPage(3); + }, + ), + ], + ), ), ); }, diff --git a/lib/widgets/scroll_edge_fade.dart b/lib/widgets/scroll_edge_fade.dart index d59b192f..a50d685b 100644 --- a/lib/widgets/scroll_edge_fade.dart +++ b/lib/widgets/scroll_edge_fade.dart @@ -1,12 +1,20 @@ import 'package:flutter/material.dart'; -/// Overlays a bottom gradient on a vertical scrollable while more content -/// remains below, signalling that the view can scroll. +/// Overlays a gradient on the trailing edge of a scrollable while more +/// content remains beyond it, signalling that the view can scroll. class ScrollEdgeFade extends StatefulWidget { final Widget child; - final double height; + final Axis axis; - const ScrollEdgeFade({super.key, required this.child, this.height = 96}); + /// Thickness of the fade: height for vertical, width for horizontal. + final double size; + + const ScrollEdgeFade({ + super.key, + required this.child, + this.axis = Axis.vertical, + this.size = 96, + }); @override State createState() => _ScrollEdgeFadeState(); @@ -16,7 +24,7 @@ class _ScrollEdgeFadeState extends State { bool _hasMore = false; bool _onMetrics(ScrollMetrics metrics) { - if (metrics.axis == Axis.vertical) { + if (metrics.axis == widget.axis) { final hasMore = metrics.extentAfter > 8; if (hasMore != _hasMore) setState(() => _hasMore = hasMore); } @@ -26,6 +34,7 @@ class _ScrollEdgeFadeState extends State { @override Widget build(BuildContext context) { final background = Theme.of(context).scaffoldBackgroundColor; + final horizontal = widget.axis == Axis.horizontal; return Stack( children: [ NotificationListener( @@ -35,20 +44,26 @@ class _ScrollEdgeFadeState extends State { child: widget.child, ), ), - Positioned( - left: 0, - right: 0, + PositionedDirectional( + start: horizontal ? null : 0, + end: 0, + top: horizontal ? 0 : null, bottom: 0, child: IgnorePointer( child: AnimatedOpacity( opacity: _hasMore ? 1 : 0, duration: const Duration(milliseconds: 200), child: Container( - height: widget.height, + width: horizontal ? widget.size : null, + height: horizontal ? null : widget.size, decoration: BoxDecoration( gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, + begin: horizontal + ? AlignmentDirectional.centerStart + : Alignment.topCenter, + end: horizontal + ? AlignmentDirectional.centerEnd + : Alignment.bottomCenter, colors: [background.withValues(alpha: 0), background], ), ),