mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-07-06 17:31:33 +00:00
refactor: enhance sleep timer functionality and improve duration formatting
This commit is contained in:
parent
4c5dc53169
commit
ff41bf2b4d
11 changed files with 565 additions and 298 deletions
|
|
@ -1,17 +1,11 @@
|
||||||
import 'package:duration_picker/duration_picker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:miniplayer/miniplayer.dart';
|
import 'package:miniplayer/miniplayer.dart';
|
||||||
import 'package:vaani/constants/sizes.dart';
|
import 'package:vaani/constants/sizes.dart';
|
||||||
import 'package:vaani/features/player/providers/currently_playing_provider.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/providers/player_form.dart';
|
||||||
import 'package:vaani/features/player/view/audiobook_player.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/view/sleep_timer_button.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/shared/extensions/inverse_lerp.dart';
|
import 'package:vaani/shared/extensions/inverse_lerp.dart';
|
||||||
import 'package:vaani/shared/widgets/not_implemented.dart';
|
import 'package:vaani/shared/widgets/not_implemented.dart';
|
||||||
|
|
||||||
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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:list_wheel_scroll_view_nls/list_wheel_scroll_view_nls.dart';
|
||||||
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
||||||
import 'package:vaani/settings/app_settings_provider.dart';
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
|
import 'package:vaani/shared/hooks.dart';
|
||||||
|
|
||||||
|
const double itemExtent = 25;
|
||||||
|
|
||||||
class SpeedSelector extends HookConsumerWidget {
|
class SpeedSelector extends HookConsumerWidget {
|
||||||
const SpeedSelector({
|
const SpeedSelector({
|
||||||
|
|
@ -34,11 +37,11 @@ class SpeedSelector extends HookConsumerWidget {
|
||||||
|
|
||||||
// the speed options
|
// the speed options
|
||||||
final minSpeed = min(
|
final minSpeed = min(
|
||||||
speeds.reduce((minSpeedSoFar, element) => min(minSpeedSoFar, element)),
|
speeds.reduce(min),
|
||||||
playerSettings.minSpeed,
|
playerSettings.minSpeed,
|
||||||
);
|
);
|
||||||
final maxSpeed = max(
|
final maxSpeed = max(
|
||||||
speeds.reduce((maxSpeedSoFar, element) => max(maxSpeedSoFar, element)),
|
speeds.reduce(max),
|
||||||
playerSettings.maxSpeed,
|
playerSettings.maxSpeed,
|
||||||
);
|
);
|
||||||
final speedIncrement = playerSettings.speedIncrement;
|
final speedIncrement = playerSettings.speedIncrement;
|
||||||
|
|
@ -53,10 +56,9 @@ class SpeedSelector extends HookConsumerWidget {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final scrollController = FixedExtentScrollController(
|
final scrollController = useFixedExtentScrollController(
|
||||||
initialItem: availableSpeedsList.indexOf(currentSpeed),
|
initialItem: availableSpeedsList.indexOf(currentSpeed),
|
||||||
);
|
);
|
||||||
const double itemExtent = 25;
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -89,7 +91,6 @@ class SpeedSelector extends HookConsumerWidget {
|
||||||
availableSpeedsList: availableSpeedsList,
|
availableSpeedsList: availableSpeedsList,
|
||||||
speedState: speedState,
|
speedState: speedState,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
itemExtent: itemExtent,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -159,14 +160,12 @@ class SpeedWheel extends StatelessWidget {
|
||||||
required this.availableSpeedsList,
|
required this.availableSpeedsList,
|
||||||
required this.speedState,
|
required this.speedState,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.itemExtent,
|
|
||||||
this.showIncrementButtons = true,
|
this.showIncrementButtons = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<double> availableSpeedsList;
|
final List<double> availableSpeedsList;
|
||||||
final ValueNotifier<double> speedState;
|
final ValueNotifier<double> speedState;
|
||||||
final FixedExtentScrollController scrollController;
|
final FixedExtentScrollController scrollController;
|
||||||
final double itemExtent;
|
|
||||||
final bool showIncrementButtons;
|
final bool showIncrementButtons;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -203,7 +202,7 @@ class SpeedWheel extends StatelessWidget {
|
||||||
physics: const FixedExtentScrollPhysics(),
|
physics: const FixedExtentScrollPhysics(),
|
||||||
children: availableSpeedsList
|
children: availableSpeedsList
|
||||||
.map(
|
.map(
|
||||||
(speed) => SpeedLine(itemExtent: itemExtent, speed: speed),
|
(speed) => SpeedLine(speed: speed),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onSelectedItemChanged: (index) {
|
onSelectedItemChanged: (index) {
|
||||||
|
|
@ -236,11 +235,9 @@ class SpeedWheel extends StatelessWidget {
|
||||||
class SpeedLine extends StatelessWidget {
|
class SpeedLine extends StatelessWidget {
|
||||||
const SpeedLine({
|
const SpeedLine({
|
||||||
super.key,
|
super.key,
|
||||||
required this.itemExtent,
|
|
||||||
required this.speed,
|
required this.speed,
|
||||||
});
|
});
|
||||||
|
|
||||||
final double itemExtent;
|
|
||||||
final double speed;
|
final double speed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -250,7 +247,6 @@ class SpeedLine extends StatelessWidget {
|
||||||
// a vertical line
|
// a vertical line
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
// height: itemExtent,
|
|
||||||
// thick if multiple of 1, thin if multiple of 0.5 and transparent if multiple of 0.05
|
// thick if multiple of 1, thin if multiple of 0.5 and transparent if multiple of 0.05
|
||||||
width: speed % 0.5 == 0
|
width: speed % 0.5 == 0
|
||||||
? 3
|
? 3
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,17 @@ class SleepTimer {
|
||||||
|
|
||||||
set duration(Duration value) {
|
set duration(Duration value) {
|
||||||
_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
|
/// The player to be paused
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,16 @@ class SleepTimer extends _$SleepTimer {
|
||||||
return 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) {
|
if (state != null) {
|
||||||
state!.duration = resultingDuration;
|
state!.duration = resultingDuration;
|
||||||
ref.notifyListeners();
|
if (notifyListeners) {
|
||||||
|
ref.notifyListeners();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final timer = core.SleepTimer(
|
final timer = core.SleepTimer(
|
||||||
duration: resultingDuration,
|
duration: resultingDuration,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'sleep_timer_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$sleepTimerHash() => r'4f80bcc342e918c70c547b8b24790ccd88aba8c3';
|
String _$sleepTimerHash() => r'722bebfb7367acb84aacd98da66c5858b7e446cd';
|
||||||
|
|
||||||
/// See also [SleepTimer].
|
/// See also [SleepTimer].
|
||||||
@ProviderFor(SleepTimer)
|
@ProviderFor(SleepTimer)
|
||||||
|
|
|
||||||
366
lib/features/sleep_timer/view/sleep_timer_button.dart
Normal file
366
lib/features/sleep_timer/view/sleep_timer_button.dart
Normal file
|
|
@ -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: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<Duration?>(
|
||||||
|
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(
|
||||||
|
Icons.timer_rounded,
|
||||||
|
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(appSettingsProvider).playerSettings.sleepTimerSettings;
|
||||||
|
|
||||||
|
final durationOptions = sleepTimerSettings.presetDurations;
|
||||||
|
final minDuration = Duration.zero;
|
||||||
|
final maxDuration = <Duration>[
|
||||||
|
...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<Duration>(
|
||||||
|
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(Icons.cancel),
|
||||||
|
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<Duration> availableDurations;
|
||||||
|
final ValueNotifier<Duration?> 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,22 +81,17 @@ class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
|
||||||
_$MinimizedPlayerSettingsFromJson(json);
|
_$MinimizedPlayerSettingsFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SleepTimerShakeSenseMode { never, always, nearEnds }
|
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SleepTimerSettings with _$SleepTimerSettings {
|
class SleepTimerSettings with _$SleepTimerSettings {
|
||||||
const factory SleepTimerSettings({
|
const factory SleepTimerSettings({
|
||||||
@Default(Duration(minutes: 15)) Duration defaultDuration,
|
@Default(Duration(minutes: 15)) Duration defaultDuration,
|
||||||
@Default(SleepTimerShakeSenseMode.always)
|
@Default(
|
||||||
SleepTimerShakeSenseMode shakeSenseMode,
|
[Duration(minutes: 5), Duration(minutes: 15), Duration(minutes: 30)],
|
||||||
|
)
|
||||||
/// the duration in which the shake is detected before the end of the timer and after the timer ends
|
List<Duration> presetDurations,
|
||||||
/// only used if [shakeSenseMode] is [SleepTimerShakeSenseMode.nearEnds]
|
@Default(Duration(minutes: 100)) Duration maxDuration,
|
||||||
@Default(Duration(seconds: 30)) Duration shakeSenseDuration,
|
|
||||||
@Default(true) bool vibrateWhenReset,
|
|
||||||
@Default(false) bool beepWhenReset,
|
|
||||||
@Default(false) bool fadeOutAudio,
|
@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
|
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||||
@Default(false) bool autoRewindWhenStopped,
|
@Default(false) bool autoRewindWhenStopped,
|
||||||
|
|
|
||||||
|
|
@ -1374,16 +1374,10 @@ SleepTimerSettings _$SleepTimerSettingsFromJson(Map<String, dynamic> json) {
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SleepTimerSettings {
|
mixin _$SleepTimerSettings {
|
||||||
Duration get defaultDuration => throw _privateConstructorUsedError;
|
Duration get defaultDuration => throw _privateConstructorUsedError;
|
||||||
SleepTimerShakeSenseMode get shakeSenseMode =>
|
List<Duration> get presetDurations => throw _privateConstructorUsedError;
|
||||||
throw _privateConstructorUsedError;
|
Duration get maxDuration => 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;
|
|
||||||
bool get fadeOutAudio => 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
|
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||||
bool get autoRewindWhenStopped => throw _privateConstructorUsedError;
|
bool get autoRewindWhenStopped => throw _privateConstructorUsedError;
|
||||||
|
|
@ -1422,12 +1416,10 @@ abstract class $SleepTimerSettingsCopyWith<$Res> {
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{Duration defaultDuration,
|
{Duration defaultDuration,
|
||||||
SleepTimerShakeSenseMode shakeSenseMode,
|
List<Duration> presetDurations,
|
||||||
Duration shakeSenseDuration,
|
Duration maxDuration,
|
||||||
bool vibrateWhenReset,
|
|
||||||
bool beepWhenReset,
|
|
||||||
bool fadeOutAudio,
|
bool fadeOutAudio,
|
||||||
double shakeDetectThreshold,
|
Duration fadeOutDuration,
|
||||||
bool autoRewindWhenStopped,
|
bool autoRewindWhenStopped,
|
||||||
Map<int, Duration> autoRewindDurations,
|
Map<int, Duration> autoRewindDurations,
|
||||||
bool autoTurnOnTimer,
|
bool autoTurnOnTimer,
|
||||||
|
|
@ -1452,12 +1444,10 @@ class _$SleepTimerSettingsCopyWithImpl<$Res, $Val extends SleepTimerSettings>
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? defaultDuration = null,
|
Object? defaultDuration = null,
|
||||||
Object? shakeSenseMode = null,
|
Object? presetDurations = null,
|
||||||
Object? shakeSenseDuration = null,
|
Object? maxDuration = null,
|
||||||
Object? vibrateWhenReset = null,
|
|
||||||
Object? beepWhenReset = null,
|
|
||||||
Object? fadeOutAudio = null,
|
Object? fadeOutAudio = null,
|
||||||
Object? shakeDetectThreshold = null,
|
Object? fadeOutDuration = null,
|
||||||
Object? autoRewindWhenStopped = null,
|
Object? autoRewindWhenStopped = null,
|
||||||
Object? autoRewindDurations = null,
|
Object? autoRewindDurations = null,
|
||||||
Object? autoTurnOnTimer = null,
|
Object? autoTurnOnTimer = null,
|
||||||
|
|
@ -1470,30 +1460,22 @@ class _$SleepTimerSettingsCopyWithImpl<$Res, $Val extends SleepTimerSettings>
|
||||||
? _value.defaultDuration
|
? _value.defaultDuration
|
||||||
: defaultDuration // ignore: cast_nullable_to_non_nullable
|
: defaultDuration // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration,
|
as Duration,
|
||||||
shakeSenseMode: null == shakeSenseMode
|
presetDurations: null == presetDurations
|
||||||
? _value.shakeSenseMode
|
? _value.presetDurations
|
||||||
: shakeSenseMode // ignore: cast_nullable_to_non_nullable
|
: presetDurations // ignore: cast_nullable_to_non_nullable
|
||||||
as SleepTimerShakeSenseMode,
|
as List<Duration>,
|
||||||
shakeSenseDuration: null == shakeSenseDuration
|
maxDuration: null == maxDuration
|
||||||
? _value.shakeSenseDuration
|
? _value.maxDuration
|
||||||
: shakeSenseDuration // ignore: cast_nullable_to_non_nullable
|
: maxDuration // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration,
|
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
|
fadeOutAudio: null == fadeOutAudio
|
||||||
? _value.fadeOutAudio
|
? _value.fadeOutAudio
|
||||||
: fadeOutAudio // ignore: cast_nullable_to_non_nullable
|
: fadeOutAudio // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
shakeDetectThreshold: null == shakeDetectThreshold
|
fadeOutDuration: null == fadeOutDuration
|
||||||
? _value.shakeDetectThreshold
|
? _value.fadeOutDuration
|
||||||
: shakeDetectThreshold // ignore: cast_nullable_to_non_nullable
|
: fadeOutDuration // ignore: cast_nullable_to_non_nullable
|
||||||
as double,
|
as Duration,
|
||||||
autoRewindWhenStopped: null == autoRewindWhenStopped
|
autoRewindWhenStopped: null == autoRewindWhenStopped
|
||||||
? _value.autoRewindWhenStopped
|
? _value.autoRewindWhenStopped
|
||||||
: autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable
|
: autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable
|
||||||
|
|
@ -1532,12 +1514,10 @@ abstract class _$$SleepTimerSettingsImplCopyWith<$Res>
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{Duration defaultDuration,
|
{Duration defaultDuration,
|
||||||
SleepTimerShakeSenseMode shakeSenseMode,
|
List<Duration> presetDurations,
|
||||||
Duration shakeSenseDuration,
|
Duration maxDuration,
|
||||||
bool vibrateWhenReset,
|
|
||||||
bool beepWhenReset,
|
|
||||||
bool fadeOutAudio,
|
bool fadeOutAudio,
|
||||||
double shakeDetectThreshold,
|
Duration fadeOutDuration,
|
||||||
bool autoRewindWhenStopped,
|
bool autoRewindWhenStopped,
|
||||||
Map<int, Duration> autoRewindDurations,
|
Map<int, Duration> autoRewindDurations,
|
||||||
bool autoTurnOnTimer,
|
bool autoTurnOnTimer,
|
||||||
|
|
@ -1560,12 +1540,10 @@ class __$$SleepTimerSettingsImplCopyWithImpl<$Res>
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? defaultDuration = null,
|
Object? defaultDuration = null,
|
||||||
Object? shakeSenseMode = null,
|
Object? presetDurations = null,
|
||||||
Object? shakeSenseDuration = null,
|
Object? maxDuration = null,
|
||||||
Object? vibrateWhenReset = null,
|
|
||||||
Object? beepWhenReset = null,
|
|
||||||
Object? fadeOutAudio = null,
|
Object? fadeOutAudio = null,
|
||||||
Object? shakeDetectThreshold = null,
|
Object? fadeOutDuration = null,
|
||||||
Object? autoRewindWhenStopped = null,
|
Object? autoRewindWhenStopped = null,
|
||||||
Object? autoRewindDurations = null,
|
Object? autoRewindDurations = null,
|
||||||
Object? autoTurnOnTimer = null,
|
Object? autoTurnOnTimer = null,
|
||||||
|
|
@ -1578,30 +1556,22 @@ class __$$SleepTimerSettingsImplCopyWithImpl<$Res>
|
||||||
? _value.defaultDuration
|
? _value.defaultDuration
|
||||||
: defaultDuration // ignore: cast_nullable_to_non_nullable
|
: defaultDuration // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration,
|
as Duration,
|
||||||
shakeSenseMode: null == shakeSenseMode
|
presetDurations: null == presetDurations
|
||||||
? _value.shakeSenseMode
|
? _value._presetDurations
|
||||||
: shakeSenseMode // ignore: cast_nullable_to_non_nullable
|
: presetDurations // ignore: cast_nullable_to_non_nullable
|
||||||
as SleepTimerShakeSenseMode,
|
as List<Duration>,
|
||||||
shakeSenseDuration: null == shakeSenseDuration
|
maxDuration: null == maxDuration
|
||||||
? _value.shakeSenseDuration
|
? _value.maxDuration
|
||||||
: shakeSenseDuration // ignore: cast_nullable_to_non_nullable
|
: maxDuration // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration,
|
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
|
fadeOutAudio: null == fadeOutAudio
|
||||||
? _value.fadeOutAudio
|
? _value.fadeOutAudio
|
||||||
: fadeOutAudio // ignore: cast_nullable_to_non_nullable
|
: fadeOutAudio // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
shakeDetectThreshold: null == shakeDetectThreshold
|
fadeOutDuration: null == fadeOutDuration
|
||||||
? _value.shakeDetectThreshold
|
? _value.fadeOutDuration
|
||||||
: shakeDetectThreshold // ignore: cast_nullable_to_non_nullable
|
: fadeOutDuration // ignore: cast_nullable_to_non_nullable
|
||||||
as double,
|
as Duration,
|
||||||
autoRewindWhenStopped: null == autoRewindWhenStopped
|
autoRewindWhenStopped: null == autoRewindWhenStopped
|
||||||
? _value.autoRewindWhenStopped
|
? _value.autoRewindWhenStopped
|
||||||
: autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable
|
: autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable
|
||||||
|
|
@ -1635,12 +1605,14 @@ class __$$SleepTimerSettingsImplCopyWithImpl<$Res>
|
||||||
class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
||||||
const _$SleepTimerSettingsImpl(
|
const _$SleepTimerSettingsImpl(
|
||||||
{this.defaultDuration = const Duration(minutes: 15),
|
{this.defaultDuration = const Duration(minutes: 15),
|
||||||
this.shakeSenseMode = SleepTimerShakeSenseMode.always,
|
final List<Duration> presetDurations = const [
|
||||||
this.shakeSenseDuration = const Duration(seconds: 30),
|
Duration(minutes: 5),
|
||||||
this.vibrateWhenReset = true,
|
Duration(minutes: 15),
|
||||||
this.beepWhenReset = false,
|
Duration(minutes: 30)
|
||||||
|
],
|
||||||
|
this.maxDuration = const Duration(minutes: 100),
|
||||||
this.fadeOutAudio = false,
|
this.fadeOutAudio = false,
|
||||||
this.shakeDetectThreshold = 0.5,
|
this.fadeOutDuration = const Duration(seconds: 20),
|
||||||
this.autoRewindWhenStopped = false,
|
this.autoRewindWhenStopped = false,
|
||||||
final Map<int, Duration> autoRewindDurations = const {
|
final Map<int, Duration> autoRewindDurations = const {
|
||||||
5: Duration(seconds: 10),
|
5: Duration(seconds: 10),
|
||||||
|
|
@ -1653,7 +1625,8 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
||||||
this.alwaysAutoTurnOnTimer = true,
|
this.alwaysAutoTurnOnTimer = true,
|
||||||
this.autoTurnOnTime = const Duration(hours: 22, minutes: 0),
|
this.autoTurnOnTime = const Duration(hours: 22, minutes: 0),
|
||||||
this.autoTurnOffTime = const Duration(hours: 6, minutes: 0)})
|
this.autoTurnOffTime = const Duration(hours: 6, minutes: 0)})
|
||||||
: _autoRewindDurations = autoRewindDurations;
|
: _presetDurations = presetDurations,
|
||||||
|
_autoRewindDurations = autoRewindDurations;
|
||||||
|
|
||||||
factory _$SleepTimerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SleepTimerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$SleepTimerSettingsImplFromJson(json);
|
_$$SleepTimerSettingsImplFromJson(json);
|
||||||
|
|
@ -1661,27 +1634,24 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final Duration defaultDuration;
|
final Duration defaultDuration;
|
||||||
|
final List<Duration> _presetDurations;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final SleepTimerShakeSenseMode shakeSenseMode;
|
List<Duration> 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
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final Duration shakeSenseDuration;
|
final Duration maxDuration;
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool vibrateWhenReset;
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool beepWhenReset;
|
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool fadeOutAudio;
|
final bool fadeOutAudio;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final double shakeDetectThreshold;
|
final Duration fadeOutDuration;
|
||||||
|
|
||||||
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||||
@override
|
@override
|
||||||
|
|
@ -1723,7 +1693,7 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
|
|
@ -1733,18 +1703,14 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
||||||
other is _$SleepTimerSettingsImpl &&
|
other is _$SleepTimerSettingsImpl &&
|
||||||
(identical(other.defaultDuration, defaultDuration) ||
|
(identical(other.defaultDuration, defaultDuration) ||
|
||||||
other.defaultDuration == defaultDuration) &&
|
other.defaultDuration == defaultDuration) &&
|
||||||
(identical(other.shakeSenseMode, shakeSenseMode) ||
|
const DeepCollectionEquality()
|
||||||
other.shakeSenseMode == shakeSenseMode) &&
|
.equals(other._presetDurations, _presetDurations) &&
|
||||||
(identical(other.shakeSenseDuration, shakeSenseDuration) ||
|
(identical(other.maxDuration, maxDuration) ||
|
||||||
other.shakeSenseDuration == shakeSenseDuration) &&
|
other.maxDuration == maxDuration) &&
|
||||||
(identical(other.vibrateWhenReset, vibrateWhenReset) ||
|
|
||||||
other.vibrateWhenReset == vibrateWhenReset) &&
|
|
||||||
(identical(other.beepWhenReset, beepWhenReset) ||
|
|
||||||
other.beepWhenReset == beepWhenReset) &&
|
|
||||||
(identical(other.fadeOutAudio, fadeOutAudio) ||
|
(identical(other.fadeOutAudio, fadeOutAudio) ||
|
||||||
other.fadeOutAudio == fadeOutAudio) &&
|
other.fadeOutAudio == fadeOutAudio) &&
|
||||||
(identical(other.shakeDetectThreshold, shakeDetectThreshold) ||
|
(identical(other.fadeOutDuration, fadeOutDuration) ||
|
||||||
other.shakeDetectThreshold == shakeDetectThreshold) &&
|
other.fadeOutDuration == fadeOutDuration) &&
|
||||||
(identical(other.autoRewindWhenStopped, autoRewindWhenStopped) ||
|
(identical(other.autoRewindWhenStopped, autoRewindWhenStopped) ||
|
||||||
other.autoRewindWhenStopped == autoRewindWhenStopped) &&
|
other.autoRewindWhenStopped == autoRewindWhenStopped) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
|
|
@ -1764,12 +1730,10 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(
|
||||||
runtimeType,
|
runtimeType,
|
||||||
defaultDuration,
|
defaultDuration,
|
||||||
shakeSenseMode,
|
const DeepCollectionEquality().hash(_presetDurations),
|
||||||
shakeSenseDuration,
|
maxDuration,
|
||||||
vibrateWhenReset,
|
|
||||||
beepWhenReset,
|
|
||||||
fadeOutAudio,
|
fadeOutAudio,
|
||||||
shakeDetectThreshold,
|
fadeOutDuration,
|
||||||
autoRewindWhenStopped,
|
autoRewindWhenStopped,
|
||||||
const DeepCollectionEquality().hash(_autoRewindDurations),
|
const DeepCollectionEquality().hash(_autoRewindDurations),
|
||||||
autoTurnOnTimer,
|
autoTurnOnTimer,
|
||||||
|
|
@ -1797,12 +1761,10 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
||||||
abstract class _SleepTimerSettings implements SleepTimerSettings {
|
abstract class _SleepTimerSettings implements SleepTimerSettings {
|
||||||
const factory _SleepTimerSettings(
|
const factory _SleepTimerSettings(
|
||||||
{final Duration defaultDuration,
|
{final Duration defaultDuration,
|
||||||
final SleepTimerShakeSenseMode shakeSenseMode,
|
final List<Duration> presetDurations,
|
||||||
final Duration shakeSenseDuration,
|
final Duration maxDuration,
|
||||||
final bool vibrateWhenReset,
|
|
||||||
final bool beepWhenReset,
|
|
||||||
final bool fadeOutAudio,
|
final bool fadeOutAudio,
|
||||||
final double shakeDetectThreshold,
|
final Duration fadeOutDuration,
|
||||||
final bool autoRewindWhenStopped,
|
final bool autoRewindWhenStopped,
|
||||||
final Map<int, Duration> autoRewindDurations,
|
final Map<int, Duration> autoRewindDurations,
|
||||||
final bool autoTurnOnTimer,
|
final bool autoTurnOnTimer,
|
||||||
|
|
@ -1816,20 +1778,13 @@ abstract class _SleepTimerSettings implements SleepTimerSettings {
|
||||||
@override
|
@override
|
||||||
Duration get defaultDuration;
|
Duration get defaultDuration;
|
||||||
@override
|
@override
|
||||||
SleepTimerShakeSenseMode get shakeSenseMode;
|
List<Duration> get 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
|
@override
|
||||||
Duration get shakeSenseDuration;
|
Duration get maxDuration;
|
||||||
@override
|
|
||||||
bool get vibrateWhenReset;
|
|
||||||
@override
|
|
||||||
bool get beepWhenReset;
|
|
||||||
@override
|
@override
|
||||||
bool get fadeOutAudio;
|
bool get fadeOutAudio;
|
||||||
@override
|
@override
|
||||||
double get shakeDetectThreshold;
|
Duration get fadeOutDuration;
|
||||||
|
|
||||||
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -150,17 +150,21 @@ _$SleepTimerSettingsImpl _$$SleepTimerSettingsImplFromJson(
|
||||||
defaultDuration: json['defaultDuration'] == null
|
defaultDuration: json['defaultDuration'] == null
|
||||||
? const Duration(minutes: 15)
|
? const Duration(minutes: 15)
|
||||||
: Duration(microseconds: (json['defaultDuration'] as num).toInt()),
|
: Duration(microseconds: (json['defaultDuration'] as num).toInt()),
|
||||||
shakeSenseMode: $enumDecodeNullable(
|
presetDurations: (json['presetDurations'] as List<dynamic>?)
|
||||||
_$SleepTimerShakeSenseModeEnumMap, json['shakeSenseMode']) ??
|
?.map((e) => Duration(microseconds: (e as num).toInt()))
|
||||||
SleepTimerShakeSenseMode.always,
|
.toList() ??
|
||||||
shakeSenseDuration: json['shakeSenseDuration'] == null
|
const [
|
||||||
? const Duration(seconds: 30)
|
Duration(minutes: 5),
|
||||||
: Duration(microseconds: (json['shakeSenseDuration'] as num).toInt()),
|
Duration(minutes: 15),
|
||||||
vibrateWhenReset: json['vibrateWhenReset'] as bool? ?? true,
|
Duration(minutes: 30)
|
||||||
beepWhenReset: json['beepWhenReset'] as bool? ?? false,
|
],
|
||||||
|
maxDuration: json['maxDuration'] == null
|
||||||
|
? const Duration(minutes: 100)
|
||||||
|
: Duration(microseconds: (json['maxDuration'] as num).toInt()),
|
||||||
fadeOutAudio: json['fadeOutAudio'] as bool? ?? false,
|
fadeOutAudio: json['fadeOutAudio'] as bool? ?? false,
|
||||||
shakeDetectThreshold:
|
fadeOutDuration: json['fadeOutDuration'] == null
|
||||||
(json['shakeDetectThreshold'] as num?)?.toDouble() ?? 0.5,
|
? const Duration(seconds: 20)
|
||||||
|
: Duration(microseconds: (json['fadeOutDuration'] as num).toInt()),
|
||||||
autoRewindWhenStopped: json['autoRewindWhenStopped'] as bool? ?? false,
|
autoRewindWhenStopped: json['autoRewindWhenStopped'] as bool? ?? false,
|
||||||
autoRewindDurations:
|
autoRewindDurations:
|
||||||
(json['autoRewindDurations'] as Map<String, dynamic>?)?.map(
|
(json['autoRewindDurations'] as Map<String, dynamic>?)?.map(
|
||||||
|
|
@ -188,13 +192,11 @@ Map<String, dynamic> _$$SleepTimerSettingsImplToJson(
|
||||||
_$SleepTimerSettingsImpl instance) =>
|
_$SleepTimerSettingsImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'defaultDuration': instance.defaultDuration.inMicroseconds,
|
'defaultDuration': instance.defaultDuration.inMicroseconds,
|
||||||
'shakeSenseMode':
|
'presetDurations':
|
||||||
_$SleepTimerShakeSenseModeEnumMap[instance.shakeSenseMode]!,
|
instance.presetDurations.map((e) => e.inMicroseconds).toList(),
|
||||||
'shakeSenseDuration': instance.shakeSenseDuration.inMicroseconds,
|
'maxDuration': instance.maxDuration.inMicroseconds,
|
||||||
'vibrateWhenReset': instance.vibrateWhenReset,
|
|
||||||
'beepWhenReset': instance.beepWhenReset,
|
|
||||||
'fadeOutAudio': instance.fadeOutAudio,
|
'fadeOutAudio': instance.fadeOutAudio,
|
||||||
'shakeDetectThreshold': instance.shakeDetectThreshold,
|
'fadeOutDuration': instance.fadeOutDuration.inMicroseconds,
|
||||||
'autoRewindWhenStopped': instance.autoRewindWhenStopped,
|
'autoRewindWhenStopped': instance.autoRewindWhenStopped,
|
||||||
'autoRewindDurations': instance.autoRewindDurations
|
'autoRewindDurations': instance.autoRewindDurations
|
||||||
.map((k, e) => MapEntry(k.toString(), e.inMicroseconds)),
|
.map((k, e) => MapEntry(k.toString(), e.inMicroseconds)),
|
||||||
|
|
@ -204,12 +206,6 @@ Map<String, dynamic> _$$SleepTimerSettingsImplToJson(
|
||||||
'autoTurnOffTime': instance.autoTurnOffTime.inMicroseconds,
|
'autoTurnOffTime': instance.autoTurnOffTime.inMicroseconds,
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$SleepTimerShakeSenseModeEnumMap = {
|
|
||||||
SleepTimerShakeSenseMode.never: 'never',
|
|
||||||
SleepTimerShakeSenseMode.always: 'always',
|
|
||||||
SleepTimerShakeSenseMode.nearEnds: 'nearEnds',
|
|
||||||
};
|
|
||||||
|
|
||||||
_$DownloadSettingsImpl _$$DownloadSettingsImplFromJson(
|
_$DownloadSettingsImpl _$$DownloadSettingsImplFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
_$DownloadSettingsImpl(
|
_$DownloadSettingsImpl(
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,27 @@ extension DurationFormat on Duration {
|
||||||
final minutes = inMinutes.remainder(60);
|
final minutes = inMinutes.remainder(60);
|
||||||
final seconds = inSeconds.remainder(60);
|
final seconds = inSeconds.remainder(60);
|
||||||
if (hours > 0) {
|
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) {
|
} else if (minutes > 0) {
|
||||||
return '${minutes}m ${seconds}s';
|
if (seconds == 0) {
|
||||||
|
return smartSingleFormat;
|
||||||
|
}
|
||||||
|
return '${Duration(minutes: minutes).smartSingleFormat} ${Duration(seconds: seconds).smartSingleFormat}';
|
||||||
} else {
|
} 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 {
|
String get smartSingleFormat {
|
||||||
if (inHours > 0) {
|
if (inHours > 0) {
|
||||||
return '${inHours}h';
|
return '${inHours}h';
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,64 @@ void useTimer(VoidCallback callback, Duration delay) {
|
||||||
[delay],
|
[delay],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates [FixedExtentScrollController] that will be disposed automatically.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// - [FixedExtentScrollController]
|
||||||
|
FixedExtentScrollController useFixedExtentScrollController({
|
||||||
|
String? debugLabel,
|
||||||
|
List<Object?>? 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<FixedExtentScrollController> {
|
||||||
|
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<FixedExtentScrollController, Hook<FixedExtentScrollController>>
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue