mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-02 09:08:35 +02:00
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.
This commit is contained in:
@@ -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';
|
||||
|
||||
+46
-40
@@ -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<QueueTab> {
|
||||
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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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<ScrollEdgeFade> createState() => _ScrollEdgeFadeState();
|
||||
@@ -16,7 +24,7 @@ class _ScrollEdgeFadeState extends State<ScrollEdgeFade> {
|
||||
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<ScrollEdgeFade> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = Theme.of(context).scaffoldBackgroundColor;
|
||||
final horizontal = widget.axis == Axis.horizontal;
|
||||
return Stack(
|
||||
children: [
|
||||
NotificationListener<ScrollMetricsNotification>(
|
||||
@@ -35,20 +44,26 @@ class _ScrollEdgeFadeState extends State<ScrollEdgeFade> {
|
||||
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],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user