mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 23:11:45 +02:00
fix(metadata): show re-enrich results above Library cards
This commit is contained in:
1 parent
4b39716b23
commit
3c62d38c34
6 files changed
+224
-31
No files matched your search
@@ -1065,10 +1065,10 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState {
|
||||
);
|
||||
await _checkEmbeddedLyrics();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(reEnrichCompletionMessage(context.l10n, result)),
|
||||
),
|
||||
messenger.hideCurrentSnackBar();
|
||||
await showReEnrichResultDialog(
|
||||
context,
|
||||
message: reEnrichCompletionMessage(context.l10n, result),
|
||||
);
|
||||
}
|
||||
} else if (method == 'ffmpeg') {
|
||||
@@ -1138,13 +1138,11 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState {
|
||||
safUri,
|
||||
);
|
||||
if (!ok && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.trackSaveFailed(
|
||||
context.l10n.snackbarFailedToWriteStorage,
|
||||
),
|
||||
),
|
||||
messenger.hideCurrentSnackBar();
|
||||
await showReEnrichResultDialog(
|
||||
context,
|
||||
message: context.l10n.trackSaveFailed(
|
||||
context.l10n.snackbarFailedToWriteStorage,
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -1171,17 +1169,17 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState {
|
||||
);
|
||||
await _checkEmbeddedLyrics();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
reEnrichCompletionMessage(context.l10n, result),
|
||||
),
|
||||
),
|
||||
messenger.hideCurrentSnackBar();
|
||||
await showReEnrichResultDialog(
|
||||
context,
|
||||
message: reEnrichCompletionMessage(context.l10n, result),
|
||||
);
|
||||
}
|
||||
} else if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.trackReEnrichFfmpegFailed)),
|
||||
messenger.hideCurrentSnackBar();
|
||||
await showReEnrichResultDialog(
|
||||
context,
|
||||
message: context.l10n.trackReEnrichFfmpegFailed,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
@@ -1201,19 +1199,19 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState {
|
||||
result['error'],
|
||||
fallback: context.l10n.metadataSaveFailedFfmpeg,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.trackSaveFailed(error))),
|
||||
messenger.hideCurrentSnackBar();
|
||||
await showReEnrichResultDialog(
|
||||
context,
|
||||
message: context.l10n.trackSaveFailed(error),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.trackSaveFailed(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
await showReEnrichResultDialog(
|
||||
context,
|
||||
message: context.l10n.trackSaveFailed(context.friendlyError(e)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:spotiflac_android/widgets/app_choice_chip.dart';
|
||||
import 'package:spotiflac_android/widgets/app_switch.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/re_enrich_review_sheet.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
@@ -303,10 +303,13 @@ Future<void> reEnrichLocalTracks(
|
||||
}
|
||||
|
||||
if (previews.isEmpty) {
|
||||
onSelectionRestore();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.trackReEnrichNoChanges)),
|
||||
await showReEnrichResultDialog(
|
||||
context,
|
||||
message: context.l10n.trackReEnrichNoChanges,
|
||||
);
|
||||
// Restore after the result closes so root-overlay fallback bars cannot
|
||||
// cover this dialog either. Selected IDs remain intact throughout.
|
||||
if (context.mounted && isActive()) onSelectionRestore();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -354,7 +357,7 @@ Future<void> reEnrichLocalTracks(
|
||||
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
final summary = lyricsSummary.message(context.l10n, successCount, total);
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(summary)));
|
||||
await showReEnrichResultDialog(context, message: summary);
|
||||
}
|
||||
|
||||
/// Runs a batch against one settings snapshot per phase. Dependencies are
|
||||
|
||||
@@ -2,11 +2,31 @@ import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/services/batch_metadata_re_enrich.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_alert_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/widgets/app_action_button.dart';
|
||||
import 'package:spotiflac_android/widgets/app_content_card.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
|
||||
/// Results belong above the Library selection card and metadata sheets.
|
||||
/// A page snackbar can be painted behind either of those surfaces.
|
||||
Future<void> showReEnrichResultDialog(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
}) => showAppDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AppAlertDialog(
|
||||
title: Text(dialogContext.l10n.trackReEnrich),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
AppDialogAction(
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: Text(MaterialLocalizations.of(dialogContext).okButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Future<bool> showReEnrichReviewSheet(
|
||||
BuildContext context, {
|
||||
required List<BatchReEnrichPreview> previews,
|
||||
|
||||
@@ -309,6 +309,10 @@ void main() {
|
||||
active = !leaveDuringRefresh;
|
||||
library.refreshed.complete();
|
||||
await tester.pumpAndSettle();
|
||||
if (!leaveDuringRefresh) {
|
||||
await tester.tap(find.text('OK'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
await action;
|
||||
expect(events, leaveDuringRefresh ? ['hide'] : ['hide', 'complete']);
|
||||
expect(tester.takeException(), isNull);
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
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:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/settings.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:spotiflac_android/services/local_track_batch_actions.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/re_enrich_review_sheet.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 {}
|
||||
}
|
||||
|
||||
void main() {
|
||||
for (final mornye in [false, true]) {
|
||||
testWidgets('result is readable above an open metadata sheet ($mornye)', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
child: MaterialApp(
|
||||
theme: mornye ? MornyeTheme.build(Brightness.dark) : ThemeData(),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => TextButton(
|
||||
onPressed: () => showAppBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (sheetContext) => SizedBox(
|
||||
height: 400,
|
||||
child: TextButton(
|
||||
onPressed: () => showReEnrichResultDialog(
|
||||
sheetContext,
|
||||
message: 'Metadata lookup failed',
|
||||
),
|
||||
child: const Text('Look up metadata'),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('Open metadata'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.text('Open metadata'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Look up metadata'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Metadata lookup failed').hitTestable(), findsOneWidget);
|
||||
expect(find.text('Look up metadata').hitTestable(), findsNothing);
|
||||
await tester.tap(find.text('OK'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Look up metadata').hitTestable(), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
for (final fails in [false, true]) {
|
||||
testWidgets(
|
||||
'empty/failed preview stays visible before selection returns ($mornye/$fails)',
|
||||
(tester) async {
|
||||
const channel = MethodChannel('com.zarz.spotiflac/backend');
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
(call) async {
|
||||
expect(call.method, 'reEnrichFile');
|
||||
if (fails) throw PlatformException(code: 'lookup_failed');
|
||||
return {
|
||||
'method': 'preview',
|
||||
'enriched_metadata': <String, dynamic>{},
|
||||
};
|
||||
},
|
||||
);
|
||||
addTearDown(
|
||||
() => tester.binding.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(channel, null),
|
||||
);
|
||||
final overlay = SelectionOverlayController();
|
||||
addTearDown(overlay.hide);
|
||||
var restored = 0;
|
||||
Future<void>? action;
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [settingsProvider.overrideWith(_Settings.new)],
|
||||
child: MaterialApp(
|
||||
theme: mornye
|
||||
? MornyeTheme.build(Brightness.dark)
|
||||
: ThemeData(),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: Consumer(
|
||||
builder: (context, ref, _) => TextButton(
|
||||
onPressed: () {
|
||||
action = reEnrichLocalTracks(
|
||||
context,
|
||||
ref,
|
||||
[
|
||||
LocalLibraryItem(
|
||||
id: 'song',
|
||||
trackName: 'Song',
|
||||
artistName: 'Artist',
|
||||
albumName: 'Album',
|
||||
filePath: '/music/song.flac',
|
||||
scannedAt: DateTime(2026),
|
||||
),
|
||||
],
|
||||
isActive: () => true,
|
||||
onSelectionHide: () async => overlay.hide(),
|
||||
onSelectionRestore: () {
|
||||
restored++;
|
||||
overlay.show(
|
||||
context,
|
||||
(_) => const SizedBox(
|
||||
height: 350,
|
||||
child: ColoredBox(
|
||||
color: Colors.blue,
|
||||
child: Center(child: Text('Selection card')),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onComplete: () => fail('No tags should be applied'),
|
||||
);
|
||||
},
|
||||
child: const Text('Re-enrich song'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.text('Re-enrich song'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.ensureVisible(find.text('Review changes'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Review changes'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find
|
||||
.text('No metadata changes were found for the selected tracks.')
|
||||
.hitTestable(),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(restored, 0);
|
||||
await tester.tap(find.text('OK'));
|
||||
await tester.pumpAndSettle();
|
||||
await action;
|
||||
expect(restored, 1);
|
||||
expect(find.text('Selection card').hitTestable(), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user