mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-27 13:22:49 +02:00
fix(ui): align metadata and wrap track badges
This commit is contained in:
@@ -320,32 +320,25 @@ class _TrackItemWithStatus extends ConsumerWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: ClickableArtistName(
|
||||
artistName: track.artistName,
|
||||
artistId: track.artistId,
|
||||
coverUrl: track.coverUrl,
|
||||
extensionId: extensionId,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
TrackMetadataBadgesLine(
|
||||
primary: ClickableArtistName(
|
||||
artistName: track.artistName,
|
||||
artistId: track.artistId,
|
||||
coverUrl: track.coverUrl,
|
||||
extensionId: extensionId,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: colorScheme.onSurfaceVariant),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
badges: [
|
||||
...buildQualityBadges(
|
||||
audioQuality: track.audioQuality,
|
||||
audioModes: track.audioModes,
|
||||
colorScheme: colorScheme,
|
||||
explicit: track.isExplicit,
|
||||
),
|
||||
if (isInLocalLibrary) ...[
|
||||
const SizedBox(width: 6),
|
||||
const InLibraryBadge(),
|
||||
],
|
||||
if (isInLocalLibrary) const InLibraryBadge(),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -522,7 +522,8 @@ extension _TrackMetadataCards on _TrackMetadataScreenState {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 100,
|
||||
|
||||
@@ -144,8 +144,44 @@ class DolbyLogoPainter extends CustomPainter {
|
||||
color != oldDelegate.color;
|
||||
}
|
||||
|
||||
/// Convenience builder: returns a list of quality badge widgets for a track.
|
||||
/// Pass the result into a Row using spread operator.
|
||||
/// Keeps artist text and optional track badges responsive on narrow rows.
|
||||
///
|
||||
/// A [Row] with a flexible artist still overflows when the badges themselves
|
||||
/// need more room than remains. [Wrap] moves whole badges to another run while
|
||||
/// keeping the artist constrained and ellipsized when it alone is too long.
|
||||
class TrackMetadataBadgesLine extends StatelessWidget {
|
||||
const TrackMetadataBadgesLine({
|
||||
super.key,
|
||||
required this.primary,
|
||||
this.badges = const [],
|
||||
});
|
||||
|
||||
final Widget primary;
|
||||
final List<Widget> badges;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (badges.isEmpty) return primary;
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: constraints.maxWidth),
|
||||
child: primary,
|
||||
),
|
||||
...badges,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the quality-related badge widgets for a track. Spacing and wrapping
|
||||
/// belong to [TrackMetadataBadgesLine] so the group stays responsive.
|
||||
List<Widget> buildQualityBadges({
|
||||
required String? audioQuality,
|
||||
required String? audioModes,
|
||||
@@ -154,17 +190,14 @@ List<Widget> buildQualityBadges({
|
||||
}) {
|
||||
final badges = <Widget>[];
|
||||
if (explicit) {
|
||||
badges.add(const SizedBox(width: 6));
|
||||
badges.add(ExplicitBadge(colorScheme: colorScheme));
|
||||
}
|
||||
if (audioQuality != null && audioQuality.isNotEmpty) {
|
||||
badges.add(const SizedBox(width: 6));
|
||||
badges.add(
|
||||
AudioQualityBadge(label: audioQuality, colorScheme: colorScheme),
|
||||
);
|
||||
}
|
||||
if (audioModes != null && audioModes.contains('DOLBY_ATMOS')) {
|
||||
badges.add(const SizedBox(width: 4));
|
||||
badges.add(DolbyAtmosBadge(colorScheme: colorScheme));
|
||||
}
|
||||
return badges;
|
||||
|
||||
@@ -76,36 +76,31 @@ class TrackListTile extends ConsumerWidget {
|
||||
isSelected: isSelected,
|
||||
leading: leading,
|
||||
title: track.name,
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: clickableArtist && !isSelectionMode
|
||||
? ClickableArtistName(
|
||||
artistName: track.artistName,
|
||||
artistId: track.artistId,
|
||||
coverUrl: track.coverUrl,
|
||||
extensionId: track.source,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
)
|
||||
: Text(
|
||||
track.artistName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
subtitle: TrackMetadataBadgesLine(
|
||||
primary: clickableArtist && !isSelectionMode
|
||||
? ClickableArtistName(
|
||||
artistName: track.artistName,
|
||||
artistId: track.artistId,
|
||||
coverUrl: track.coverUrl,
|
||||
extensionId: track.source,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
)
|
||||
: Text(
|
||||
track.artistName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
badges: [
|
||||
...buildQualityBadges(
|
||||
audioQuality: track.audioQuality,
|
||||
audioModes: track.audioModes,
|
||||
colorScheme: colorScheme,
|
||||
explicit: track.isExplicit,
|
||||
),
|
||||
if (isInLocalLibrary || isInHistory) ...[
|
||||
const SizedBox(width: 6),
|
||||
const InLibraryBadge(),
|
||||
],
|
||||
if (isInLocalLibrary || isInHistory) const InLibraryBadge(),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:spotiflac_android/l10n/app_localizations.dart';
|
||||
import 'package:spotiflac_android/widgets/audio_quality_badges.dart';
|
||||
import 'package:spotiflac_android/widgets/in_library_badge.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('track metadata badges wrap on a narrow result row', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: Align(
|
||||
child: SizedBox(
|
||||
width: 180,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return TrackMetadataBadgesLine(
|
||||
primary: const Text(
|
||||
'RADWIMPS',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
badges: [
|
||||
AudioQualityBadge(
|
||||
label: '16-bit',
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
DolbyAtmosBadge(colorScheme: colorScheme),
|
||||
const InLibraryBadge(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(
|
||||
tester.getTopLeft(find.byType(InLibraryBadge)).dy,
|
||||
greaterThan(tester.getTopLeft(find.text('RADWIMPS')).dy),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -56,5 +56,14 @@ void main() {
|
||||
separators.every((text) => text.style?.color == Colors.white70),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
final durationLabel = find.text('Duration');
|
||||
final metadataRow = find
|
||||
.ancestor(of: durationLabel, matching: find.byType(Row))
|
||||
.first;
|
||||
final row = tester.widget<Row>(metadataRow);
|
||||
|
||||
expect(row.crossAxisAlignment, CrossAxisAlignment.baseline);
|
||||
expect(row.textBaseline, TextBaseline.alphabetic);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user