Skip to content

Commit

Permalink
feat: add sleep timer support
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Jun 19, 2023
1 parent 0620b62 commit 4a75f3d
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 21 deletions.
75 changes: 59 additions & 16 deletions lib/components/player/player_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand All @@ -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: [
Expand Down Expand Up @@ -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),
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
},
Expand Down
4 changes: 2 additions & 2 deletions lib/components/shared/dialogs/track_details_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
],
Expand Down
2 changes: 1 addition & 1 deletion lib/extensions/duration.dart
Original file line number Diff line number Diff line change
@@ -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))}";
}
3 changes: 2 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 31 additions & 0 deletions lib/provider/sleep_timer_provider.dart
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 4a75f3d

Please sign in to comment.