mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: add sleep timer support
This commit is contained in:
parent
0620b62023
commit
4a75f3dbd1
@ -10,11 +10,13 @@ import 'package:spotube/components/player/sibling_tracks_sheet.dart';
|
||||
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
|
||||
import 'package:spotube/components/shared/heart_button.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/duration.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/sleep_timer_provider.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
||||
class PlayerActions extends HookConsumerWidget {
|
||||
@ -39,6 +41,8 @@ class PlayerActions extends HookConsumerWidget {
|
||||
downloader.activeItem!.id == playlist.activeTrack?.id;
|
||||
final localTracks = [] /* ref.watch(localTracksProvider).value */;
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
final sleepTimer = ref.watch(SleepTimerNotifier.provider);
|
||||
final sleepTimerNotifier = ref.watch(SleepTimerNotifier.notifier);
|
||||
|
||||
final isDownloaded = useMemoized(() {
|
||||
return localTracks.any(
|
||||
@ -53,6 +57,18 @@ class PlayerActions extends HookConsumerWidget {
|
||||
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: [
|
||||
@ -129,30 +145,57 @@ class PlayerActions extends HookConsumerWidget {
|
||||
if (playlist.activeTrack != null && !isLocalTrack && auth != null)
|
||||
TrackHeartButton(track: playlist.activeTrack!),
|
||||
AdaptivePopSheetList(
|
||||
offset: const Offset(0, -50 * 5),
|
||||
offset: Offset(0, -50 * (sleepTimerEntries.values.length + 2)),
|
||||
headings: [
|
||||
Text(context.l10n.sleep_timer),
|
||||
],
|
||||
icon: const Icon(SpotubeIcons.timer),
|
||||
icon: Icon(
|
||||
SpotubeIcons.timer,
|
||||
color: sleepTimer != null ? Colors.red : null,
|
||||
),
|
||||
onSelected: (value) {
|
||||
if (value == Duration.zero) {
|
||||
sleepTimerNotifier.cancelSleepTimer();
|
||||
} else {
|
||||
sleepTimerNotifier.setSleepTimer(value);
|
||||
}
|
||||
},
|
||||
children: [
|
||||
for (final entry in sleepTimerEntries.entries)
|
||||
PopSheetEntry(
|
||||
value: entry.value,
|
||||
enabled: sleepTimer != entry.value,
|
||||
title: Text(entry.key),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: const Duration(minutes: 15),
|
||||
title: Text(context.l10n.mins(15)),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: const Duration(minutes: 30),
|
||||
title: Text(context.l10n.mins(30)),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: const Duration(hours: 1),
|
||||
title: Text(context.l10n.hour(1)),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: const Duration(hours: 2),
|
||||
title: Text(context.l10n.hours(2)),
|
||||
title: Text(
|
||||
customHoursEnabled
|
||||
? context.l10n.custom_hours
|
||||
: sleepTimer.toHumanReadableString(),
|
||||
),
|
||||
// only enabled when there's no preset timers selected
|
||||
enabled: customHoursEnabled,
|
||||
onTap: () async {
|
||||
final currentTime = TimeOfDay.now();
|
||||
final time = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: currentTime,
|
||||
);
|
||||
|
||||
if (time != null) {
|
||||
sleepTimerNotifier.setSleepTimer(
|
||||
Duration(
|
||||
hours: (time.hour - currentTime.hour).abs(),
|
||||
minutes: (time.minute - currentTime.minute).abs(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: Duration.zero,
|
||||
enabled: sleepTimer != Duration.zero && sleepTimer != null,
|
||||
textColor: Colors.green,
|
||||
title: Text(context.l10n.cancel),
|
||||
),
|
||||
],
|
||||
|
@ -194,8 +194,8 @@ class _AdaptivePopSheetListItem<T> extends StatelessWidget {
|
||||
? null
|
||||
: () {
|
||||
item.onTap?.call();
|
||||
Navigator.pop(context);
|
||||
if (item.value != null) {
|
||||
Navigator.pop(context);
|
||||
onSelected?.call(item.value as T);
|
||||
}
|
||||
},
|
||||
|
@ -119,9 +119,9 @@ class TrackDetailsDialog extends HookWidget {
|
||||
),
|
||||
if (entry.value is Widget)
|
||||
entry.value as Widget
|
||||
else
|
||||
else if (entry.value is String)
|
||||
Text(
|
||||
entry.value,
|
||||
entry.value as String,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
|
||||
extension DurationToHumanReadableString on Duration {
|
||||
toHumanReadableString() =>
|
||||
String toHumanReadableString() =>
|
||||
"${inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(inSeconds.remainder(60))}";
|
||||
}
|
||||
|
@ -243,5 +243,6 @@
|
||||
"sleep_timer": "Sleep Timer",
|
||||
"mins": "{minutes} Minutes",
|
||||
"hours": "{hours} Hours",
|
||||
"hour": "{hours} Hour"
|
||||
"hour": "{hours} Hour",
|
||||
"custom_hours": "Custom Hours"
|
||||
}
|
31
lib/provider/sleep_timer_provider.dart
Normal file
31
lib/provider/sleep_timer_provider.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class SleepTimerNotifier extends StateNotifier<Duration?> {
|
||||
SleepTimerNotifier() : super(null);
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
static final provider = StateNotifierProvider<SleepTimerNotifier, Duration?>(
|
||||
(ref) => SleepTimerNotifier(),
|
||||
);
|
||||
|
||||
static AlwaysAliveRefreshable<SleepTimerNotifier> get notifier =>
|
||||
provider.notifier;
|
||||
|
||||
void setSleepTimer(Duration duration) {
|
||||
state = duration;
|
||||
|
||||
_timer = Timer(duration, () {
|
||||
//! This can be a reason for app termination in iOS AppStore
|
||||
exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
void cancelSleepTimer() {
|
||||
state = null;
|
||||
_timer?.cancel();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user