From aec7bf7a1b9efae56d2d90a5ebf2f37d533d0646 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 23 Aug 2026 11:36:15 +0700 Subject: [PATCH] fix(library): show complete quality labels --- lib/screens/queue_tab_collection_items.dart | 43 ++++++++++++++------- lib/screens/queue_tab_item_widgets.dart | 6 ++- lib/screens/queue_tab_selection.dart | 22 ----------- lib/utils/audio_quality_badge_policy.dart | 33 ++++++++++++++++ test/audio_quality_badge_policy_test.dart | 20 ++++++++++ 5 files changed, 88 insertions(+), 36 deletions(-) diff --git a/lib/screens/queue_tab_collection_items.dart b/lib/screens/queue_tab_collection_items.dart index 0cf627d6..5ba02eda 100644 --- a/lib/screens/queue_tab_collection_items.dart +++ b/lib/screens/queue_tab_collection_items.dart @@ -146,8 +146,12 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState { if (quality != null && quality.isNotEmpty) Positioned( left: 4, + right: 4, top: 4, - child: _buildLibraryQualityBadge(context, colorScheme, quality), + child: Align( + alignment: Alignment.centerLeft, + child: _buildLibraryQualityBadge(context, colorScheme, quality), + ), ), ], title: trackName, @@ -242,6 +246,24 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState { }) { final tokens = context.tokens; final isHighlightedQuality = shouldHighlightAudioQualityBadge(quality); + final displayQuality = listStyle + ? quality + : formatLibraryGridAudioQualityLabel(quality); + final isDetailed = isDetailedLibraryAudioQualityLabel(displayQuality); + final label = Text( + displayQuality, + maxLines: 1, + softWrap: false, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: isHighlightedQuality + ? listStyle + ? colorScheme.onPrimaryContainer + : colorScheme.onPrimary + : colorScheme.onSurfaceVariant, + fontSize: isDetailed ? tokens.badgeFontSize - 1 : tokens.badgeFontSize, + fontWeight: listStyle ? FontWeight.w500 : FontWeight.w600, + ), + ); return Container( padding: tokens.badgePadding, decoration: BoxDecoration( @@ -252,18 +274,13 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState { : colorScheme.surfaceContainerHighest, borderRadius: tokens.borderRadiusBadge, ), - child: Text( - listStyle ? quality : _getQualityBadgeText(quality), - style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: isHighlightedQuality - ? listStyle - ? colorScheme.onPrimaryContainer - : colorScheme.onPrimary - : colorScheme.onSurfaceVariant, - fontSize: tokens.badgeFontSize, - fontWeight: listStyle ? FontWeight.w500 : FontWeight.w600, - ), - ), + child: !listStyle && isDetailed + ? FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: label, + ) + : label, ); } diff --git a/lib/screens/queue_tab_item_widgets.dart b/lib/screens/queue_tab_item_widgets.dart index fce840bc..c070a996 100644 --- a/lib/screens/queue_tab_item_widgets.dart +++ b/lib/screens/queue_tab_item_widgets.dart @@ -1052,8 +1052,12 @@ extension _QueueTabItemWidgets on _QueueTabState { if (quality != null && quality.isNotEmpty) Positioned( left: 4, + right: _isSelectionMode ? 4 : 28, top: 4, - child: _buildLibraryQualityBadge(context, colorScheme, quality), + child: Align( + alignment: Alignment.centerLeft, + child: _buildLibraryQualityBadge(context, colorScheme, quality), + ), ), if (!_isSelectionMode) Positioned( diff --git a/lib/screens/queue_tab_selection.dart b/lib/screens/queue_tab_selection.dart index 8e10cb37..e7538b60 100644 --- a/lib/screens/queue_tab_selection.dart +++ b/lib/screens/queue_tab_selection.dart @@ -380,28 +380,6 @@ extension _QueueTabSelectionActions on _QueueTabState { ); } - String _getQualityBadgeText(String quality) { - final q = quality.trim().toLowerCase(); - if (q.contains('bit')) { - return quality.split('/').first; - } - - final bitrateTextMatch = RegExp( - r'(\d+)\s*k(?:bps)?', - caseSensitive: false, - ).firstMatch(quality); - if (bitrateTextMatch != null) { - return '${bitrateTextMatch.group(1)}k'; - } - - final bitrateIdMatch = RegExp(r'_(\d+)$').firstMatch(q); - if (bitrateIdMatch != null) { - return '${bitrateIdMatch.group(1)}k'; - } - - return quality.split(' ').first; - } - Future _deleteSelected(List allItems) async { final count = _selectedIds.length; final confirmed = await showDialog( diff --git a/lib/utils/audio_quality_badge_policy.dart b/lib/utils/audio_quality_badge_policy.dart index bb15e347..86228c48 100644 --- a/lib/utils/audio_quality_badge_policy.dart +++ b/lib/utils/audio_quality_badge_policy.dart @@ -4,6 +4,11 @@ import 'package:spotiflac_android/utils/string_utils.dart'; const highQualityBadgeBitrateThresholdKbps = 900; +final RegExp _bitDepthQualityPattern = RegExp( + r'\b\d+\s*(?:-\s*)?bit\b', + caseSensitive: false, +); + String normalizeLibraryQualityLabelMode(String? mode) { return switch (mode) { AppSettings.libraryQualityLabelBitDepth => @@ -82,6 +87,34 @@ String? _buildBitDepthBitrateLabel({int? bitDepth, int? bitrateKbps}) { return '$bitDepth-bit/${bitrateKbps}kbps'; } +/// Keeps detailed bit-depth labels intact in Library grid badges while +/// shortening bitrate-only labels that already include a codec name. +String formatLibraryGridAudioQualityLabel(String quality) { + final normalized = quality.trim().toLowerCase(); + if (_bitDepthQualityPattern.hasMatch(normalized)) return quality; + + final bitrateTextMatch = RegExp( + r'(\d+)\s*k(?:bps)?', + caseSensitive: false, + ).firstMatch(quality); + if (bitrateTextMatch != null) { + return '${bitrateTextMatch.group(1)}k'; + } + + final bitrateIdMatch = RegExp(r'_(\d+)$').firstMatch(normalized); + if (bitrateIdMatch != null) { + return '${bitrateIdMatch.group(1)}k'; + } + + return quality.split(' ').first; +} + +bool isDetailedLibraryAudioQualityLabel(String quality) { + final normalized = quality.trim().toLowerCase(); + return _bitDepthQualityPattern.hasMatch(normalized) && + normalized.contains('/'); +} + /// Preserves the highlighted color used by legacy 24-bit Library badges while /// also supporting newer labels that display a measured bitrate instead. bool shouldHighlightAudioQualityBadge(String quality) { diff --git a/test/audio_quality_badge_policy_test.dart b/test/audio_quality_badge_policy_test.dart index c3700a8b..b64d1040 100644 --- a/test/audio_quality_badge_policy_test.dart +++ b/test/audio_quality_badge_policy_test.dart @@ -136,4 +136,24 @@ void main() { ); }); }); + + group('Library grid audio quality label', () { + test('keeps both parts of detailed bit-depth labels', () { + expect( + formatLibraryGridAudioQualityLabel('16-bit/44.1kHz'), + '16-bit/44.1kHz', + ); + expect( + formatLibraryGridAudioQualityLabel('16-bit/1411kbps'), + '16-bit/1411kbps', + ); + expect(isDetailedLibraryAudioQualityLabel('16-bit/44.1kHz'), isTrue); + }); + + test('keeps bitrate-only grid labels compact', () { + expect(formatLibraryGridAudioQualityLabel('FLAC 1760kbps'), '1760k'); + expect(formatLibraryGridAudioQualityLabel('Bitrate 1760kbps'), '1760k'); + expect(isDetailedLibraryAudioQualityLabel('FLAC 1760kbps'), isFalse); + }); + }); }