diff --git a/lib/features/player/view/player_when_expanded.dart b/lib/features/player/view/player_when_expanded.dart index 933121c..5fe0ea7 100644 --- a/lib/features/player/view/player_when_expanded.dart +++ b/lib/features/player/view/player_when_expanded.dart @@ -1,17 +1,11 @@ -import 'package:duration_picker/duration_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:miniplayer/miniplayer.dart'; import 'package:vaani/constants/sizes.dart'; import 'package:vaani/features/player/providers/currently_playing_provider.dart'; import 'package:vaani/features/player/providers/player_form.dart'; import 'package:vaani/features/player/view/audiobook_player.dart'; -import 'package:vaani/features/sleep_timer/core/sleep_timer.dart'; -import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart' - show sleepTimerProvider; -import 'package:vaani/settings/app_settings_provider.dart'; -import 'package:vaani/shared/extensions/duration_format.dart'; +import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart'; import 'package:vaani/shared/extensions/inverse_lerp.dart'; import 'package:vaani/shared/widgets/not_implemented.dart'; @@ -246,13 +240,13 @@ class PlayerWhenExpanded extends HookConsumerWidget { // chapter list const ChapterSelectionButton(), // settings - IconButton( - icon: const Icon(Icons.more_horiz), - onPressed: () { - // show toast - showNotImplementedToast(context); - }, - ), + // IconButton( + // icon: const Icon(Icons.more_horiz), + // onPressed: () { + // // show toast + // showNotImplementedToast(context); + // }, + // ), ], ), ), @@ -261,128 +255,3 @@ class PlayerWhenExpanded extends HookConsumerWidget { ); } } - -class SleepTimerButton extends HookConsumerWidget { - const SleepTimerButton({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final sleepTimer = ref.watch(sleepTimerProvider); - // if sleep timer is not active, show the button with the sleep timer icon - // if the sleep timer is active, show the remaining time in a pill shaped container - return Tooltip( - message: 'Sleep Timer', - child: InkWell( - onTap: () async { - pendingPlayerModals++; - // show the sleep timer dialog - final resultingDuration = await showDurationPicker( - context: context, - initialTime: ref - .watch(appSettingsProvider) - .playerSettings - .sleepTimerSettings - .defaultDuration, - ); - pendingPlayerModals--; - if (resultingDuration != null) { - // if 0 is selected, cancel the timer - if (resultingDuration.inSeconds == 0) { - ref.read(sleepTimerProvider.notifier).cancelTimer(); - } else { - ref.read(sleepTimerProvider.notifier).setTimer(resultingDuration); - } - } - }, - child: sleepTimer == null - ? Icon( - Icons.timer_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - : RemainingSleepTimeDisplay( - timer: sleepTimer, - ), - ), - ); - } -} - -class SleepTimerDialog extends HookConsumerWidget { - const SleepTimerDialog({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final sleepTimer = ref.watch(sleepTimerProvider); - final sleepTimerSettings = - ref.watch(appSettingsProvider).playerSettings.sleepTimerSettings; - final timerDurationController = useTextEditingController( - text: sleepTimerSettings.defaultDuration.inMinutes.toString(), - ); - - return AlertDialog( - title: const Text('Sleep Timer'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Set the duration for the sleep timer'), - TextField( - controller: timerDurationController, - keyboardType: TextInputType.number, - decoration: const InputDecoration( - labelText: 'Duration in minutes', - ), - ), - ], - ), - actions: [ - TextButton( - onPressed: () { - // sleepTimer.setTimer( - // Duration( - // minutes: int.tryParse(timerDurationController.text) ?? 0, - // ), - // ); - Navigator.of(context).pop(); - }, - child: const Text('Set Timer'), - ), - ], - ); - } -} - -class RemainingSleepTimeDisplay extends HookConsumerWidget { - const RemainingSleepTimeDisplay({ - super.key, - required this.timer, - }); - - final SleepTimer timer; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final remainingTime = useStream(timer.remainingTimeStream).data; - return Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular(16), - ), - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - child: Text( - timer.timer == null - ? timer.duration.smartBinaryFormat - : remainingTime?.smartBinaryFormat ?? '', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onPrimary, - ), - ), - ); - } -} diff --git a/lib/features/player/view/widgets/speed_selector.dart b/lib/features/player/view/widgets/speed_selector.dart index 987579a..65a3bfa 100644 --- a/lib/features/player/view/widgets/speed_selector.dart +++ b/lib/features/player/view/widgets/speed_selector.dart @@ -6,6 +6,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:list_wheel_scroll_view_nls/list_wheel_scroll_view_nls.dart'; import 'package:vaani/features/player/providers/audiobook_player.dart'; import 'package:vaani/settings/app_settings_provider.dart'; +import 'package:vaani/shared/hooks.dart'; + +const double itemExtent = 25; class SpeedSelector extends HookConsumerWidget { const SpeedSelector({ @@ -34,11 +37,11 @@ class SpeedSelector extends HookConsumerWidget { // the speed options final minSpeed = min( - speeds.reduce((minSpeedSoFar, element) => min(minSpeedSoFar, element)), + speeds.reduce(min), playerSettings.minSpeed, ); final maxSpeed = max( - speeds.reduce((maxSpeedSoFar, element) => max(maxSpeedSoFar, element)), + speeds.reduce(max), playerSettings.maxSpeed, ); final speedIncrement = playerSettings.speedIncrement; @@ -53,10 +56,9 @@ class SpeedSelector extends HookConsumerWidget { }, ); - final scrollController = FixedExtentScrollController( + final scrollController = useFixedExtentScrollController( initialItem: availableSpeedsList.indexOf(currentSpeed), ); - const double itemExtent = 25; return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -89,7 +91,6 @@ class SpeedSelector extends HookConsumerWidget { availableSpeedsList: availableSpeedsList, speedState: speedState, scrollController: scrollController, - itemExtent: itemExtent, ), ), ), @@ -159,14 +160,12 @@ class SpeedWheel extends StatelessWidget { required this.availableSpeedsList, required this.speedState, required this.scrollController, - required this.itemExtent, this.showIncrementButtons = true, }); final List availableSpeedsList; final ValueNotifier speedState; final FixedExtentScrollController scrollController; - final double itemExtent; final bool showIncrementButtons; @override @@ -203,7 +202,7 @@ class SpeedWheel extends StatelessWidget { physics: const FixedExtentScrollPhysics(), children: availableSpeedsList .map( - (speed) => SpeedLine(itemExtent: itemExtent, speed: speed), + (speed) => SpeedLine(speed: speed), ) .toList(), onSelectedItemChanged: (index) { @@ -236,11 +235,9 @@ class SpeedWheel extends StatelessWidget { class SpeedLine extends StatelessWidget { const SpeedLine({ super.key, - required this.itemExtent, required this.speed, }); - final double itemExtent; final double speed; @override @@ -250,7 +247,6 @@ class SpeedLine extends StatelessWidget { // a vertical line Expanded( child: Container( - // height: itemExtent, // thick if multiple of 1, thin if multiple of 0.5 and transparent if multiple of 0.05 width: speed % 0.5 == 0 ? 3 diff --git a/lib/features/sleep_timer/core/sleep_timer.dart b/lib/features/sleep_timer/core/sleep_timer.dart index 215c264..9350436 100644 --- a/lib/features/sleep_timer/core/sleep_timer.dart +++ b/lib/features/sleep_timer/core/sleep_timer.dart @@ -19,7 +19,17 @@ class SleepTimer { set duration(Duration value) { _duration = value; - clearCountDownTimer(); + _logger.fine('duration set to $value'); + + /// if the timer is active, restart it with the new duration + /// if the timer is not active, do nothing + if (isActive && player.playing) { + _logger.fine('timer is active counting down with new duration'); + startCountDown(value); + } else { + _logger.fine('timer is not active'); + clearCountDownTimer(); + } } /// The player to be paused diff --git a/lib/features/sleep_timer/providers/sleep_timer_provider.dart b/lib/features/sleep_timer/providers/sleep_timer_provider.dart index 92e5eb8..5c9ac2d 100644 --- a/lib/features/sleep_timer/providers/sleep_timer_provider.dart +++ b/lib/features/sleep_timer/providers/sleep_timer_provider.dart @@ -11,20 +11,16 @@ part 'sleep_timer_provider.g.dart'; class SleepTimer extends _$SleepTimer { @override core.SleepTimer? build() { - final appSettings = ref.watch(appSettingsProvider); - final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings; - bool isEnabled = sleepTimerSettings.autoTurnOnTimer; - if (!isEnabled) { + final sleepTimerSettings = ref.watch(sleepTimerSettingsProvider); + if (!sleepTimerSettings.autoTurnOnTimer) { return null; } if ((!sleepTimerSettings.alwaysAutoTurnOnTimer) && - (sleepTimerSettings.autoTurnOnTime - .toTimeOfDay() - .isAfter(TimeOfDay.now()) && - sleepTimerSettings.autoTurnOffTime - .toTimeOfDay() - .isBefore(TimeOfDay.now()))) { + !shouldBuildRightNow( + sleepTimerSettings.autoTurnOnTime, + sleepTimerSettings.autoTurnOffTime, + )) { return null; } @@ -36,10 +32,16 @@ class SleepTimer extends _$SleepTimer { return sleepTimer; } - void setTimer(Duration resultingDuration) { + void setTimer(Duration? resultingDuration, {bool notifyListeners = true}) { + if (resultingDuration == null || resultingDuration.inSeconds == 0) { + cancelTimer(); + return; + } if (state != null) { state!.duration = resultingDuration; - ref.notifyListeners(); + if (notifyListeners) { + ref.notifyListeners(); + } } else { final timer = core.SleepTimer( duration: resultingDuration, @@ -62,3 +64,11 @@ class SleepTimer extends _$SleepTimer { state = null; } } + +bool shouldBuildRightNow(Duration autoTurnOnTime, Duration autoTurnOffTime) { + final now = TimeOfDay.now(); + return now.isBetween( + autoTurnOnTime.toTimeOfDay(), + autoTurnOffTime.toTimeOfDay(), + ); +} diff --git a/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart b/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart index 6d820e4..7daade0 100644 --- a/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart +++ b/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart @@ -6,7 +6,7 @@ part of 'sleep_timer_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$sleepTimerHash() => r'4f80bcc342e918c70c547b8b24790ccd88aba8c3'; +String _$sleepTimerHash() => r'2679454a217d0630a833d730557ab4e4feac2e56'; /// See also [SleepTimer]. @ProviderFor(SleepTimer) diff --git a/lib/features/sleep_timer/view/sleep_timer_button.dart b/lib/features/sleep_timer/view/sleep_timer_button.dart new file mode 100644 index 0000000..075b0e4 --- /dev/null +++ b/lib/features/sleep_timer/view/sleep_timer_button.dart @@ -0,0 +1,366 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:list_wheel_scroll_view_nls/list_wheel_scroll_view_nls.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:vaani/features/player/view/player_when_expanded.dart'; +import 'package:vaani/features/player/view/widgets/speed_selector.dart'; +import 'package:vaani/features/sleep_timer/core/sleep_timer.dart'; +import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart' + show sleepTimerProvider; +import 'package:vaani/main.dart'; +import 'package:vaani/settings/app_settings_provider.dart'; +import 'package:vaani/shared/extensions/duration_format.dart'; +import 'package:vaani/shared/hooks.dart'; + +class SleepTimerButton extends HookConsumerWidget { + const SleepTimerButton({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sleepTimer = ref.watch(sleepTimerProvider); + final durationState = useState(sleepTimer?.duration); + + // if sleep timer is not active, show the button with the sleep timer icon + // if the sleep timer is active, show the remaining time in a pill shaped container + return Tooltip( + message: 'Sleep Timer', + child: InkWell( + onTap: () async { + appLogger.fine('Sleep Timer button pressed'); + pendingPlayerModals++; + // show the sleep timer dialog + await showModalBottomSheet( + context: context, + barrierLabel: 'Sleep Timer', + builder: (context) { + return SleepTimerBottomSheet( + onDurationSelected: (duration) { + durationState.value = duration; + // ref + // .read(sleepTimerProvider.notifier) + // .setTimer(duration, notifyListeners: false); + }, + ); + }, + ); + pendingPlayerModals--; + ref.read(sleepTimerProvider.notifier).setTimer(durationState.value); + appLogger + .fine('Sleep Timer dialog closed with ${durationState.value}'); + }, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: sleepTimer == null + ? Icon( + Symbols.bedtime, + color: Theme.of(context).colorScheme.onSurface, + ) + : RemainingSleepTimeDisplay( + timer: sleepTimer, + ), + ), + ), + ); + } +} + +class SleepTimerBottomSheet extends HookConsumerWidget { + const SleepTimerBottomSheet({ + super.key, + this.onDurationSelected, + }); + + final void Function(Duration?)? onDurationSelected; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sleepTimer = ref.watch(sleepTimerProvider); + final sleepTimerSettings = ref.watch(sleepTimerSettingsProvider); + + final durationOptions = sleepTimerSettings.presetDurations; + final minDuration = Duration.zero; + final maxDuration = [ + ...durationOptions, + sleepTimerSettings.maxDuration, + ].reduce((a, b) => a > b ? a : b); + final incrementStep = Duration(minutes: 1); + final allPossibleDurations = [ + for (var i = minDuration; i <= maxDuration; i += incrementStep) i, + ]; + + final scrollController = useFixedExtentScrollController( + initialItem: + allPossibleDurations.indexOf(sleepTimer?.duration ?? minDuration), + ); + + final durationState = useState( + sleepTimer?.duration ?? minDuration, + ); + + // useEffect to rebuild the sleep timer when the duration changes + useEffect( + () { + onDurationSelected?.call(durationState.value); + return null; + }, + [durationState.value], + ); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // the title + Padding( + padding: const EdgeInsets.only(top: 16.0, bottom: 8.0), + child: Center( + child: Text( + 'Sleep Timer', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ), + + // a inverted triangle to indicate the speed selector + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Icon( + Icons.arrow_drop_down, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + + // the speed selector + Padding( + padding: const EdgeInsets.only(bottom: 8.0, right: 8.0, left: 8.0), + child: SizedBox( + height: 80, + child: SleepTimerWheel( + durationState: durationState, + availableDurations: allPossibleDurations, + scrollController: scrollController, + ), + ), + ), + + // a cancel button to cancel the sleep timer + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TextButton.icon( + onPressed: () { + ref.read(sleepTimerProvider.notifier).cancelTimer(); + onDurationSelected?.call(null); + Navigator.of(context).pop(); + }, + icon: const Icon(Symbols.bedtime_off), + label: const Text('Cancel Sleep Timer'), + ), + ), + + // the speed buttons + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 8.0, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: durationOptions + .map( + (timerDuration) => TextButton( + style: timerDuration == durationState.value + ? TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.primaryContainer, + foregroundColor: Theme.of(context) + .colorScheme + .onPrimaryContainer, + ) + // border if not selected + : TextButton.styleFrom( + side: BorderSide( + color: Theme.of(context) + .colorScheme + .primaryContainer, + ), + ), + onPressed: () async { + // animate the wheel to the selected speed + var index = allPossibleDurations.indexOf(timerDuration); + // if the speed is not in the list + if (index == -1) { + // find the nearest speed + final nearestDuration = allPossibleDurations.firstWhere( + (element) => element > timerDuration, + orElse: () => allPossibleDurations.last, + ); + index = allPossibleDurations.indexOf(nearestDuration); + } + await scrollController.animateToItem( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + child: Text(timerDuration.smartBinaryFormat), + ), + ) + .toList(), + ), + ), + ], + ); + } +} + +class RemainingSleepTimeDisplay extends HookConsumerWidget { + const RemainingSleepTimeDisplay({ + super.key, + required this.timer, + }); + + final SleepTimer timer; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final remainingTime = useStream(timer.remainingTimeStream).data; + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: Text( + timer.timer == null + ? timer.duration.smartBinaryFormat + : remainingTime?.smartBinaryFormat ?? '', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ); + } +} + +class SleepTimerWheel extends StatelessWidget { + const SleepTimerWheel({ + super.key, + required this.availableDurations, + required this.scrollController, + required this.durationState, + this.showIncrementButtons = true, + }); + + final List availableDurations; + final ValueNotifier durationState; + final FixedExtentScrollController scrollController; + final bool showIncrementButtons; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // a minus button to decrease the speed + if (showIncrementButtons) + IconButton.filledTonal( + icon: const Icon(Icons.remove), + onPressed: () { + // animate to index - 1 + final index = availableDurations + .indexOf(durationState.value ?? Duration.zero); + if (index > 0) { + scrollController.animateToItem( + index - 1, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + }, + ), + // the speed selector wheel + Flexible( + child: ListWheelScrollViewX( + controller: scrollController, + scrollDirection: Axis.horizontal, + itemExtent: itemExtent, + diameterRatio: 1.5, squeeze: 1.2, + // useMagnifier: true, + // magnification: 1.5, + physics: const FixedExtentScrollPhysics(), + children: availableDurations + .map( + (duration) => DurationLine(duration: duration), + ) + .toList(), + onSelectedItemChanged: (index) { + durationState.value = availableDurations[index]; + }, + ), + ), + + if (showIncrementButtons) + // a plus button to increase the speed + IconButton.filledTonal( + icon: const Icon(Icons.add), + onPressed: () { + // animate to index + 1 + final index = availableDurations + .indexOf(durationState.value ?? Duration.zero); + if (index < availableDurations.length - 1) { + scrollController.animateToItem( + index + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + }, + ), + ], + ); + } +} + +class DurationLine extends StatelessWidget { + const DurationLine({ + super.key, + required this.duration, + }); + + final Duration duration; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // a vertical line + Expanded( + child: Container( + // thick if multiple of 1, thin if multiple of 0.5 and transparent if multiple of 0.05 + width: duration.inMinutes % 5 == 0 + ? 3 + : duration.inMinutes % 2.5 == 0 + ? 2 + : 0.5, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Opacity( + opacity: duration.inMinutes % 2.5 == 0 ? 1 : 0, + child: Text( + '${duration.inMinutes}m', + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelSmall?.fontSize, + ), + ), + ), + ], + ); + } +} diff --git a/lib/settings/app_settings_provider.dart b/lib/settings/app_settings_provider.dart index 662a043..6d61774 100644 --- a/lib/settings/app_settings_provider.dart +++ b/lib/settings/app_settings_provider.dart @@ -60,3 +60,19 @@ class AppSettings extends _$AppSettings { state = const model.AppSettings(); } } + +// SleepTimerSettings provider but only rebuilds when the sleep timer settings change +@Riverpod(keepAlive: true) +class SleepTimerSettings extends _$SleepTimerSettings { + @override + model.SleepTimerSettings build() { + final settings = ref.read(appSettingsProvider).sleepTimerSettings; + state = settings; + ref.listen(appSettingsProvider, (a, b) { + if (a?.sleepTimerSettings != b.sleepTimerSettings) { + state = b.sleepTimerSettings; + } + }); + return state; + } +} diff --git a/lib/settings/app_settings_provider.g.dart b/lib/settings/app_settings_provider.g.dart index 2f321ee..df95738 100644 --- a/lib/settings/app_settings_provider.g.dart +++ b/lib/settings/app_settings_provider.g.dart @@ -21,5 +21,22 @@ final appSettingsProvider = ); typedef _$AppSettings = Notifier; +String _$sleepTimerSettingsHash() => + r'85bb3d3fb292b9a3a5b771d86e5fc57718519c69'; + +/// See also [SleepTimerSettings]. +@ProviderFor(SleepTimerSettings) +final sleepTimerSettingsProvider = + NotifierProvider.internal( + SleepTimerSettings.new, + name: r'sleepTimerSettingsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$sleepTimerSettingsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$SleepTimerSettings = Notifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/settings/models/app_settings.dart b/lib/settings/models/app_settings.dart index 7debe11..80262ea 100644 --- a/lib/settings/models/app_settings.dart +++ b/lib/settings/models/app_settings.dart @@ -14,6 +14,7 @@ class AppSettings with _$AppSettings { const factory AppSettings({ @Default(ThemeSettings()) ThemeSettings themeSettings, @Default(PlayerSettings()) PlayerSettings playerSettings, + @Default(SleepTimerSettings()) SleepTimerSettings sleepTimerSettings, @Default(DownloadSettings()) DownloadSettings downloadSettings, @Default(NotificationSettings()) NotificationSettings notificationSettings, @Default(ShakeDetectionSettings()) @@ -49,7 +50,6 @@ class PlayerSettings with _$PlayerSettings { @Default(0.05) double speedIncrement, @Default(0.1) double minSpeed, @Default(4) double maxSpeed, - @Default(SleepTimerSettings()) SleepTimerSettings sleepTimerSettings, @Default(Duration(seconds: 10)) Duration minimumPositionForReporting, @Default(Duration(seconds: 10)) Duration playbackReportInterval, @Default(Duration(seconds: 15)) Duration markCompleteWhenTimeLeft, @@ -81,22 +81,23 @@ class MinimizedPlayerSettings with _$MinimizedPlayerSettings { _$MinimizedPlayerSettingsFromJson(json); } -enum SleepTimerShakeSenseMode { never, always, nearEnds } - @freezed class SleepTimerSettings with _$SleepTimerSettings { const factory SleepTimerSettings({ @Default(Duration(minutes: 15)) Duration defaultDuration, - @Default(SleepTimerShakeSenseMode.always) - SleepTimerShakeSenseMode shakeSenseMode, - - /// the duration in which the shake is detected before the end of the timer and after the timer ends - /// only used if [shakeSenseMode] is [SleepTimerShakeSenseMode.nearEnds] - @Default(Duration(seconds: 30)) Duration shakeSenseDuration, - @Default(true) bool vibrateWhenReset, - @Default(false) bool beepWhenReset, + @Default( + [ + Duration(minutes: 5), + Duration(minutes: 10), + Duration(minutes: 15), + Duration(minutes: 20), + Duration(minutes: 30), + ], + ) + List presetDurations, + @Default(Duration(minutes: 100)) Duration maxDuration, @Default(false) bool fadeOutAudio, - @Default(0.5) double shakeDetectThreshold, + @Default(Duration(seconds: 20)) Duration fadeOutDuration, /// if true, the player will automatically rewind the audio when the sleep timer is stopped @Default(false) bool autoRewindWhenStopped, @@ -115,7 +116,7 @@ class SleepTimerSettings with _$SleepTimerSettings { @Default(false) bool autoTurnOnTimer, /// always auto turn on timer settings or during specific times - @Default(true) bool alwaysAutoTurnOnTimer, + @Default(false) bool alwaysAutoTurnOnTimer, /// auto timer settings, only used if [alwaysAutoTurnOnTimer] is false /// diff --git a/lib/settings/models/app_settings.freezed.dart b/lib/settings/models/app_settings.freezed.dart index 4980b85..3f8fd48 100644 --- a/lib/settings/models/app_settings.freezed.dart +++ b/lib/settings/models/app_settings.freezed.dart @@ -22,6 +22,8 @@ AppSettings _$AppSettingsFromJson(Map json) { mixin _$AppSettings { ThemeSettings get themeSettings => throw _privateConstructorUsedError; PlayerSettings get playerSettings => throw _privateConstructorUsedError; + SleepTimerSettings get sleepTimerSettings => + throw _privateConstructorUsedError; DownloadSettings get downloadSettings => throw _privateConstructorUsedError; NotificationSettings get notificationSettings => throw _privateConstructorUsedError; @@ -47,12 +49,14 @@ abstract class $AppSettingsCopyWith<$Res> { $Res call( {ThemeSettings themeSettings, PlayerSettings playerSettings, + SleepTimerSettings sleepTimerSettings, DownloadSettings downloadSettings, NotificationSettings notificationSettings, ShakeDetectionSettings shakeDetectionSettings}); $ThemeSettingsCopyWith<$Res> get themeSettings; $PlayerSettingsCopyWith<$Res> get playerSettings; + $SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings; $DownloadSettingsCopyWith<$Res> get downloadSettings; $NotificationSettingsCopyWith<$Res> get notificationSettings; $ShakeDetectionSettingsCopyWith<$Res> get shakeDetectionSettings; @@ -75,6 +79,7 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings> $Res call({ Object? themeSettings = null, Object? playerSettings = null, + Object? sleepTimerSettings = null, Object? downloadSettings = null, Object? notificationSettings = null, Object? shakeDetectionSettings = null, @@ -88,6 +93,10 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings> ? _value.playerSettings : playerSettings // ignore: cast_nullable_to_non_nullable as PlayerSettings, + sleepTimerSettings: null == sleepTimerSettings + ? _value.sleepTimerSettings + : sleepTimerSettings // ignore: cast_nullable_to_non_nullable + as SleepTimerSettings, downloadSettings: null == downloadSettings ? _value.downloadSettings : downloadSettings // ignore: cast_nullable_to_non_nullable @@ -123,6 +132,17 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings> }); } + /// Create a copy of AppSettings + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings { + return $SleepTimerSettingsCopyWith<$Res>(_value.sleepTimerSettings, + (value) { + return _then(_value.copyWith(sleepTimerSettings: value) as $Val); + }); + } + /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @override @@ -167,6 +187,7 @@ abstract class _$$AppSettingsImplCopyWith<$Res> $Res call( {ThemeSettings themeSettings, PlayerSettings playerSettings, + SleepTimerSettings sleepTimerSettings, DownloadSettings downloadSettings, NotificationSettings notificationSettings, ShakeDetectionSettings shakeDetectionSettings}); @@ -176,6 +197,8 @@ abstract class _$$AppSettingsImplCopyWith<$Res> @override $PlayerSettingsCopyWith<$Res> get playerSettings; @override + $SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings; + @override $DownloadSettingsCopyWith<$Res> get downloadSettings; @override $NotificationSettingsCopyWith<$Res> get notificationSettings; @@ -198,6 +221,7 @@ class __$$AppSettingsImplCopyWithImpl<$Res> $Res call({ Object? themeSettings = null, Object? playerSettings = null, + Object? sleepTimerSettings = null, Object? downloadSettings = null, Object? notificationSettings = null, Object? shakeDetectionSettings = null, @@ -211,6 +235,10 @@ class __$$AppSettingsImplCopyWithImpl<$Res> ? _value.playerSettings : playerSettings // ignore: cast_nullable_to_non_nullable as PlayerSettings, + sleepTimerSettings: null == sleepTimerSettings + ? _value.sleepTimerSettings + : sleepTimerSettings // ignore: cast_nullable_to_non_nullable + as SleepTimerSettings, downloadSettings: null == downloadSettings ? _value.downloadSettings : downloadSettings // ignore: cast_nullable_to_non_nullable @@ -233,6 +261,7 @@ class _$AppSettingsImpl implements _AppSettings { const _$AppSettingsImpl( {this.themeSettings = const ThemeSettings(), this.playerSettings = const PlayerSettings(), + this.sleepTimerSettings = const SleepTimerSettings(), this.downloadSettings = const DownloadSettings(), this.notificationSettings = const NotificationSettings(), this.shakeDetectionSettings = const ShakeDetectionSettings()}); @@ -248,6 +277,9 @@ class _$AppSettingsImpl implements _AppSettings { final PlayerSettings playerSettings; @override @JsonKey() + final SleepTimerSettings sleepTimerSettings; + @override + @JsonKey() final DownloadSettings downloadSettings; @override @JsonKey() @@ -258,7 +290,7 @@ class _$AppSettingsImpl implements _AppSettings { @override String toString() { - return 'AppSettings(themeSettings: $themeSettings, playerSettings: $playerSettings, downloadSettings: $downloadSettings, notificationSettings: $notificationSettings, shakeDetectionSettings: $shakeDetectionSettings)'; + return 'AppSettings(themeSettings: $themeSettings, playerSettings: $playerSettings, sleepTimerSettings: $sleepTimerSettings, downloadSettings: $downloadSettings, notificationSettings: $notificationSettings, shakeDetectionSettings: $shakeDetectionSettings)'; } @override @@ -270,6 +302,8 @@ class _$AppSettingsImpl implements _AppSettings { other.themeSettings == themeSettings) && (identical(other.playerSettings, playerSettings) || other.playerSettings == playerSettings) && + (identical(other.sleepTimerSettings, sleepTimerSettings) || + other.sleepTimerSettings == sleepTimerSettings) && (identical(other.downloadSettings, downloadSettings) || other.downloadSettings == downloadSettings) && (identical(other.notificationSettings, notificationSettings) || @@ -280,8 +314,14 @@ class _$AppSettingsImpl implements _AppSettings { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, themeSettings, playerSettings, - downloadSettings, notificationSettings, shakeDetectionSettings); + int get hashCode => Object.hash( + runtimeType, + themeSettings, + playerSettings, + sleepTimerSettings, + downloadSettings, + notificationSettings, + shakeDetectionSettings); /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @@ -303,6 +343,7 @@ abstract class _AppSettings implements AppSettings { const factory _AppSettings( {final ThemeSettings themeSettings, final PlayerSettings playerSettings, + final SleepTimerSettings sleepTimerSettings, final DownloadSettings downloadSettings, final NotificationSettings notificationSettings, final ShakeDetectionSettings shakeDetectionSettings}) = _$AppSettingsImpl; @@ -315,6 +356,8 @@ abstract class _AppSettings implements AppSettings { @override PlayerSettings get playerSettings; @override + SleepTimerSettings get sleepTimerSettings; + @override DownloadSettings get downloadSettings; @override NotificationSettings get notificationSettings; @@ -552,8 +595,6 @@ mixin _$PlayerSettings { double get speedIncrement => throw _privateConstructorUsedError; double get minSpeed => throw _privateConstructorUsedError; double get maxSpeed => throw _privateConstructorUsedError; - SleepTimerSettings get sleepTimerSettings => - throw _privateConstructorUsedError; Duration get minimumPositionForReporting => throw _privateConstructorUsedError; Duration get playbackReportInterval => throw _privateConstructorUsedError; @@ -585,7 +626,6 @@ abstract class $PlayerSettingsCopyWith<$Res> { double speedIncrement, double minSpeed, double maxSpeed, - SleepTimerSettings sleepTimerSettings, Duration minimumPositionForReporting, Duration playbackReportInterval, Duration markCompleteWhenTimeLeft, @@ -593,7 +633,6 @@ abstract class $PlayerSettingsCopyWith<$Res> { $MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings; $ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings; - $SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings; } /// @nodoc @@ -619,7 +658,6 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings> Object? speedIncrement = null, Object? minSpeed = null, Object? maxSpeed = null, - Object? sleepTimerSettings = null, Object? minimumPositionForReporting = null, Object? playbackReportInterval = null, Object? markCompleteWhenTimeLeft = null, @@ -658,10 +696,6 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings> ? _value.maxSpeed : maxSpeed // ignore: cast_nullable_to_non_nullable as double, - sleepTimerSettings: null == sleepTimerSettings - ? _value.sleepTimerSettings - : sleepTimerSettings // ignore: cast_nullable_to_non_nullable - as SleepTimerSettings, minimumPositionForReporting: null == minimumPositionForReporting ? _value.minimumPositionForReporting : minimumPositionForReporting // ignore: cast_nullable_to_non_nullable @@ -702,17 +736,6 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings> return _then(_value.copyWith(expandedPlayerSettings: value) as $Val); }); } - - /// Create a copy of PlayerSettings - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings { - return $SleepTimerSettingsCopyWith<$Res>(_value.sleepTimerSettings, - (value) { - return _then(_value.copyWith(sleepTimerSettings: value) as $Val); - }); - } } /// @nodoc @@ -732,7 +755,6 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res> double speedIncrement, double minSpeed, double maxSpeed, - SleepTimerSettings sleepTimerSettings, Duration minimumPositionForReporting, Duration playbackReportInterval, Duration markCompleteWhenTimeLeft, @@ -742,8 +764,6 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res> $MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings; @override $ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings; - @override - $SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings; } /// @nodoc @@ -767,7 +787,6 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res> Object? speedIncrement = null, Object? minSpeed = null, Object? maxSpeed = null, - Object? sleepTimerSettings = null, Object? minimumPositionForReporting = null, Object? playbackReportInterval = null, Object? markCompleteWhenTimeLeft = null, @@ -806,10 +825,6 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res> ? _value.maxSpeed : maxSpeed // ignore: cast_nullable_to_non_nullable as double, - sleepTimerSettings: null == sleepTimerSettings - ? _value.sleepTimerSettings - : sleepTimerSettings // ignore: cast_nullable_to_non_nullable - as SleepTimerSettings, minimumPositionForReporting: null == minimumPositionForReporting ? _value.minimumPositionForReporting : minimumPositionForReporting // ignore: cast_nullable_to_non_nullable @@ -842,7 +857,6 @@ class _$PlayerSettingsImpl implements _PlayerSettings { this.speedIncrement = 0.05, this.minSpeed = 0.1, this.maxSpeed = 4, - this.sleepTimerSettings = const SleepTimerSettings(), this.minimumPositionForReporting = const Duration(seconds: 10), this.playbackReportInterval = const Duration(seconds: 10), this.markCompleteWhenTimeLeft = const Duration(seconds: 15), @@ -884,9 +898,6 @@ class _$PlayerSettingsImpl implements _PlayerSettings { final double maxSpeed; @override @JsonKey() - final SleepTimerSettings sleepTimerSettings; - @override - @JsonKey() final Duration minimumPositionForReporting; @override @JsonKey() @@ -900,7 +911,7 @@ class _$PlayerSettingsImpl implements _PlayerSettings { @override String toString() { - return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, speedIncrement: $speedIncrement, minSpeed: $minSpeed, maxSpeed: $maxSpeed, sleepTimerSettings: $sleepTimerSettings, minimumPositionForReporting: $minimumPositionForReporting, playbackReportInterval: $playbackReportInterval, markCompleteWhenTimeLeft: $markCompleteWhenTimeLeft, configurePlayerForEveryBook: $configurePlayerForEveryBook)'; + return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, speedIncrement: $speedIncrement, minSpeed: $minSpeed, maxSpeed: $maxSpeed, minimumPositionForReporting: $minimumPositionForReporting, playbackReportInterval: $playbackReportInterval, markCompleteWhenTimeLeft: $markCompleteWhenTimeLeft, configurePlayerForEveryBook: $configurePlayerForEveryBook)'; } @override @@ -924,8 +935,6 @@ class _$PlayerSettingsImpl implements _PlayerSettings { other.minSpeed == minSpeed) && (identical(other.maxSpeed, maxSpeed) || other.maxSpeed == maxSpeed) && - (identical(other.sleepTimerSettings, sleepTimerSettings) || - other.sleepTimerSettings == sleepTimerSettings) && (identical(other.minimumPositionForReporting, minimumPositionForReporting) || other.minimumPositionForReporting == @@ -953,7 +962,6 @@ class _$PlayerSettingsImpl implements _PlayerSettings { speedIncrement, minSpeed, maxSpeed, - sleepTimerSettings, minimumPositionForReporting, playbackReportInterval, markCompleteWhenTimeLeft, @@ -986,7 +994,6 @@ abstract class _PlayerSettings implements PlayerSettings { final double speedIncrement, final double minSpeed, final double maxSpeed, - final SleepTimerSettings sleepTimerSettings, final Duration minimumPositionForReporting, final Duration playbackReportInterval, final Duration markCompleteWhenTimeLeft, @@ -1012,8 +1019,6 @@ abstract class _PlayerSettings implements PlayerSettings { @override double get maxSpeed; @override - SleepTimerSettings get sleepTimerSettings; - @override Duration get minimumPositionForReporting; @override Duration get playbackReportInterval; @@ -1374,16 +1379,10 @@ SleepTimerSettings _$SleepTimerSettingsFromJson(Map json) { /// @nodoc mixin _$SleepTimerSettings { Duration get defaultDuration => throw _privateConstructorUsedError; - SleepTimerShakeSenseMode get shakeSenseMode => - throw _privateConstructorUsedError; - - /// the duration in which the shake is detected before the end of the timer and after the timer ends - /// only used if [shakeSenseMode] is [SleepTimerShakeSenseMode.nearEnds] - Duration get shakeSenseDuration => throw _privateConstructorUsedError; - bool get vibrateWhenReset => throw _privateConstructorUsedError; - bool get beepWhenReset => throw _privateConstructorUsedError; + List get presetDurations => throw _privateConstructorUsedError; + Duration get maxDuration => throw _privateConstructorUsedError; bool get fadeOutAudio => throw _privateConstructorUsedError; - double get shakeDetectThreshold => throw _privateConstructorUsedError; + Duration get fadeOutDuration => throw _privateConstructorUsedError; /// if true, the player will automatically rewind the audio when the sleep timer is stopped bool get autoRewindWhenStopped => throw _privateConstructorUsedError; @@ -1422,12 +1421,10 @@ abstract class $SleepTimerSettingsCopyWith<$Res> { @useResult $Res call( {Duration defaultDuration, - SleepTimerShakeSenseMode shakeSenseMode, - Duration shakeSenseDuration, - bool vibrateWhenReset, - bool beepWhenReset, + List presetDurations, + Duration maxDuration, bool fadeOutAudio, - double shakeDetectThreshold, + Duration fadeOutDuration, bool autoRewindWhenStopped, Map autoRewindDurations, bool autoTurnOnTimer, @@ -1452,12 +1449,10 @@ class _$SleepTimerSettingsCopyWithImpl<$Res, $Val extends SleepTimerSettings> @override $Res call({ Object? defaultDuration = null, - Object? shakeSenseMode = null, - Object? shakeSenseDuration = null, - Object? vibrateWhenReset = null, - Object? beepWhenReset = null, + Object? presetDurations = null, + Object? maxDuration = null, Object? fadeOutAudio = null, - Object? shakeDetectThreshold = null, + Object? fadeOutDuration = null, Object? autoRewindWhenStopped = null, Object? autoRewindDurations = null, Object? autoTurnOnTimer = null, @@ -1470,30 +1465,22 @@ class _$SleepTimerSettingsCopyWithImpl<$Res, $Val extends SleepTimerSettings> ? _value.defaultDuration : defaultDuration // ignore: cast_nullable_to_non_nullable as Duration, - shakeSenseMode: null == shakeSenseMode - ? _value.shakeSenseMode - : shakeSenseMode // ignore: cast_nullable_to_non_nullable - as SleepTimerShakeSenseMode, - shakeSenseDuration: null == shakeSenseDuration - ? _value.shakeSenseDuration - : shakeSenseDuration // ignore: cast_nullable_to_non_nullable + presetDurations: null == presetDurations + ? _value.presetDurations + : presetDurations // ignore: cast_nullable_to_non_nullable + as List, + maxDuration: null == maxDuration + ? _value.maxDuration + : maxDuration // ignore: cast_nullable_to_non_nullable as Duration, - vibrateWhenReset: null == vibrateWhenReset - ? _value.vibrateWhenReset - : vibrateWhenReset // ignore: cast_nullable_to_non_nullable - as bool, - beepWhenReset: null == beepWhenReset - ? _value.beepWhenReset - : beepWhenReset // ignore: cast_nullable_to_non_nullable - as bool, fadeOutAudio: null == fadeOutAudio ? _value.fadeOutAudio : fadeOutAudio // ignore: cast_nullable_to_non_nullable as bool, - shakeDetectThreshold: null == shakeDetectThreshold - ? _value.shakeDetectThreshold - : shakeDetectThreshold // ignore: cast_nullable_to_non_nullable - as double, + fadeOutDuration: null == fadeOutDuration + ? _value.fadeOutDuration + : fadeOutDuration // ignore: cast_nullable_to_non_nullable + as Duration, autoRewindWhenStopped: null == autoRewindWhenStopped ? _value.autoRewindWhenStopped : autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable @@ -1532,12 +1519,10 @@ abstract class _$$SleepTimerSettingsImplCopyWith<$Res> @useResult $Res call( {Duration defaultDuration, - SleepTimerShakeSenseMode shakeSenseMode, - Duration shakeSenseDuration, - bool vibrateWhenReset, - bool beepWhenReset, + List presetDurations, + Duration maxDuration, bool fadeOutAudio, - double shakeDetectThreshold, + Duration fadeOutDuration, bool autoRewindWhenStopped, Map autoRewindDurations, bool autoTurnOnTimer, @@ -1560,12 +1545,10 @@ class __$$SleepTimerSettingsImplCopyWithImpl<$Res> @override $Res call({ Object? defaultDuration = null, - Object? shakeSenseMode = null, - Object? shakeSenseDuration = null, - Object? vibrateWhenReset = null, - Object? beepWhenReset = null, + Object? presetDurations = null, + Object? maxDuration = null, Object? fadeOutAudio = null, - Object? shakeDetectThreshold = null, + Object? fadeOutDuration = null, Object? autoRewindWhenStopped = null, Object? autoRewindDurations = null, Object? autoTurnOnTimer = null, @@ -1578,30 +1561,22 @@ class __$$SleepTimerSettingsImplCopyWithImpl<$Res> ? _value.defaultDuration : defaultDuration // ignore: cast_nullable_to_non_nullable as Duration, - shakeSenseMode: null == shakeSenseMode - ? _value.shakeSenseMode - : shakeSenseMode // ignore: cast_nullable_to_non_nullable - as SleepTimerShakeSenseMode, - shakeSenseDuration: null == shakeSenseDuration - ? _value.shakeSenseDuration - : shakeSenseDuration // ignore: cast_nullable_to_non_nullable + presetDurations: null == presetDurations + ? _value._presetDurations + : presetDurations // ignore: cast_nullable_to_non_nullable + as List, + maxDuration: null == maxDuration + ? _value.maxDuration + : maxDuration // ignore: cast_nullable_to_non_nullable as Duration, - vibrateWhenReset: null == vibrateWhenReset - ? _value.vibrateWhenReset - : vibrateWhenReset // ignore: cast_nullable_to_non_nullable - as bool, - beepWhenReset: null == beepWhenReset - ? _value.beepWhenReset - : beepWhenReset // ignore: cast_nullable_to_non_nullable - as bool, fadeOutAudio: null == fadeOutAudio ? _value.fadeOutAudio : fadeOutAudio // ignore: cast_nullable_to_non_nullable as bool, - shakeDetectThreshold: null == shakeDetectThreshold - ? _value.shakeDetectThreshold - : shakeDetectThreshold // ignore: cast_nullable_to_non_nullable - as double, + fadeOutDuration: null == fadeOutDuration + ? _value.fadeOutDuration + : fadeOutDuration // ignore: cast_nullable_to_non_nullable + as Duration, autoRewindWhenStopped: null == autoRewindWhenStopped ? _value.autoRewindWhenStopped : autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable @@ -1635,12 +1610,16 @@ class __$$SleepTimerSettingsImplCopyWithImpl<$Res> class _$SleepTimerSettingsImpl implements _SleepTimerSettings { const _$SleepTimerSettingsImpl( {this.defaultDuration = const Duration(minutes: 15), - this.shakeSenseMode = SleepTimerShakeSenseMode.always, - this.shakeSenseDuration = const Duration(seconds: 30), - this.vibrateWhenReset = true, - this.beepWhenReset = false, + final List presetDurations = const [ + Duration(minutes: 5), + Duration(minutes: 10), + Duration(minutes: 15), + Duration(minutes: 20), + Duration(minutes: 30) + ], + this.maxDuration = const Duration(minutes: 100), this.fadeOutAudio = false, - this.shakeDetectThreshold = 0.5, + this.fadeOutDuration = const Duration(seconds: 20), this.autoRewindWhenStopped = false, final Map autoRewindDurations = const { 5: Duration(seconds: 10), @@ -1650,10 +1629,11 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings { 120: Duration(minutes: 2) }, this.autoTurnOnTimer = false, - this.alwaysAutoTurnOnTimer = true, + this.alwaysAutoTurnOnTimer = false, this.autoTurnOnTime = const Duration(hours: 22, minutes: 0), this.autoTurnOffTime = const Duration(hours: 6, minutes: 0)}) - : _autoRewindDurations = autoRewindDurations; + : _presetDurations = presetDurations, + _autoRewindDurations = autoRewindDurations; factory _$SleepTimerSettingsImpl.fromJson(Map json) => _$$SleepTimerSettingsImplFromJson(json); @@ -1661,27 +1641,24 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings { @override @JsonKey() final Duration defaultDuration; + final List _presetDurations; @override @JsonKey() - final SleepTimerShakeSenseMode shakeSenseMode; + List get presetDurations { + if (_presetDurations is EqualUnmodifiableListView) return _presetDurations; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_presetDurations); + } - /// the duration in which the shake is detected before the end of the timer and after the timer ends - /// only used if [shakeSenseMode] is [SleepTimerShakeSenseMode.nearEnds] @override @JsonKey() - final Duration shakeSenseDuration; - @override - @JsonKey() - final bool vibrateWhenReset; - @override - @JsonKey() - final bool beepWhenReset; + final Duration maxDuration; @override @JsonKey() final bool fadeOutAudio; @override @JsonKey() - final double shakeDetectThreshold; + final Duration fadeOutDuration; /// if true, the player will automatically rewind the audio when the sleep timer is stopped @override @@ -1723,7 +1700,7 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings { @override String toString() { - return 'SleepTimerSettings(defaultDuration: $defaultDuration, shakeSenseMode: $shakeSenseMode, shakeSenseDuration: $shakeSenseDuration, vibrateWhenReset: $vibrateWhenReset, beepWhenReset: $beepWhenReset, fadeOutAudio: $fadeOutAudio, shakeDetectThreshold: $shakeDetectThreshold, autoRewindWhenStopped: $autoRewindWhenStopped, autoRewindDurations: $autoRewindDurations, autoTurnOnTimer: $autoTurnOnTimer, alwaysAutoTurnOnTimer: $alwaysAutoTurnOnTimer, autoTurnOnTime: $autoTurnOnTime, autoTurnOffTime: $autoTurnOffTime)'; + return 'SleepTimerSettings(defaultDuration: $defaultDuration, presetDurations: $presetDurations, maxDuration: $maxDuration, fadeOutAudio: $fadeOutAudio, fadeOutDuration: $fadeOutDuration, autoRewindWhenStopped: $autoRewindWhenStopped, autoRewindDurations: $autoRewindDurations, autoTurnOnTimer: $autoTurnOnTimer, alwaysAutoTurnOnTimer: $alwaysAutoTurnOnTimer, autoTurnOnTime: $autoTurnOnTime, autoTurnOffTime: $autoTurnOffTime)'; } @override @@ -1733,18 +1710,14 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings { other is _$SleepTimerSettingsImpl && (identical(other.defaultDuration, defaultDuration) || other.defaultDuration == defaultDuration) && - (identical(other.shakeSenseMode, shakeSenseMode) || - other.shakeSenseMode == shakeSenseMode) && - (identical(other.shakeSenseDuration, shakeSenseDuration) || - other.shakeSenseDuration == shakeSenseDuration) && - (identical(other.vibrateWhenReset, vibrateWhenReset) || - other.vibrateWhenReset == vibrateWhenReset) && - (identical(other.beepWhenReset, beepWhenReset) || - other.beepWhenReset == beepWhenReset) && + const DeepCollectionEquality() + .equals(other._presetDurations, _presetDurations) && + (identical(other.maxDuration, maxDuration) || + other.maxDuration == maxDuration) && (identical(other.fadeOutAudio, fadeOutAudio) || other.fadeOutAudio == fadeOutAudio) && - (identical(other.shakeDetectThreshold, shakeDetectThreshold) || - other.shakeDetectThreshold == shakeDetectThreshold) && + (identical(other.fadeOutDuration, fadeOutDuration) || + other.fadeOutDuration == fadeOutDuration) && (identical(other.autoRewindWhenStopped, autoRewindWhenStopped) || other.autoRewindWhenStopped == autoRewindWhenStopped) && const DeepCollectionEquality() @@ -1764,12 +1737,10 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings { int get hashCode => Object.hash( runtimeType, defaultDuration, - shakeSenseMode, - shakeSenseDuration, - vibrateWhenReset, - beepWhenReset, + const DeepCollectionEquality().hash(_presetDurations), + maxDuration, fadeOutAudio, - shakeDetectThreshold, + fadeOutDuration, autoRewindWhenStopped, const DeepCollectionEquality().hash(_autoRewindDurations), autoTurnOnTimer, @@ -1797,12 +1768,10 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings { abstract class _SleepTimerSettings implements SleepTimerSettings { const factory _SleepTimerSettings( {final Duration defaultDuration, - final SleepTimerShakeSenseMode shakeSenseMode, - final Duration shakeSenseDuration, - final bool vibrateWhenReset, - final bool beepWhenReset, + final List presetDurations, + final Duration maxDuration, final bool fadeOutAudio, - final double shakeDetectThreshold, + final Duration fadeOutDuration, final bool autoRewindWhenStopped, final Map autoRewindDurations, final bool autoTurnOnTimer, @@ -1816,20 +1785,13 @@ abstract class _SleepTimerSettings implements SleepTimerSettings { @override Duration get defaultDuration; @override - SleepTimerShakeSenseMode get shakeSenseMode; - - /// the duration in which the shake is detected before the end of the timer and after the timer ends - /// only used if [shakeSenseMode] is [SleepTimerShakeSenseMode.nearEnds] + List get presetDurations; @override - Duration get shakeSenseDuration; - @override - bool get vibrateWhenReset; - @override - bool get beepWhenReset; + Duration get maxDuration; @override bool get fadeOutAudio; @override - double get shakeDetectThreshold; + Duration get fadeOutDuration; /// if true, the player will automatically rewind the audio when the sleep timer is stopped @override diff --git a/lib/settings/models/app_settings.g.dart b/lib/settings/models/app_settings.g.dart index da9ca9c..90b317e 100644 --- a/lib/settings/models/app_settings.g.dart +++ b/lib/settings/models/app_settings.g.dart @@ -16,6 +16,10 @@ _$AppSettingsImpl _$$AppSettingsImplFromJson(Map json) => ? const PlayerSettings() : PlayerSettings.fromJson( json['playerSettings'] as Map), + sleepTimerSettings: json['sleepTimerSettings'] == null + ? const SleepTimerSettings() + : SleepTimerSettings.fromJson( + json['sleepTimerSettings'] as Map), downloadSettings: json['downloadSettings'] == null ? const DownloadSettings() : DownloadSettings.fromJson( @@ -34,6 +38,7 @@ Map _$$AppSettingsImplToJson(_$AppSettingsImpl instance) => { 'themeSettings': instance.themeSettings, 'playerSettings': instance.playerSettings, + 'sleepTimerSettings': instance.sleepTimerSettings, 'downloadSettings': instance.downloadSettings, 'notificationSettings': instance.notificationSettings, 'shakeDetectionSettings': instance.shakeDetectionSettings, @@ -77,10 +82,6 @@ _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map json) => speedIncrement: (json['speedIncrement'] as num?)?.toDouble() ?? 0.05, minSpeed: (json['minSpeed'] as num?)?.toDouble() ?? 0.1, maxSpeed: (json['maxSpeed'] as num?)?.toDouble() ?? 4, - sleepTimerSettings: json['sleepTimerSettings'] == null - ? const SleepTimerSettings() - : SleepTimerSettings.fromJson( - json['sleepTimerSettings'] as Map), minimumPositionForReporting: json['minimumPositionForReporting'] == null ? const Duration(seconds: 10) : Duration( @@ -109,7 +110,6 @@ Map _$$PlayerSettingsImplToJson( 'speedIncrement': instance.speedIncrement, 'minSpeed': instance.minSpeed, 'maxSpeed': instance.maxSpeed, - 'sleepTimerSettings': instance.sleepTimerSettings, 'minimumPositionForReporting': instance.minimumPositionForReporting.inMicroseconds, 'playbackReportInterval': instance.playbackReportInterval.inMicroseconds, @@ -150,17 +150,23 @@ _$SleepTimerSettingsImpl _$$SleepTimerSettingsImplFromJson( defaultDuration: json['defaultDuration'] == null ? const Duration(minutes: 15) : Duration(microseconds: (json['defaultDuration'] as num).toInt()), - shakeSenseMode: $enumDecodeNullable( - _$SleepTimerShakeSenseModeEnumMap, json['shakeSenseMode']) ?? - SleepTimerShakeSenseMode.always, - shakeSenseDuration: json['shakeSenseDuration'] == null - ? const Duration(seconds: 30) - : Duration(microseconds: (json['shakeSenseDuration'] as num).toInt()), - vibrateWhenReset: json['vibrateWhenReset'] as bool? ?? true, - beepWhenReset: json['beepWhenReset'] as bool? ?? false, + presetDurations: (json['presetDurations'] as List?) + ?.map((e) => Duration(microseconds: (e as num).toInt())) + .toList() ?? + const [ + Duration(minutes: 5), + Duration(minutes: 10), + Duration(minutes: 15), + Duration(minutes: 20), + Duration(minutes: 30) + ], + maxDuration: json['maxDuration'] == null + ? const Duration(minutes: 100) + : Duration(microseconds: (json['maxDuration'] as num).toInt()), fadeOutAudio: json['fadeOutAudio'] as bool? ?? false, - shakeDetectThreshold: - (json['shakeDetectThreshold'] as num?)?.toDouble() ?? 0.5, + fadeOutDuration: json['fadeOutDuration'] == null + ? const Duration(seconds: 20) + : Duration(microseconds: (json['fadeOutDuration'] as num).toInt()), autoRewindWhenStopped: json['autoRewindWhenStopped'] as bool? ?? false, autoRewindDurations: (json['autoRewindDurations'] as Map?)?.map( @@ -175,7 +181,7 @@ _$SleepTimerSettingsImpl _$$SleepTimerSettingsImplFromJson( 120: Duration(minutes: 2) }, autoTurnOnTimer: json['autoTurnOnTimer'] as bool? ?? false, - alwaysAutoTurnOnTimer: json['alwaysAutoTurnOnTimer'] as bool? ?? true, + alwaysAutoTurnOnTimer: json['alwaysAutoTurnOnTimer'] as bool? ?? false, autoTurnOnTime: json['autoTurnOnTime'] == null ? const Duration(hours: 22, minutes: 0) : Duration(microseconds: (json['autoTurnOnTime'] as num).toInt()), @@ -188,13 +194,11 @@ Map _$$SleepTimerSettingsImplToJson( _$SleepTimerSettingsImpl instance) => { 'defaultDuration': instance.defaultDuration.inMicroseconds, - 'shakeSenseMode': - _$SleepTimerShakeSenseModeEnumMap[instance.shakeSenseMode]!, - 'shakeSenseDuration': instance.shakeSenseDuration.inMicroseconds, - 'vibrateWhenReset': instance.vibrateWhenReset, - 'beepWhenReset': instance.beepWhenReset, + 'presetDurations': + instance.presetDurations.map((e) => e.inMicroseconds).toList(), + 'maxDuration': instance.maxDuration.inMicroseconds, 'fadeOutAudio': instance.fadeOutAudio, - 'shakeDetectThreshold': instance.shakeDetectThreshold, + 'fadeOutDuration': instance.fadeOutDuration.inMicroseconds, 'autoRewindWhenStopped': instance.autoRewindWhenStopped, 'autoRewindDurations': instance.autoRewindDurations .map((k, e) => MapEntry(k.toString(), e.inMicroseconds)), @@ -204,12 +208,6 @@ Map _$$SleepTimerSettingsImplToJson( 'autoTurnOffTime': instance.autoTurnOffTime.inMicroseconds, }; -const _$SleepTimerShakeSenseModeEnumMap = { - SleepTimerShakeSenseMode.never: 'never', - SleepTimerShakeSenseMode.always: 'always', - SleepTimerShakeSenseMode.nearEnds: 'nearEnds', -}; - _$DownloadSettingsImpl _$$DownloadSettingsImplFromJson( Map json) => _$DownloadSettingsImpl( diff --git a/lib/settings/view/app_settings_page.dart b/lib/settings/view/app_settings_page.dart index 58754c6..77afaae 100644 --- a/lib/settings/view/app_settings_page.dart +++ b/lib/settings/view/app_settings_page.dart @@ -6,6 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:vaani/api/authenticated_user_provider.dart'; import 'package:vaani/api/server_provider.dart'; import 'package:vaani/router/router.dart'; @@ -26,7 +27,7 @@ class AppSettingsPage extends HookConsumerWidget { final registeredServersAsList = registeredServers.toList(); final availableUsers = ref.watch(authenticatedUserProvider); final serverURIController = useTextEditingController(); - final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings; + final sleepTimerSettings = appSettings.sleepTimerSettings; return SimpleSettingsPage( title: const Text('App Settings'), @@ -91,15 +92,15 @@ class AppSettingsPage extends HookConsumerWidget { 'Automatically turn on the sleep timer based on the time of day', ), leading: sleepTimerSettings.autoTurnOnTimer - ? const Icon(Icons.timer) - : const Icon(Icons.timer_off), + ? const Icon(Symbols.time_auto, fill: 1) + : const Icon(Symbols.timer_off, fill: 1), onPressed: (context) { context.pushNamed(Routes.autoSleepTimerSettings.name); }, value: sleepTimerSettings.autoTurnOnTimer, onToggle: (value) { ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.playerSettings.sleepTimerSettings( + appSettings.copyWith.sleepTimerSettings( autoTurnOnTimer: value, ), ); diff --git a/lib/settings/view/auto_sleep_timer_settings_page.dart b/lib/settings/view/auto_sleep_timer_settings_page.dart index 29524a9..fe7d4b3 100644 --- a/lib/settings/view/auto_sleep_timer_settings_page.dart +++ b/lib/settings/view/auto_sleep_timer_settings_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/shared/extensions/time_of_day.dart'; @@ -13,8 +14,13 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final appSettings = ref.watch(appSettingsProvider); - final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings; + final sleepTimerSettings = appSettings.sleepTimerSettings; + var enabled = sleepTimerSettings.autoTurnOnTimer && + !sleepTimerSettings.alwaysAutoTurnOnTimer; + final selectedValueColor = enabled + ? Theme.of(context).colorScheme.primary + : Theme.of(context).disabledColor; return SimpleSettingsPage( title: const Text('Auto Sleep Timer Settings'), sections: [ @@ -31,11 +37,11 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { 'Automatically turn on the sleep timer based on the time of day', ), leading: sleepTimerSettings.autoTurnOnTimer - ? const Icon(Icons.timer) - : const Icon(Icons.timer_off), + ? const Icon(Symbols.time_auto) + : const Icon(Symbols.timer_off), onToggle: (value) { ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.playerSettings.sleepTimerSettings( + appSettings.copyWith.sleepTimerSettings( autoTurnOnTimer: value, ), ); @@ -44,8 +50,9 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { ), // auto turn on time settings, enabled only when autoTurnOnTimer is enabled SettingsTile.navigation( - enabled: sleepTimerSettings.autoTurnOnTimer, - title: const Text('Auto Turn On Time'), + enabled: enabled, + leading: const Icon(Symbols.timer_play), + title: const Text('From'), description: const Text( 'Turn on the sleep timer at the specified time', ), @@ -57,22 +64,24 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { ); if (selected != null) { ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.playerSettings.sleepTimerSettings( + appSettings.copyWith.sleepTimerSettings( autoTurnOnTime: selected.toDuration(), ), ); } }, - value: Text( + trailing: Text( sleepTimerSettings.autoTurnOnTime.toTimeOfDay().format(context), + style: TextStyle(color: selectedValueColor), ), ), SettingsTile.navigation( - title: const Text('Auto Turn Off Time'), + enabled: enabled, + leading: const Icon(Symbols.timer_pause), + title: const Text('Until'), description: const Text( 'Turn off the sleep timer at the specified time', ), - enabled: sleepTimerSettings.autoTurnOnTimer, onPressed: (context) async { // navigate to the time picker final selected = await showTimePicker( @@ -81,18 +90,37 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { ); if (selected != null) { ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.playerSettings.sleepTimerSettings( + appSettings.copyWith.sleepTimerSettings( autoTurnOffTime: selected.toDuration(), ), ); } }, - value: Text( + trailing: Text( sleepTimerSettings.autoTurnOffTime .toTimeOfDay() .format(context), + style: TextStyle(color: selectedValueColor), ), ), + + // switch tile for always auto turn on timer no matter what + SettingsTile.switchTile( + leading: const Icon(Symbols.all_inclusive), + title: const Text('Always Auto Turn On Timer'), + description: const Text( + 'Always turn on the sleep timer, no matter what', + ), + onToggle: (value) { + ref.read(appSettingsProvider.notifier).update( + appSettings.copyWith.sleepTimerSettings( + alwaysAutoTurnOnTimer: value, + ), + ); + }, + enabled: sleepTimerSettings.autoTurnOnTimer, + initialValue: sleepTimerSettings.alwaysAutoTurnOnTimer, + ), ], ), ], diff --git a/lib/shared/extensions/duration_format.dart b/lib/shared/extensions/duration_format.dart index 09906cf..b492a37 100644 --- a/lib/shared/extensions/duration_format.dart +++ b/lib/shared/extensions/duration_format.dart @@ -9,14 +9,27 @@ extension DurationFormat on Duration { final minutes = inMinutes.remainder(60); final seconds = inSeconds.remainder(60); if (hours > 0) { - return '${hours}h ${minutes}m'; + // skip minutes if it's 0 + if (minutes == 0) { + return smartSingleFormat; + } + return '${Duration(hours: hours).smartBinaryFormat} ${Duration(minutes: minutes).smartSingleFormat}'; } else if (minutes > 0) { - return '${minutes}m ${seconds}s'; + if (seconds == 0) { + return smartSingleFormat; + } + return '${Duration(minutes: minutes).smartSingleFormat} ${Duration(seconds: seconds).smartSingleFormat}'; } else { - return '${seconds}s'; + return smartSingleFormat; } } + /// formats the duration using only 1 unit + /// if the duration is more than 1 hour, it will return `10h` + /// if the duration is less than 1 hour, it will return `30m` + /// if the duration is less than 1 minute, it will return `20s` + /// + /// rest of the duration will be ignored String get smartSingleFormat { if (inHours > 0) { return '${inHours}h'; diff --git a/lib/shared/extensions/time_of_day.dart b/lib/shared/extensions/time_of_day.dart index 702c5b6..9088b46 100644 --- a/lib/shared/extensions/time_of_day.dart +++ b/lib/shared/extensions/time_of_day.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; extension ToTimeOfDay on Duration { TimeOfDay toTimeOfDay() { - return TimeOfDay(hour: inHours, minute: inMinutes % 60); + return TimeOfDay( + hour: inHours % 24, + minute: inMinutes % 60, + ); } } @@ -28,4 +31,16 @@ extension TimeOfDayExtension on TimeOfDay { bool isBefore(TimeOfDay other) => this < other; bool isAfter(TimeOfDay other) => this > other; + + bool isBetween(TimeOfDay start, TimeOfDay end) { + // needs more logic to handle the case where start is after end + //but on the other day + if (start == end) { + return this == start; + } + if (start < end) { + return this >= start && this <= end; + } + return this >= start || this <= end; + } } diff --git a/lib/shared/hooks.dart b/lib/shared/hooks.dart index 8e27b3a..62c6116 100644 --- a/lib/shared/hooks.dart +++ b/lib/shared/hooks.dart @@ -28,3 +28,64 @@ void useTimer(VoidCallback callback, Duration delay) { [delay], ); } + +/// Creates [FixedExtentScrollController] that will be disposed automatically. +/// +/// See also: +/// - [FixedExtentScrollController] +FixedExtentScrollController useFixedExtentScrollController({ + String? debugLabel, + List? keys, + int initialItem = 0, + void Function(ScrollPosition)? onAttach, + void Function(ScrollPosition)? onDetach, +}) { + return use( + _FixedExtentScrollControllerHook( + debugLabel: debugLabel, + keys: keys, + initialItem: initialItem, + onAttach: onAttach, + onDetach: onDetach, + ), + ); +} + +class _FixedExtentScrollControllerHook + extends Hook { + const _FixedExtentScrollControllerHook({ + this.debugLabel, + super.keys, + required this.initialItem, + this.onAttach, + this.onDetach, + }); + + final int initialItem; + final void Function(ScrollPosition)? onAttach; + final void Function(ScrollPosition)? onDetach; + + final String? debugLabel; + + @override + HookState> + createState() => _FixedExtentScrollControllerHookState(); +} + +class _FixedExtentScrollControllerHookState extends HookState< + FixedExtentScrollController, _FixedExtentScrollControllerHook> { + late final controller = FixedExtentScrollController( + initialItem: hook.initialItem, + onAttach: hook.onAttach, + onDetach: hook.onDetach, + ); + + @override + FixedExtentScrollController build(BuildContext context) => controller; + + @override + void dispose() => controller.dispose(); + + @override + String get debugLabel => 'useFixedExtentScrollController'; +} diff --git a/pubspec.lock b/pubspec.lock index 0436fc6..e675e19 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -814,6 +814,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" + material_symbols_icons: + dependency: "direct main" + description: + name: material_symbols_icons + sha256: "66416c4e30bd363508e12669634fc4f3250b83b69e862de67f4f9c480cf42414" + url: "https://pub.dev" + source: hosted + version: "4.2785.1" media_kit: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 123f6f7..aaab763 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,7 @@ dependencies: list_wheel_scroll_view_nls: ^0.0.3 logging: ^1.2.0 lottie: ^3.1.0 + material_symbols_icons: ^4.2785.1 media_kit_libs_linux: any media_kit_libs_windows_audio: any miniplayer: