spotube/lib/modules/player/player_actions.dart
Alessio fece073def This pull request primarily involves the removal of several configuration files and assets, as well as minor updates to documentation. The most significant changes are the deletion of various .vscode configuration files and the removal of unused assets from the project.
Configuration File Removals:

    .vscode/c_cpp_properties.json: Removed the entire configuration for C/C++ properties.
    .vscode/launch.json: Removed the Dart launch configurations for different environments and modes.
    .vscode/settings.json: Removed settings related to CMake, spell checking, file nesting, and Dart Flutter SDK path.
    .vscode/snippets.code-snippets: Removed code snippets for Dart, including PaginatedState and PaginatedNotifier templates.
    .vscode/tasks.json: Removed the tasks configuration file.

Documentation Updates:

    CONTRIBUTION.md: Removed heart emoji from the introductory text.
    README.md: Updated the logo image and made minor text adjustments, including removing emojis and updating section titles. [1] [2] [3] [4] [5]

Asset Removals:

    lib/collections/assets.gen.dart: Removed multiple unused asset references, including images related to Spotube logos and banners. [1] [2] [3]

Minor Code Cleanups:

    cli/commands/build/linux.dart, cli/commands/build/windows.dart, cli/commands/translated.dart, cli/commands/untranslated.dart: Adjusted import statements for consistency. [1] [2] [3] [4]
    integration_test/app_test.dart: Removed an unnecessary blank line.
    lib/collections/routes.dart: Commented out the TrackRoute configuration.
2025-04-13 18:40:37 +02:00

268 lines
10 KiB
Dart

import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/routes.gr.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart';
import 'package:spotube/components/heart_button/heart_button.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/duration.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/modules/player/player_queue.dart';
import 'package:spotube/modules/player/sibling_tracks_sheet.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/sleep_timer_provider.dart';
class PlayerActions extends HookConsumerWidget {
final MainAxisAlignment mainAxisAlignment;
final bool floatingQueue;
final bool showQueue;
final List<Widget>? extraActions;
const PlayerActions({
this.mainAxisAlignment = MainAxisAlignment.center,
this.floatingQueue = true,
this.showQueue = true,
this.extraActions,
super.key,
});
@override
Widget build(BuildContext context, ref) {
final playlist = ref.watch(audioPlayerProvider);
final isLocalTrack = playlist.activeTrack is LocalTrack;
ref.watch(downloadManagerProvider);
final downloader = ref.watch(downloadManagerProvider.notifier);
final isInQueue = useMemoized(() {
if (playlist.activeTrack == null) return false;
return downloader.isActive(playlist.activeTrack!);
}, [
playlist.activeTrack,
downloader,
]);
final localTracks = [] /* ref.watch(localTracksProvider).value */;
final auth = ref.watch(authenticationProvider);
final sleepTimer = ref.watch(sleepTimerProvider);
final sleepTimerNotifier = ref.watch(sleepTimerProvider.notifier);
final isDownloaded = useMemoized(() {
return localTracks.any(
(element) =>
element.name == playlist.activeTrack?.name &&
element.album?.name == playlist.activeTrack?.album?.name &&
element.artists?.asString() ==
playlist.activeTrack?.artists?.asString(),
) ==
true;
}, [localTracks, playlist.activeTrack]);
final sleepTimerEntries = useMemoized(
() => {
context.l10n.minutes(15): const Duration(minutes: 15),
context.l10n.minutes(30): const Duration(minutes: 30),
context.l10n.hour(1): const Duration(hours: 1),
context.l10n.hour(2): const Duration(hours: 2),
},
[context.l10n],
);
var customHoursEnabled =
sleepTimer == null || sleepTimerEntries.values.contains(sleepTimer);
return Row(
mainAxisAlignment: mainAxisAlignment,
children: [
if (showQueue)
Tooltip(
tooltip: TooltipContainer(child: Text(context.l10n.queue)),
child: IconButton.ghost(
icon: const Icon(SpotubeIcons.queue),
enabled: playlist.activeTrack != null,
onPressed: () {
openDrawer(
context: context,
position: OverlayPosition.right,
transformBackdrop: false,
draggable: false,
surfaceBlur: context.theme.surfaceBlur,
surfaceOpacity: 0.7,
builder: (context) {
return Container(
constraints: const BoxConstraints(maxWidth: 800),
child: Consumer(
builder: (context, ref, _) {
final playlist = ref.watch(audioPlayerProvider);
final playlistNotifier =
ref.read(audioPlayerProvider.notifier);
return PlayerQueue.fromAudioPlayerNotifier(
floating: true,
playlist: playlist,
notifier: playlistNotifier,
);
},
),
);
},
);
},
),
),
if (!isLocalTrack)
Tooltip(
tooltip: TooltipContainer(
child: Text(context.l10n.alternative_track_sources)),
child: IconButton.ghost(
enabled: playlist.activeTrack != null,
icon: const Icon(SpotubeIcons.alternativeRoute),
onPressed: () {
final screenSize = MediaQuery.sizeOf(context);
if (screenSize.mdAndUp) {
showPopover(
alignment: Alignment.bottomCenter,
context: context,
builder: (context) {
return SurfaceCard(
padding: EdgeInsets.zero,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 600,
maxWidth: 500,
),
child: SiblingTracksSheet(floating: floatingQueue),
),
);
},
);
} else {
context.pushRoute(const PlayerTrackSourcesRoute());
}
},
),
),
if (!kIsWeb && !isLocalTrack)
if (isInQueue)
const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
size: 2,
),
)
else
Tooltip(
tooltip:
TooltipContainer(child: Text(context.l10n.download_track)),
child: IconButton.ghost(
icon: Icon(
isDownloaded ? SpotubeIcons.done : SpotubeIcons.download,
),
onPressed: playlist.activeTrack != null
? () => downloader.addToQueue(playlist.activeTrack!)
: null,
),
),
if (playlist.activeTrack != null &&
!isLocalTrack &&
auth.asData?.value != null)
TrackHeartButton(track: playlist.activeTrack!),
AdaptivePopSheetList<Duration>(
tooltip: context.l10n.sleep_timer,
offset: Offset(0, -50 * (sleepTimerEntries.values.length + 2)),
headings: [
Text(context.l10n.sleep_timer),
],
icon: Icon(
SpotubeIcons.timer,
color: sleepTimer != null ? Colors.red : null,
),
onSelected: (value) {
if (value == Duration.zero) {
sleepTimerNotifier.cancelSleepTimer();
} else {
sleepTimerNotifier.setSleepTimer(value);
}
},
items: (context) => [
for (final entry in sleepTimerEntries.entries)
AdaptiveMenuButton(
value: entry.value,
enabled: sleepTimer != entry.value,
child: Text(entry.key),
),
AdaptiveMenuButton(
enabled: customHoursEnabled,
onPressed: (context) async {
final currentTime = TimeOfDay.now();
final time = await showDialog<TimeOfDay?>(
context: context,
builder: (context) => HookBuilder(builder: (context) {
final timeRef = useRef<TimeOfDay?>(null);
return AlertDialog(
trailing: IconButton.ghost(
size: ButtonSize.xSmall,
icon: const Icon(SpotubeIcons.close),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
ShadcnLocalizations.of(context).placeholderTimePicker,
),
content: TimePickerDialog(
use24HourFormat: false,
initialValue: TimeOfDay.fromDateTime(
DateTime.now().add(sleepTimer ?? Duration.zero),
),
onChanged: (value) => timeRef.value = value,
),
actions: [
Button.primary(
onPressed: () {
Navigator.of(context).pop(timeRef.value);
},
child: Text(context.l10n.save),
),
],
);
}),
);
if (time != null) {
sleepTimerNotifier.setSleepTimer(
Duration(
hours: (time.hour - currentTime.hour).abs(),
minutes: (time.minute - currentTime.minute).abs(),
),
);
}
},
child: Text(
customHoursEnabled
? context.l10n.custom_hours
: sleepTimer.format(abbreviated: true),
),
),
AdaptiveMenuButton(
value: Duration.zero,
enabled: sleepTimer != Duration.zero && sleepTimer != null,
child: Text(
context.l10n.cancel,
style: const TextStyle(color: Colors.green),
),
),
],
),
...(extraActions ?? [])
],
);
}
}