mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05: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/adaptive/adaptive_pop_sheet_list.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/shared/heart_button.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/extensions/duration.dart';
|
||||||
import 'package:spotube/models/local_track.dart';
|
import 'package:spotube/models/local_track.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_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';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class PlayerActions extends HookConsumerWidget {
|
class PlayerActions extends HookConsumerWidget {
|
||||||
@ -39,6 +41,8 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
downloader.activeItem!.id == playlist.activeTrack?.id;
|
downloader.activeItem!.id == playlist.activeTrack?.id;
|
||||||
final localTracks = [] /* ref.watch(localTracksProvider).value */;
|
final localTracks = [] /* ref.watch(localTracksProvider).value */;
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
final sleepTimer = ref.watch(SleepTimerNotifier.provider);
|
||||||
|
final sleepTimerNotifier = ref.watch(SleepTimerNotifier.notifier);
|
||||||
|
|
||||||
final isDownloaded = useMemoized(() {
|
final isDownloaded = useMemoized(() {
|
||||||
return localTracks.any(
|
return localTracks.any(
|
||||||
@ -53,6 +57,18 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
true;
|
true;
|
||||||
}, [localTracks, playlist.activeTrack]);
|
}, [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(
|
return Row(
|
||||||
mainAxisAlignment: mainAxisAlignment,
|
mainAxisAlignment: mainAxisAlignment,
|
||||||
children: [
|
children: [
|
||||||
@ -129,30 +145,57 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
if (playlist.activeTrack != null && !isLocalTrack && auth != null)
|
if (playlist.activeTrack != null && !isLocalTrack && auth != null)
|
||||||
TrackHeartButton(track: playlist.activeTrack!),
|
TrackHeartButton(track: playlist.activeTrack!),
|
||||||
AdaptivePopSheetList(
|
AdaptivePopSheetList(
|
||||||
offset: const Offset(0, -50 * 5),
|
offset: Offset(0, -50 * (sleepTimerEntries.values.length + 2)),
|
||||||
headings: [
|
headings: [
|
||||||
Text(context.l10n.sleep_timer),
|
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: [
|
children: [
|
||||||
|
for (final entry in sleepTimerEntries.entries)
|
||||||
|
PopSheetEntry(
|
||||||
|
value: entry.value,
|
||||||
|
enabled: sleepTimer != entry.value,
|
||||||
|
title: Text(entry.key),
|
||||||
|
),
|
||||||
PopSheetEntry(
|
PopSheetEntry(
|
||||||
value: const Duration(minutes: 15),
|
title: Text(
|
||||||
title: Text(context.l10n.mins(15)),
|
customHoursEnabled
|
||||||
),
|
? context.l10n.custom_hours
|
||||||
PopSheetEntry(
|
: sleepTimer.toHumanReadableString(),
|
||||||
value: const Duration(minutes: 30),
|
),
|
||||||
title: Text(context.l10n.mins(30)),
|
// only enabled when there's no preset timers selected
|
||||||
),
|
enabled: customHoursEnabled,
|
||||||
PopSheetEntry(
|
onTap: () async {
|
||||||
value: const Duration(hours: 1),
|
final currentTime = TimeOfDay.now();
|
||||||
title: Text(context.l10n.hour(1)),
|
final time = await showTimePicker(
|
||||||
),
|
context: context,
|
||||||
PopSheetEntry(
|
initialTime: currentTime,
|
||||||
value: const Duration(hours: 2),
|
);
|
||||||
title: Text(context.l10n.hours(2)),
|
|
||||||
|
if (time != null) {
|
||||||
|
sleepTimerNotifier.setSleepTimer(
|
||||||
|
Duration(
|
||||||
|
hours: (time.hour - currentTime.hour).abs(),
|
||||||
|
minutes: (time.minute - currentTime.minute).abs(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
PopSheetEntry(
|
PopSheetEntry(
|
||||||
value: Duration.zero,
|
value: Duration.zero,
|
||||||
|
enabled: sleepTimer != Duration.zero && sleepTimer != null,
|
||||||
|
textColor: Colors.green,
|
||||||
title: Text(context.l10n.cancel),
|
title: Text(context.l10n.cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -194,8 +194,8 @@ class _AdaptivePopSheetListItem<T> extends StatelessWidget {
|
|||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
item.onTap?.call();
|
item.onTap?.call();
|
||||||
Navigator.pop(context);
|
|
||||||
if (item.value != null) {
|
if (item.value != null) {
|
||||||
|
Navigator.pop(context);
|
||||||
onSelected?.call(item.value as T);
|
onSelected?.call(item.value as T);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -119,9 +119,9 @@ class TrackDetailsDialog extends HookWidget {
|
|||||||
),
|
),
|
||||||
if (entry.value is Widget)
|
if (entry.value is Widget)
|
||||||
entry.value as Widget
|
entry.value as Widget
|
||||||
else
|
else if (entry.value is String)
|
||||||
Text(
|
Text(
|
||||||
entry.value,
|
entry.value as String,
|
||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
|
|
||||||
extension DurationToHumanReadableString on Duration {
|
extension DurationToHumanReadableString on Duration {
|
||||||
toHumanReadableString() =>
|
String toHumanReadableString() =>
|
||||||
"${inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(inSeconds.remainder(60))}";
|
"${inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(inSeconds.remainder(60))}";
|
||||||
}
|
}
|
||||||
|
@ -243,5 +243,6 @@
|
|||||||
"sleep_timer": "Sleep Timer",
|
"sleep_timer": "Sleep Timer",
|
||||||
"mins": "{minutes} Minutes",
|
"mins": "{minutes} Minutes",
|
||||||
"hours": "{hours} Hours",
|
"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