feat(album): replace selection sharing with metadata re-enrichment

This commit is contained in:
zarzet committed 2026-09-30 23:51:54 +07:00
1 parent ce13e29b71
commit eed56d4cbb
3 files changed
+292 -47

No files matched your search

+209
View File
@@ -0,0 +1,209 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/settings.dart';
import 'package:spotiflac_android/providers/download_history_provider.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/screens/downloaded_album_screen.dart';
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
import 'package:spotiflac_android/theme/app_theme.dart';
import 'package:spotiflac_android/theme/mornye_theme.dart';
import 'package:spotiflac_android/widgets/selection_bottom_bar.dart';
class _Settings extends SettingsNotifier {
@override
AppSettings build() => const AppSettings();
@override
Future<void> syncLyricsSettingsToBackend({AppSettings? settings}) async {}
}
class _History extends DownloadHistoryNotifier {
final updates = <String, String?>{};
@override
DownloadHistoryState build() => DownloadHistoryState();
@override
Future<void> updateMetadataForItem({
required String id,
required String trackName,
required String artistName,
required String albumName,
String? albumArtist,
String? isrc,
int? trackNumber,
int? totalTracks,
int? discNumber,
int? totalDiscs,
String? releaseDate,
String? genre,
String? composer,
String? label,
String? copyright,
bool? explicit,
bool? hasLyrics,
int? lyricsMetadataScanVersion,
}) async {
updates[id] = isrc;
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('com.zarz.spotiflac/backend');
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
for (final mornye in [false, true]) {
testWidgets(
'downloaded album re-enrich restores selection on cancel and updates only selected files ($mornye)',
(tester) async {
SharedPreferences.setMockInitialValues({});
final cache = Directory.systemTemp.createTempSync('album_reenrich_');
DownloadedEmbeddedCoverResolver.setPersistentCacheDirectoryForTesting(
cache,
);
addTearDown(() async {
await tester.pumpWidget(const SizedBox.shrink());
await tester.runAsync(() async {
await DownloadedEmbeddedCoverResolver.resetMemoryStateForTesting();
DownloadedEmbeddedCoverResolver.setPersistentCacheDirectoryForTesting(
null,
);
await cache.delete(recursive: true);
});
});
const path = 'content://library/document/track.flac';
final requests = <Map<String, dynamic>>[];
messenger.setMockMethodCallHandler(channel, (call) async {
if (call.method == 'reEnrichFile') {
final request = Map<String, dynamic>.from(
jsonDecode((call.arguments as Map)['request_json'] as String)
as Map,
);
requests.add(request);
return request['preview_only'] == true
? {
'method': 'preview',
'enriched_metadata': {'isrc': 'USABC2600001'},
}
: {'method': 'native'};
}
if (call.method == 'readAudioMetadata') {
return {
'trackName': 'Track',
'artistName': 'Artist',
'albumName': 'Album',
'isrc': 'USABC2600001',
'hasLyrics': true,
};
}
return null;
});
addTearDown(() => messenger.setMockMethodCallHandler(channel, null));
final history = _History();
final navigator = GlobalKey<NavigatorState>();
await tester.pumpWidget(
ProviderScope(
overrides: [
settingsProvider.overrideWith(_Settings.new),
downloadHistoryProvider.overrideWith(() => history),
downloadedAlbumTracksProvider(
const DownloadedAlbumTracksRequest(
albumName: 'Album',
artistName: 'Artist',
),
).overrideWith(
(ref) async => [
for (final id in ['track', 'other'])
DownloadHistoryItem(
id: id,
trackName: id == 'track' ? 'Track' : 'Other',
artistName: 'Artist',
albumName: 'Album',
filePath: id == 'track' ? path : '/music/other.flac',
service: 'example-provider',
downloadedAt: DateTime(2026),
duration: 180,
),
],
),
],
child: MaterialApp(
navigatorKey: navigator,
theme: mornye
? MornyeTheme.build(Brightness.light)
: AppTheme.light(),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const SelectionOverlayHost(
child: DownloadedAlbumScreen(
albumName: 'Album',
artistName: 'Artist',
),
),
),
),
);
await tester.pumpAndSettle();
await tester.scrollUntilVisible(find.text('Track'), 200);
await tester.longPress(find.text('Track'));
await tester.pumpAndSettle();
final l10n = AppLocalizations.of(
tester.element(find.byType(SelectionBottomBar)),
);
expect(find.text(l10n.selectionShareCount(1)), findsNothing);
Future<void> openReEnrich() async {
await tester.tap(find.text('${l10n.trackReEnrich} (1)'));
await tester.pumpAndSettle();
expect(find.byType(SelectionBottomBar), findsNothing);
expect(find.text('Review changes'), findsOneWidget);
}
await openReEnrich();
navigator.currentState!.pop();
await tester.pumpAndSettle();
expect(find.byType(SelectionBottomBar), findsOneWidget);
expect(requests, isEmpty);
await openReEnrich();
await tester.ensureVisible(find.text('Review changes'));
await tester.pumpAndSettle();
await tester.tap(find.text('Review changes'));
await tester.pumpAndSettle();
expect(requests, hasLength(1));
expect(requests.single['file_path'], path);
expect(requests.single['duration_ms'], 180000);
await tester.ensureVisible(find.text('Apply changes'));
await tester.pumpAndSettle();
await tester.tap(find.text('Apply changes'));
// The history refresh also invalidates the disk-backed cover cache.
for (
var attempt = 0;
attempt < 50 && history.updates.isEmpty;
attempt++
) {
await tester.pump(const Duration(milliseconds: 100));
await tester.runAsync(() async {
await Future<void>.delayed(const Duration(milliseconds: 10));
});
}
await tester.pumpAndSettle();
expect(requests, hasLength(2));
expect(requests.last['file_path'], path);
expect(history.updates, {'track': 'USABC2600001'});
expect(find.byType(SelectionBottomBar), findsNothing);
expect(tester.takeException(), isNull);
await tester.pumpWidget(const SizedBox.shrink());
},
);
}
}