mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-29 07:18:49 +02:00
The local/downloaded album screens had drifted copies of the album header, selection bottom bar, disc chip, batch convert/ReplayGain trio, and assorted small helpers. All now delegate to the shared widgets, with the online album screen's design as the reference, and both album screens run batch actions through the queue_tab engine via UnifiedLibraryItem (strict-superset implementation covering both DB writebacks and SAF paths). Also folds the remaining per-screen helper copies (cover URL, error card, byte/clock formatting, readPositiveInt) into their shared homes. Intentional deltas: selection-bar strings follow queue_tab's l10n keys, both providers reload after a conversion, and audio-analysis durations round instead of floor.
147 lines
4.0 KiB
Dart
147 lines
4.0 KiB
Dart
part of 'queue_tab.dart';
|
|
|
|
class _QueueItemSliverRow extends ConsumerWidget {
|
|
final String itemId;
|
|
final ColorScheme colorScheme;
|
|
final Widget Function(BuildContext, DownloadItem, ColorScheme) itemBuilder;
|
|
|
|
const _QueueItemSliverRow({
|
|
super.key,
|
|
required this.itemId,
|
|
required this.colorScheme,
|
|
required this.itemBuilder,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final item = ref.watch(
|
|
downloadQueueLookupProvider.select((lookup) => lookup.byItemId[itemId]),
|
|
);
|
|
if (item == null) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return RepaintBoundary(child: itemBuilder(context, item, colorScheme));
|
|
}
|
|
}
|
|
|
|
enum _CollectionEntryType { wishlist, loved, favoriteArtists, playlist }
|
|
|
|
class _CollectionEntry {
|
|
final _CollectionEntryType type;
|
|
final int playlistIndex;
|
|
|
|
const _CollectionEntry._(this.type, [this.playlistIndex = -1]);
|
|
|
|
static const wishlist = _CollectionEntry._(_CollectionEntryType.wishlist);
|
|
static const loved = _CollectionEntry._(_CollectionEntryType.loved);
|
|
static const favoriteArtists = _CollectionEntry._(
|
|
_CollectionEntryType.favoriteArtists,
|
|
);
|
|
static _CollectionEntry playlist(int index) =>
|
|
_CollectionEntry._(_CollectionEntryType.playlist, index);
|
|
}
|
|
|
|
class _FilterChip extends StatelessWidget {
|
|
final String label;
|
|
final int count;
|
|
final bool isSelected;
|
|
final VoidCallback onTap;
|
|
|
|
const _FilterChip({
|
|
required this.label,
|
|
required this.count,
|
|
required this.isSelected,
|
|
required this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return FilterChip(
|
|
label: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(label),
|
|
const SizedBox(width: 6),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? colorScheme.primary.withValues(alpha: 0.2)
|
|
: colorScheme.outline.withValues(alpha: 0.2),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Text(
|
|
count.toString(),
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: isSelected
|
|
? colorScheme.onPrimaryContainer
|
|
: colorScheme.onSurfaceVariant,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
selected: isSelected,
|
|
onSelected: (_) => onTap(),
|
|
showCheckmark: false,
|
|
backgroundColor: settingsGroupColor(context),
|
|
side: BorderSide(color: colorScheme.outlineVariant.withValues(alpha: 0.6)),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _AnimatedOverlayBottomBar extends StatefulWidget {
|
|
final Widget child;
|
|
|
|
const _AnimatedOverlayBottomBar({required this.child});
|
|
|
|
@override
|
|
State<_AnimatedOverlayBottomBar> createState() =>
|
|
_AnimatedOverlayBottomBarState();
|
|
}
|
|
|
|
class _AnimatedOverlayBottomBarState extends State<_AnimatedOverlayBottomBar>
|
|
with SingleTickerProviderStateMixin {
|
|
late final AnimationController _controller;
|
|
late final Animation<Offset> _slideAnimation;
|
|
late final Animation<double> _fadeAnimation;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 240),
|
|
);
|
|
final curve = CurvedAnimation(
|
|
parent: _controller,
|
|
curve: Curves.easeOutCubic,
|
|
);
|
|
_slideAnimation = Tween<Offset>(
|
|
begin: const Offset(0, 0.08),
|
|
end: Offset.zero,
|
|
).animate(curve);
|
|
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(curve);
|
|
_controller.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return FadeTransition(
|
|
opacity: _fadeAnimation,
|
|
child: SlideTransition(position: _slideAnimation, child: widget.child),
|
|
);
|
|
}
|
|
}
|