spotube/lib/modules/player/player_actions.dart

269 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/extensions/constrains.dart';
import 'package:spotube/modules/player/player_queue.dart';
import 'package:spotube/modules/player/sibling_tracks_sheet.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/context.dart';
import 'package:spotube/extensions/duration.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/audio_player/audio_player.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.mins(15): const Duration(minutes: 15),
context.l10n.mins(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 ?? [])
],
);
}
}