mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 21:02:09 +02:00
feat(album): display disc groups from catalog metadata
This commit is contained in:
@@ -36,6 +36,7 @@ import 'package:spotiflac_android/widgets/track_detail_actions.dart';
|
||||
import 'package:spotiflac_android/widgets/selection_action_button.dart';
|
||||
import 'package:spotiflac_android/widgets/selection_bottom_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/downloadable_cover.dart';
|
||||
import 'package:spotiflac_android/widgets/disc_separator_chip.dart';
|
||||
import 'package:spotiflac_android/widgets/mornye_artist_header.dart';
|
||||
|
||||
class _AlbumCache {
|
||||
@@ -628,6 +629,14 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen>
|
||||
final existingHistoryKeys = ref.watch(
|
||||
downloadHistoryVisibleBatchExistsProvider(_historySnapshot.request),
|
||||
);
|
||||
int discNumber(Track track) {
|
||||
final number = track.discNumber ?? 1;
|
||||
return number > 0 ? number : 1;
|
||||
}
|
||||
|
||||
final hasMultipleDiscs = tracks.any(
|
||||
(track) => discNumber(track) > 1 || (track.totalDiscs ?? 1) > 1,
|
||||
);
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverList(
|
||||
@@ -637,7 +646,7 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen>
|
||||
final isInHistory = existingHistoryKeys.contains(
|
||||
historyLookups[index].lookupKey,
|
||||
);
|
||||
return KeyedSubtree(
|
||||
final row = KeyedSubtree(
|
||||
key: ValueKey(selectionId),
|
||||
child: StaggeredListItem(
|
||||
index: index,
|
||||
@@ -675,6 +684,18 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen>
|
||||
),
|
||||
),
|
||||
);
|
||||
final disc = discNumber(track);
|
||||
if (hasMultipleDiscs &&
|
||||
(index == 0 || discNumber(tracks[index - 1]) != disc)) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DiscSeparatorChip(discNumber: disc),
|
||||
row,
|
||||
],
|
||||
);
|
||||
}
|
||||
return row;
|
||||
}, childCount: tracks.length),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -490,10 +490,12 @@ class _CollectionHeaderSkeleton extends StatelessWidget {
|
||||
children: [
|
||||
const SkeletonBox(width: 48, height: 48, borderRadius: 24),
|
||||
const SizedBox(width: 16),
|
||||
SkeletonBox(
|
||||
width: screenWidth * 0.45,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
Flexible(
|
||||
child: SkeletonBox(
|
||||
width: screenWidth * 0.45,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const SkeletonBox(width: 48, height: 48, borderRadius: 24),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
|
||||
/// "Disc N" chip with a trailing hairline, shown between disc groups in
|
||||
/// album track lists.
|
||||
@@ -11,6 +12,28 @@ class DiscSeparatorChip extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
if (context.isMornye) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Divider(
|
||||
height: 1,
|
||||
color: MornyeTheme.metadataDividerColor(context),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
context.l10n.downloadedAlbumDiscHeader(discNumber),
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/widgets/album_description.dart';
|
||||
import 'package:spotiflac_android/widgets/audio_quality_badges.dart';
|
||||
import 'package:spotiflac_android/widgets/disc_separator_chip.dart';
|
||||
import 'package:spotiflac_android/widgets/track_list_tile.dart';
|
||||
|
||||
void main() {
|
||||
@@ -109,6 +110,84 @@ void main() {
|
||||
}
|
||||
}
|
||||
|
||||
for (final mornye in [false, true]) {
|
||||
for (final discs in [1, 2]) {
|
||||
testWidgets('online album disc groups (Mornye: $mornye, discs: $discs)', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.binding.setSurfaceSize(const Size(430, 1200));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
backendMessenger.setMockMethodCallHandler(backendChannel, (call) async {
|
||||
if (call.method != 'getProviderMetadata') return null;
|
||||
return jsonEncode({
|
||||
'album_info': {'name': 'Example Album', 'total_tracks': 2},
|
||||
'track_list': [
|
||||
for (var index = 0; index < 2; index++)
|
||||
{
|
||||
'id': 'song-$index',
|
||||
'name': 'Song $index',
|
||||
'artists': 'Example Artist',
|
||||
'disc_number': index == 0 ? 1 : discs,
|
||||
'total_discs': discs,
|
||||
'track_number': discs == 1 ? index + 1 : 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
child: MaterialApp(
|
||||
theme: mornye ? MornyeTheme.build(Brightness.dark) : ThemeData(),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: ExtensionAlbumScreen(
|
||||
extensionId: 'example-metadata',
|
||||
albumId: 'disc-album-$mornye-$discs',
|
||||
albumName: 'Example Album',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final tiles = tester.widgetList<TrackListTile>(
|
||||
find.byType(TrackListTile),
|
||||
);
|
||||
expect(tiles.map((tile) => tile.track.discNumber), [1, discs]);
|
||||
expect(tiles.map((tile) => tile.track.totalDiscs), [discs, discs]);
|
||||
expect(
|
||||
tiles.map((tile) => tile.track.trackNumber),
|
||||
discs == 1 ? [1, 2] : [1, 1],
|
||||
);
|
||||
if (discs == 1) {
|
||||
expect(find.byType(DiscSeparatorChip), findsNothing);
|
||||
} else {
|
||||
final headers = tester.widgetList<DiscSeparatorChip>(
|
||||
find.byType(DiscSeparatorChip),
|
||||
);
|
||||
expect(headers.map((header) => header.discNumber), [1, 2]);
|
||||
expect(
|
||||
tester.getTopLeft(find.text('Disc 2')).dy,
|
||||
inExclusiveRange(
|
||||
tester.getBottomLeft(find.text('Song 0')).dy,
|
||||
tester.getTopLeft(find.text('Song 1')).dy,
|
||||
),
|
||||
);
|
||||
await tester.longPress(find.text('Song 1'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
tester
|
||||
.widgetList<TrackListTile>(find.byType(TrackListTile))
|
||||
.map((tile) => tile.isSelected),
|
||||
[false, true],
|
||||
);
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets('album tracks retain the extended tags supplied in search', (
|
||||
tester,
|
||||
) async {
|
||||
|
||||
Reference in New Issue
Block a user