mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-03 16:50:40 +02:00
116 lines
3.5 KiB
Dart
116 lines
3.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:spotiflac_android/l10n/l10n.dart';
|
|
import 'package:spotiflac_android/providers/settings_provider.dart';
|
|
import 'package:spotiflac_android/services/cover_download_service.dart';
|
|
import 'package:spotiflac_android/utils/string_utils.dart';
|
|
|
|
/// Adds the app-wide long-press-to-save behavior to a remote cover image.
|
|
///
|
|
/// The child keeps its normal tap behavior. Only the long-press gesture is
|
|
/// claimed here, so a surrounding track or collection card can still open its
|
|
/// usual options when the user presses outside the artwork.
|
|
class DownloadableCover extends ConsumerStatefulWidget {
|
|
final String? coverUrl;
|
|
final String baseName;
|
|
final Widget child;
|
|
final bool enabled;
|
|
|
|
const DownloadableCover({
|
|
super.key,
|
|
required this.coverUrl,
|
|
required this.baseName,
|
|
required this.child,
|
|
this.enabled = true,
|
|
});
|
|
|
|
@override
|
|
ConsumerState<DownloadableCover> createState() => _DownloadableCoverState();
|
|
}
|
|
|
|
class _DownloadableCoverState extends ConsumerState<DownloadableCover> {
|
|
bool _saving = false;
|
|
|
|
String get _coverUrl => normalizeRemoteHttpUrl(widget.coverUrl) ?? '';
|
|
|
|
Future<void> _saveCover() async {
|
|
if (_saving) return;
|
|
final coverUrl = _coverUrl;
|
|
if (coverUrl.isEmpty) {
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text(context.l10n.trackCoverNoSource)));
|
|
return;
|
|
}
|
|
|
|
setState(() => _saving = true);
|
|
ScaffoldMessenger.of(context)
|
|
..hideCurrentSnackBar()
|
|
..showSnackBar(SnackBar(content: Text(context.l10n.updateDownloading)));
|
|
|
|
try {
|
|
final saved = await CoverDownloadService.saveRemoteCover(
|
|
coverUrl: coverUrl,
|
|
baseName: widget.baseName,
|
|
settings: ref.read(settingsProvider),
|
|
);
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context)
|
|
..hideCurrentSnackBar()
|
|
..showSnackBar(
|
|
SnackBar(content: Text(context.l10n.trackCoverSaved(saved.fileName))),
|
|
);
|
|
} catch (error) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context)
|
|
..hideCurrentSnackBar()
|
|
..showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
context.l10n.trackSaveFailed(context.friendlyError(error)),
|
|
),
|
|
),
|
|
);
|
|
} finally {
|
|
if (mounted) setState(() => _saving = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final canSave = widget.enabled && _coverUrl.isNotEmpty;
|
|
final semanticLabel =
|
|
'${context.l10n.dialogDownload} '
|
|
'${context.l10n.editMetadataFieldCover}: ${widget.baseName}';
|
|
|
|
return Semantics(
|
|
button: canSave,
|
|
label: semanticLabel,
|
|
onLongPress: canSave ? _saveCover : null,
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
excludeFromSemantics: true,
|
|
onLongPress: canSave ? _saveCover : null,
|
|
child: Stack(
|
|
fit: StackFit.passthrough,
|
|
children: [
|
|
widget.child,
|
|
if (_saving)
|
|
const Positioned.fill(
|
|
child: ColoredBox(
|
|
color: Color(0x33000000),
|
|
child: Center(
|
|
child: SizedBox.square(
|
|
dimension: 22,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|