fix(extensions): resume metadata after verification

This commit is contained in:
zarzet
2026-08-27 13:08:44 +07:00
parent 9bbfee25d0
commit 47deb9f34e
4 changed files with 99 additions and 12 deletions
+1
View File
@@ -26,6 +26,7 @@ import 'package:spotiflac_android/services/cover_download_service.dart';
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
import 'package:spotiflac_android/services/platform_bridge.dart';
import 'package:spotiflac_android/utils/adaptive_layout.dart';
import 'package:spotiflac_android/utils/extension_auth_launcher.dart';
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
import 'package:spotiflac_android/utils/file_access.dart';
import 'package:spotiflac_android/utils/string_utils.dart';
+30 -12
View File
@@ -932,10 +932,16 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
});
try {
final result = await PlatformBridge.getProviderMetadata(
widget.extensionId,
'album',
widget.albumId,
final result = await runExtensionOperationWithVerificationRetry(
extensionId: widget.extensionId,
browserMode: ref
.read(settingsProvider)
.extensionVerificationBrowserMode,
operation: () => PlatformBridge.getProviderMetadata(
widget.extensionId,
'album',
widget.albumId,
),
);
if (!mounted) return;
@@ -1106,10 +1112,16 @@ class _ExtensionPlaylistScreenState
});
try {
final result = await PlatformBridge.getProviderMetadata(
widget.extensionId,
'playlist',
widget.playlistId,
final result = await runExtensionOperationWithVerificationRetry(
extensionId: widget.extensionId,
browserMode: ref
.read(settingsProvider)
.extensionVerificationBrowserMode,
operation: () => PlatformBridge.getProviderMetadata(
widget.extensionId,
'playlist',
widget.playlistId,
),
);
if (!mounted) return;
@@ -1251,10 +1263,16 @@ class _ExtensionArtistScreenState extends ConsumerState<ExtensionArtistScreen>
});
try {
final result = await PlatformBridge.getProviderMetadata(
widget.extensionId,
'artist',
widget.artistId,
final result = await runExtensionOperationWithVerificationRetry(
extensionId: widget.extensionId,
browserMode: ref
.read(settingsProvider)
.extensionVerificationBrowserMode,
operation: () => PlatformBridge.getProviderMetadata(
widget.extensionId,
'artist',
widget.artistId,
),
);
if (!mounted) return;
+23
View File
@@ -25,6 +25,29 @@ bool isExtensionVerificationRequired(Object error) {
_containsHttpStatusCode(message, '428');
}
Future<T> runExtensionOperationWithVerificationRetry<T>({
required String extensionId,
required String browserMode,
required Future<T> Function() operation,
Future<bool> Function()? verify,
}) async {
try {
return await operation();
} catch (error) {
if (!isExtensionVerificationRequired(error)) rethrow;
final verified =
await (verify ??
() => openVerificationAndAwaitGrant(
extensionId,
browserMode: browserMode,
))();
if (!verified) rethrow;
return operation();
}
}
String? extensionIdFromVerificationError(
Object error,
Iterable<String> knownExtensionIds,
+45
View File
@@ -24,6 +24,51 @@ void main() {
);
});
test(
'retries an extension operation after successful verification',
() async {
var operationCalls = 0;
var verificationCalls = 0;
final result = await runExtensionOperationWithVerificationRetry(
extensionId: 'qobuz-web',
browserMode: 'in_app_first',
operation: () async {
operationCalls++;
if (operationCalls == 1) throw Exception('VERIFY_REQUIRED');
return 'artist metadata';
},
verify: () async {
verificationCalls++;
return true;
},
);
expect(result, 'artist metadata');
expect(operationCalls, 2);
expect(verificationCalls, 1);
},
);
test('does not retry when extension verification is not completed', () async {
var operationCalls = 0;
await expectLater(
runExtensionOperationWithVerificationRetry(
extensionId: 'qobuz-web',
browserMode: 'in_app_first',
operation: () async {
operationCalls++;
throw Exception('VERIFY_REQUIRED');
},
verify: () async => false,
),
throwsA(isA<Exception>()),
);
expect(operationCalls, 1);
});
test('verification wait can be cancelled before foreground resume', () async {
final foreground = Completer<void>();
final cancellation = Completer<void>();