diff --git a/lib/screens/track_metadata_lyrics.dart b/lib/screens/track_metadata_lyrics.dart index 5accd1d6..95236edc 100644 --- a/lib/screens/track_metadata_lyrics.dart +++ b/lib/screens/track_metadata_lyrics.dart @@ -674,156 +674,151 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState { bool get _isSafFile => isContentUri(cleanFilePath); + /// Writes `baseName.extension` as a sidecar of the current track. + /// + /// `writeTo` renders the file at the given path and returns the bridge + /// result, or null when it has already told the user why nothing was + /// written. SAF tracks are rendered into a temp directory and published to + /// the download tree; local tracks are written next to the audio file. + /// Exceptions propagate to the caller. + Future _publishSidecarFile({ + required String baseName, + required String tempDirPrefix, + required String extension, + required String mimeType, + required String Function() successMessage, + required bool hideCurrentSnackBar, + required Future?> Function(String outputPath) writeTo, + }) async { + void showMessage(String message) { + final messenger = ScaffoldMessenger.of(context); + if (hideCurrentSnackBar) { + messenger.hideCurrentSnackBar(); + } + messenger.showSnackBar(SnackBar(content: Text(message))); + } + + final fileName = '$baseName.$extension'; + + if (_isSafFile) { + final tempDir = await Directory.systemTemp.createTemp(tempDirPrefix); + final tempOutput = '${tempDir.path}${Platform.pathSeparator}$fileName'; + + final result = await writeTo(tempOutput); + if (result == null) { + return; + } + + if (result['error'] != null) { + if (mounted) { + showMessage( + context.l10n.trackSaveFailed( + context.friendlyError(result['error']), + ), + ); + } + try { + await Directory(tempDir.path).delete(recursive: true); + } catch (_) {} + return; + } + + final treeUri = _downloadItem?.downloadTreeUri; + final relativeDir = _downloadItem?.safRelativeDir ?? ''; + if (treeUri != null && treeUri.isNotEmpty) { + final safUri = await PlatformBridge.createSafFileFromPath( + treeUri: treeUri, + relativeDir: relativeDir, + fileName: fileName, + mimeType: mimeType, + srcPath: tempOutput, + ); + try { + await Directory(tempDir.path).delete(recursive: true); + } catch (_) {} + if (mounted) { + if (safUri != null) { + showMessage(successMessage()); + } else { + showMessage( + context.l10n.trackSaveFailed('Failed to write to storage'), + ); + } + } + } else { + try { + await Directory(tempDir.path).delete(recursive: true); + } catch (_) {} + if (mounted) { + showMessage(context.l10n.trackSaveFailed('No storage access')); + } + } + return; + } + + final dir = _getFileDirectory(); + final outputPath = '$dir${Platform.pathSeparator}$fileName'; + + final result = await writeTo(outputPath); + if (result == null) { + return; + } + + if (mounted) { + if (result['error'] != null) { + showMessage( + context.l10n.trackSaveFailed(context.friendlyError(result['error'])), + ); + } else { + showMessage(successMessage()); + } + } + } + Future _saveCoverArt() async { try { final baseName = _buildSaveBaseName(); - if (_isSafFile) { - final tempDir = await Directory.systemTemp.createTemp('cover_'); - final tempOutput = - '${tempDir.path}${Platform.pathSeparator}$baseName.jpg'; - - Map result; - if (_fileExists) { - // Prefer extracting cover from the already-downloaded file to avoid - // a redundant network request. - result = await PlatformBridge.extractCoverToFile( - cleanFilePath, - tempOutput, - ); - if (result['error'] != null && - _coverUrl != null && - _coverUrl!.isNotEmpty) { - result = await PlatformBridge.downloadCoverToFile( - _coverUrl!, - tempOutput, + await _publishSidecarFile( + baseName: baseName, + tempDirPrefix: 'cover_', + extension: 'jpg', + mimeType: 'image/jpeg', + successMessage: () => context.l10n.trackCoverSaved(baseName), + hideCurrentSnackBar: false, + writeTo: (outputPath) async { + Map result; + if (_fileExists) { + // Prefer extracting cover from the already-downloaded file to + // avoid a redundant network request. + result = await PlatformBridge.extractCoverToFile( + cleanFilePath, + outputPath, ); - } - } else if (_coverUrl != null && _coverUrl!.isNotEmpty) { - result = await PlatformBridge.downloadCoverToFile( - _coverUrl!, - tempOutput, - ); - } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.trackCoverNoSource)), - ); - } - return; - } - - if (result['error'] != null) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed( - context.friendlyError(result['error']), - ), - ), - ), - ); - } - try { - await Directory(tempDir.path).delete(recursive: true); - } catch (_) {} - return; - } - - final treeUri = _downloadItem?.downloadTreeUri; - final relativeDir = _downloadItem?.safRelativeDir ?? ''; - if (treeUri != null && treeUri.isNotEmpty) { - final safUri = await PlatformBridge.createSafFileFromPath( - treeUri: treeUri, - relativeDir: relativeDir, - fileName: '$baseName.jpg', - mimeType: 'image/jpeg', - srcPath: tempOutput, - ); - try { - await Directory(tempDir.path).delete(recursive: true); - } catch (_) {} - if (mounted) { - if (safUri != null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.trackCoverSaved(baseName))), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed('Failed to write to storage'), - ), - ), + if (result['error'] != null && + _coverUrl != null && + _coverUrl!.isNotEmpty) { + result = await PlatformBridge.downloadCoverToFile( + _coverUrl!, + outputPath, ); } - } - } else { - try { - await Directory(tempDir.path).delete(recursive: true); - } catch (_) {} - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed('No storage access'), - ), - ), + } else if (_coverUrl != null && _coverUrl!.isNotEmpty) { + result = await PlatformBridge.downloadCoverToFile( + _coverUrl!, + outputPath, ); + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.trackCoverNoSource)), + ); + } + return null; } - } - return; - } - - final dir = _getFileDirectory(); - final outputPath = '$dir${Platform.pathSeparator}$baseName.jpg'; - - Map result; - if (_fileExists) { - result = await PlatformBridge.extractCoverToFile( - cleanFilePath, - outputPath, - ); - if (result['error'] != null && - _coverUrl != null && - _coverUrl!.isNotEmpty) { - result = await PlatformBridge.downloadCoverToFile( - _coverUrl!, - outputPath, - ); - } - } else if (_coverUrl != null && _coverUrl!.isNotEmpty) { - result = await PlatformBridge.downloadCoverToFile( - _coverUrl!, - outputPath, - ); - } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.trackCoverNoSource)), - ); - } - return; - } - - if (mounted) { - if (result['error'] != null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed( - context.friendlyError(result['error']), - ), - ), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.trackCoverSaved(baseName))), - ); - } - } + return result; + }, + ); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -849,128 +844,22 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState { ); } - if (_isSafFile) { - final tempDir = await Directory.systemTemp.createTemp('lyrics_'); - final tempOutput = - '${tempDir.path}${Platform.pathSeparator}$baseName.lrc'; - - final result = await PlatformBridge.fetchAndSaveLyrics( + await _publishSidecarFile( + baseName: baseName, + tempDirPrefix: 'lyrics_', + extension: 'lrc', + mimeType: 'application/octet-stream', + successMessage: () => context.l10n.trackLyricsSaved(baseName), + hideCurrentSnackBar: true, + writeTo: (outputPath) => PlatformBridge.fetchAndSaveLyrics( trackName: trackName, artistName: artistName, spotifyId: _spotifyId ?? '', durationMs: durationMs, - outputPath: tempOutput, + outputPath: outputPath, audioFilePath: _fileExists ? cleanFilePath : '', - ); - - if (result['error'] != null) { - if (mounted) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed( - context.friendlyError(result['error']), - ), - ), - ), - ); - } - try { - await Directory(tempDir.path).delete(recursive: true); - } catch (_) {} - return; - } - - final treeUri = _downloadItem?.downloadTreeUri; - final relativeDir = _downloadItem?.safRelativeDir ?? ''; - if (treeUri != null && treeUri.isNotEmpty) { - final safUri = await PlatformBridge.createSafFileFromPath( - treeUri: treeUri, - relativeDir: relativeDir, - fileName: '$baseName.lrc', - mimeType: 'application/octet-stream', - srcPath: tempOutput, - ); - try { - await Directory(tempDir.path).delete(recursive: true); - } catch (_) {} - if (mounted) { - if (safUri != null) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text(context.l10n.trackLyricsSaved(baseName)), - ), - ); - } else { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed( - 'Failed to write to storage', - ), - ), - ), - ); - } - } - } else { - try { - await Directory(tempDir.path).delete(recursive: true); - } catch (_) {} - if (mounted) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed('No storage access'), - ), - ), - ); - } - } - return; - } - - final dir = _getFileDirectory(); - final outputPath = '$dir${Platform.pathSeparator}$baseName.lrc'; - - final result = await PlatformBridge.fetchAndSaveLyrics( - trackName: trackName, - artistName: artistName, - spotifyId: _spotifyId ?? '', - durationMs: durationMs, - outputPath: outputPath, - audioFilePath: _fileExists ? cleanFilePath : '', + ), ); - - if (mounted) { - if (result['error'] != null) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed( - context.friendlyError(result['error']), - ), - ), - ), - ); - } else { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(context.l10n.trackLyricsSaved(baseName))), - ); - } - } } catch (e) { if (mounted) { ScaffoldMessenger.of(context)