Files
SpotiFLAC-Mobile/lib/widgets/downloadable_cover.dart
T

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),
),
),
),
),
],
),
),
);
}
}