fix(library): keep album batch actions alive after selection closes

Use the album screen context for ReplayGain and conversion so dismissing the selection overlay does not invalidate the pending operation. Cover local and downloaded albums in both themes.
This commit is contained in:
zarzet
2026-09-25 01:20:40 +07:00
parent 2adcdf0768
commit bb057fd210
3 changed files with 126 additions and 4 deletions
+2 -2
View File
@@ -738,7 +738,7 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
label: context.l10n.selectionConvertCount(selectedCount),
onPressed: selectedCount > 0
? () => showBatchConvertSheet(
context,
this.context,
ref,
_selectedUnifiedItems(tracks),
onExitSelectionMode: exitSelectionMode,
@@ -751,7 +751,7 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
label: context.l10n.selectionReplayGainCount(selectedCount),
onPressed: selectedCount > 0
? () => runBatchReplayGain(
context,
this.context,
_selectedUnifiedItems(tracks),
onExitSelectionMode: exitSelectionMode,
)
+2 -2
View File
@@ -547,7 +547,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
label: context.l10n.selectionConvertCount(selectedCount),
onPressed: selectedCount > 0
? () => showBatchConvertSheet(
context,
this.context,
ref,
_selectedUnifiedItems(tracks),
onExitSelectionMode: exitSelectionMode,
@@ -563,7 +563,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
label: context.l10n.selectionReplayGainCount(selectedCount),
onPressed: selectedCount > 0
? () => runBatchReplayGain(
context,
this.context,
_selectedUnifiedItems(tracks),
onExitSelectionMode: exitSelectionMode,
)
+122
View File
@@ -0,0 +1,122 @@
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/providers/download_history_provider.dart';
import 'package:spotiflac_android/screens/downloaded_album_screen.dart';
import 'package:spotiflac_android/screens/local_album_screen.dart';
import 'package:spotiflac_android/services/library_database.dart';
import 'package:spotiflac_android/theme/app_theme.dart';
import 'package:spotiflac_android/theme/mornye_theme.dart';
import 'package:spotiflac_android/widgets/app_alert_dialog.dart';
import 'package:spotiflac_android/widgets/selection_bottom_bar.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('com.zarz.spotiflac/backend');
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
for (final mornye in [false, true]) {
for (final downloaded in [false, true]) {
testWidgets(
'ReplayGain survives hiding the album selection (Mornye: $mornye, downloaded: $downloaded)',
(tester) async {
SharedPreferences.setMockInitialValues({});
var attempts = 0;
messenger.setMockMethodCallHandler(channel, (call) async {
if (call.method == 'safCopyToTemp') attempts++;
// Simulate an inaccessible file. Reaching this call and showing
// the result proves confirmation actually starts the operation.
return null;
});
addTearDown(() => messenger.setMockMethodCallHandler(channel, null));
const path = 'content://library/document/track.flac';
final item = DownloadHistoryItem(
id: 'track',
trackName: 'Track',
artistName: 'Artist',
albumName: 'Album',
filePath: path,
service: 'provider-a',
downloadedAt: DateTime(2026),
);
await tester.pumpWidget(
ProviderScope(
overrides: [
downloadedAlbumTracksProvider(
const DownloadedAlbumTracksRequest(
albumName: 'Album',
artistName: 'Artist',
),
).overrideWith((ref) async => [item]),
],
child: MaterialApp(
theme: mornye
? MornyeTheme.build(Brightness.light)
: AppTheme.light(),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: SelectionOverlayHost(
child: downloaded
? const DownloadedAlbumScreen(
albumName: 'Album',
artistName: 'Artist',
)
: LocalAlbumScreen(
albumName: 'Album',
artistName: 'Artist',
tracks: [
LocalLibraryItem(
id: 'track',
trackName: 'Track',
artistName: 'Artist',
albumName: 'Album',
filePath: path,
scannedAt: DateTime(2026),
),
],
),
),
),
),
);
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)),
);
Future<void> openConfirmation() async {
await tester.tap(find.text(l10n.selectionReplayGainCount(1)));
await tester.pumpAndSettle();
expect(find.byType(SelectionBottomBar), findsNothing);
expect(find.byType(AppAlertDialog), findsOneWidget);
}
await openConfirmation();
await tester.tap(find.text(l10n.dialogCancel));
await tester.pumpAndSettle();
expect(attempts, 0);
expect(find.byType(SelectionBottomBar), findsOneWidget);
await openConfirmation();
await tester.tap(
find.descendant(
of: find.byType(AppDialogAction),
matching: find.text(l10n.replayGainBatchConfirmTitle),
),
);
await tester.pumpAndSettle();
expect(attempts, 1);
expect(find.text(l10n.replayGainBatchSuccess(0, 1)), findsOneWidget);
expect(find.byType(SelectionBottomBar), findsNothing);
expect(tester.takeException(), isNull);
},
);
}
}
}