mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-10 04:59:29 +00:00
sleeptimer
This commit is contained in:
parent
d372a6b096
commit
b98188d7fb
17 changed files with 1262 additions and 363 deletions
|
|
@ -1,8 +1,9 @@
|
|||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:whispering_pages/api/api_provider.dart';
|
||||
import 'package:whispering_pages/features/player/core/audiobook_player.dart' as abp;
|
||||
import 'package:whispering_pages/features/player/core/audiobook_player.dart'
|
||||
as abp;
|
||||
|
||||
part 'audiobook_player_provider.g.dart';
|
||||
part 'audiobook_player.g.dart';
|
||||
|
||||
// @Riverpod(keepAlive: true)
|
||||
// abp.AudiobookPlayer audiobookPlayer(
|
||||
|
|
@ -19,12 +20,23 @@ part 'audiobook_player_provider.g.dart';
|
|||
const playerId = 'audiobook_player';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class AudiobookPlayer extends _$AudiobookPlayer {
|
||||
class SimpleAudiobookPlayer extends _$SimpleAudiobookPlayer {
|
||||
@override
|
||||
abp.AudiobookPlayer build() {
|
||||
final api = ref.watch(authenticatedApiProvider);
|
||||
final player =
|
||||
abp.AudiobookPlayer(api.token!, api.baseUrl);
|
||||
final player = abp.AudiobookPlayer(api.token!, api.baseUrl);
|
||||
|
||||
ref.onDispose(player.dispose);
|
||||
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class AudiobookPlayer extends _$AudiobookPlayer {
|
||||
@override
|
||||
abp.AudiobookPlayer build() {
|
||||
final player = ref.watch(simpleAudiobookPlayerProvider);
|
||||
|
||||
ref.onDispose(player.dispose);
|
||||
|
||||
|
|
@ -40,7 +52,7 @@ class AudiobookPlayer extends _$AudiobookPlayer {
|
|||
ref.notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setSpeed(double speed) async {
|
||||
Future<void> setSpeed(double speed) async {
|
||||
await state.setSpeed(speed);
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,24 @@ part of 'audiobook_player.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$audiobookPlayerHash() => r'a636d5e8e73dc6bbf7b3f47f83884bb3af3b9370';
|
||||
String _$simpleAudiobookPlayerHash() =>
|
||||
r'b65e6d779476a2c1fa38f617771bf997acb4f5b8';
|
||||
|
||||
/// See also [SimpleAudiobookPlayer].
|
||||
@ProviderFor(SimpleAudiobookPlayer)
|
||||
final simpleAudiobookPlayerProvider =
|
||||
NotifierProvider<SimpleAudiobookPlayer, abp.AudiobookPlayer>.internal(
|
||||
SimpleAudiobookPlayer.new,
|
||||
name: r'simpleAudiobookPlayerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$simpleAudiobookPlayerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SimpleAudiobookPlayer = Notifier<abp.AudiobookPlayer>;
|
||||
String _$audiobookPlayerHash() => r'38042d0c93034e6907677fdb614a9af1b9d636af';
|
||||
|
||||
/// See also [AudiobookPlayer].
|
||||
@ProviderFor(AudiobookPlayer)
|
||||
|
|
@ -1,15 +1,19 @@
|
|||
import 'dart:async';
|
||||
|
||||
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:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:whispering_pages/constants/sizes.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart';
|
||||
import 'package:whispering_pages/features/player/view/audiobook_player.dart';
|
||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||
import 'package:whispering_pages/features/sleep_timer/core/sleep_timer.dart';
|
||||
import 'package:whispering_pages/features/sleep_timer/providers/sleep_timer_provider.dart';
|
||||
import 'package:whispering_pages/shared/extensions/inverse_lerp.dart';
|
||||
|
||||
import 'widgets/audiobook_player_seek_button.dart';
|
||||
import 'widgets/audiobook_player_seek_chapter_button.dart';
|
||||
import 'widgets/player_speed_adjust_button.dart';
|
||||
|
||||
class PlayerWhenExpanded extends HookConsumerWidget {
|
||||
const PlayerWhenExpanded({
|
||||
super.key,
|
||||
|
|
@ -127,7 +131,7 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
|||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.onSurface
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
maxLines: 1,
|
||||
|
|
@ -193,10 +197,7 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
|||
// speed control
|
||||
const PlayerSpeedAdjustButton(),
|
||||
// sleep timer
|
||||
IconButton(
|
||||
icon: const Icon(Icons.timer),
|
||||
onPressed: () {},
|
||||
),
|
||||
const SleepTimerButton(),
|
||||
// chapter list
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu_book_rounded),
|
||||
|
|
@ -216,334 +217,85 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class PlayerSpeedAdjustButton extends HookConsumerWidget {
|
||||
const PlayerSpeedAdjustButton({
|
||||
class SleepTimerButton extends HookConsumerWidget {
|
||||
const SleepTimerButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
final notifier = ref.watch(audiobookPlayerProvider.notifier);
|
||||
return TextButton(
|
||||
child: Text('${player.speed}x'),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
barrierLabel: 'Select Speed',
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 225,
|
||||
),
|
||||
builder: (context) {
|
||||
return SpeedSelector(
|
||||
onSpeedSelected: (speed) {
|
||||
notifier.setSpeed(speed);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
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 sleepTimer == null
|
||||
? IconButton(
|
||||
color: sleepTimer != null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
icon: const Icon(Icons.timer_rounded),
|
||||
onPressed: () {},
|
||||
)
|
||||
: RemainingSleepTimeDisplay(
|
||||
timer: sleepTimer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpeedSelector extends HookConsumerWidget {
|
||||
const SpeedSelector({
|
||||
class RemainingSleepTimeDisplay extends HookConsumerWidget {
|
||||
const RemainingSleepTimeDisplay({
|
||||
super.key,
|
||||
required this.onSpeedSelected,
|
||||
required this.timer,
|
||||
});
|
||||
|
||||
final void Function(double speed) onSpeedSelected;
|
||||
final SleepTimer timer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final appSettings = ref.watch(appSettingsProvider);
|
||||
final speeds = appSettings.playerSettings.speedOptions;
|
||||
final currentSpeed = ref.watch(audiobookPlayerProvider).speed;
|
||||
final speedState = useState(currentSpeed);
|
||||
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.formatSingleLargestUnit()
|
||||
: remainingTime?.formatSingleLargestUnit() ?? '',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension DurationFormat on Duration {
|
||||
/// will return a number followed by h, m, or s depending on the duration
|
||||
/// only the largest unit will be shown
|
||||
String formatSingleLargestUnit() {
|
||||
if (inHours > 0) {
|
||||
return '${inHours}h';
|
||||
} else if (inMinutes > 0) {
|
||||
return '${inMinutes}m';
|
||||
} else {
|
||||
return '${inSeconds}s';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void useInterval(VoidCallback callback, Duration delay) {
|
||||
final savedCallback = useRef(callback);
|
||||
savedCallback.value = callback;
|
||||
|
||||
// hook the onSpeedSelected function to the state
|
||||
useEffect(
|
||||
() {
|
||||
onSpeedSelected(speedState.value);
|
||||
return null;
|
||||
final timer = Timer.periodic(delay, (_) => savedCallback.value());
|
||||
return timer.cancel;
|
||||
},
|
||||
[speedState.value],
|
||||
[delay],
|
||||
);
|
||||
|
||||
// the speed options
|
||||
const minSpeed = 0.1;
|
||||
const maxSpeed = 4.0;
|
||||
const speedIncrement = 0.05;
|
||||
final availableSpeeds = ((maxSpeed - minSpeed) / speedIncrement).ceil();
|
||||
final availableSpeedsList = List.generate(
|
||||
availableSpeeds,
|
||||
(index) {
|
||||
// need to round to 2 decimal place to avoid floating point errors
|
||||
return double.parse(
|
||||
(minSpeed + index * speedIncrement).toStringAsFixed(2),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final scrollController = FixedExtentScrollController(
|
||||
initialItem: availableSpeedsList.indexOf(currentSpeed),
|
||||
);
|
||||
const double itemExtent = 25;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Playback Speed: ${speedState.value}x',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// a minus button to decrease the speed
|
||||
IconButton.filledTonal(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: () {
|
||||
// animate to index - 1
|
||||
final index = availableSpeedsList.indexOf(speedState.value);
|
||||
if (index > 0) {
|
||||
scrollController.animateToItem(
|
||||
index - 1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: ListWheelScrollViewX(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemExtent: itemExtent,
|
||||
diameterRatio: 1.5, squeeze: 1.2,
|
||||
// useMagnifier: true,
|
||||
// magnification: 1.5,
|
||||
physics: const FixedExtentScrollPhysics(),
|
||||
children: availableSpeedsList
|
||||
.map(
|
||||
(speed) => Column(
|
||||
children: [
|
||||
// a vertical line
|
||||
Container(
|
||||
height: itemExtent * 2,
|
||||
// thick if multiple of 1, thin if multiple of 0.5 and transparent if multiple of 0.05
|
||||
width: speed % 0.5 == 0
|
||||
? 3
|
||||
: speed % 0.25 == 0
|
||||
? 2
|
||||
: 0.5,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
// the speed text but only at .5 increments of speed
|
||||
if (speed % 0.25 == 0)
|
||||
Text(
|
||||
speed.toString(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onSelectedItemChanged: (index) {
|
||||
speedState.value = availableSpeedsList[index];
|
||||
// onSpeedSelected(availableSpeedsList[index]);
|
||||
// call after 500ms to avoid the scrollview from scrolling to the selected speed
|
||||
// Future.delayed(
|
||||
// const Duration(milliseconds: 100),
|
||||
// () => onSpeedSelected(availableSpeedsList[index]),
|
||||
// );
|
||||
},
|
||||
),
|
||||
),
|
||||
// a plus button to increase the speed
|
||||
IconButton.filledTonal(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
// animate to index + 1
|
||||
final index = availableSpeedsList.indexOf(speedState.value);
|
||||
if (index < availableSpeedsList.length - 1) {
|
||||
scrollController.animateToItem(
|
||||
index + 1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: speeds
|
||||
.map(
|
||||
(speed) => Flexible(
|
||||
// the text button should be highlighted if the speed is selected
|
||||
child: TextButton(
|
||||
style: speed == speedState.value
|
||||
? TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer,
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
)
|
||||
: null,
|
||||
onPressed: () async {
|
||||
// animate the wheel to the selected speed
|
||||
var index = availableSpeedsList.indexOf(speed);
|
||||
// if the speed is not in the list
|
||||
if (index == -1) {
|
||||
// find the nearest speed
|
||||
final nearestSpeed = availableSpeedsList.firstWhere(
|
||||
(element) => element > speed,
|
||||
orElse: () => availableSpeedsList.last,
|
||||
);
|
||||
index = availableSpeedsList.indexOf(nearestSpeed);
|
||||
}
|
||||
await scrollController.animateToItem(
|
||||
index,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
|
||||
// call the onSpeedSelected function
|
||||
speedState.value = speed;
|
||||
},
|
||||
child: Text('$speed'),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AudiobookPlayerSeekButton extends HookConsumerWidget {
|
||||
const AudiobookPlayerSeekButton({
|
||||
super.key,
|
||||
required this.isForward,
|
||||
});
|
||||
|
||||
/// if true, the button seeks forward, else it seeks backwards
|
||||
final bool isForward;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
isForward ? Icons.forward_30 : Icons.replay_30,
|
||||
size: AppElementSizes.iconSizeSmall,
|
||||
),
|
||||
onPressed: () {
|
||||
if (isForward) {
|
||||
player.seek(player.positionInBook + const Duration(seconds: 30));
|
||||
} else {
|
||||
player.seek(player.positionInBook - const Duration(seconds: 30));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
|
||||
const AudiobookPlayerSeekChapterButton({
|
||||
super.key,
|
||||
required this.isForward,
|
||||
});
|
||||
|
||||
/// if true, the button seeks forward, else it seeks backwards
|
||||
final bool isForward;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
|
||||
// add a small offset so the display does not show the previous chapter for a split second
|
||||
const offset = Duration(milliseconds: 10);
|
||||
|
||||
/// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
||||
const doNotSeekBackIfLessThan = Duration(seconds: 5);
|
||||
|
||||
/// seek forward to the next chapter
|
||||
void seekForward() {
|
||||
final index = player.book!.chapters.indexOf(player.currentChapter!);
|
||||
if (index < player.book!.chapters.length - 1) {
|
||||
player.seek(
|
||||
player.book!.chapters[index + 1].start + offset,
|
||||
);
|
||||
} else {
|
||||
player.seek(player.currentChapter!.end);
|
||||
}
|
||||
}
|
||||
|
||||
/// seek backward to the previous chapter or the start of the current chapter
|
||||
void seekBackward() {
|
||||
final currentPlayingChapterIndex =
|
||||
player.book!.chapters.indexOf(player.currentChapter!);
|
||||
final chapterPosition =
|
||||
player.positionInBook - player.currentChapter!.start;
|
||||
BookChapter chapterToSeekTo;
|
||||
// if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||
if (chapterPosition < doNotSeekBackIfLessThan &&
|
||||
currentPlayingChapterIndex > 0) {
|
||||
chapterToSeekTo = player.book!.chapters[currentPlayingChapterIndex - 1];
|
||||
} else {
|
||||
chapterToSeekTo = player.currentChapter!;
|
||||
}
|
||||
player.seek(
|
||||
chapterToSeekTo.start + offset,
|
||||
);
|
||||
}
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
isForward ? Icons.skip_next : Icons.skip_previous,
|
||||
size: AppElementSizes.iconSizeSmall,
|
||||
),
|
||||
onPressed: () {
|
||||
if (player.book == null) {
|
||||
return;
|
||||
}
|
||||
// if chapter does not exist, go to the start or end of the book
|
||||
if (player.currentChapter == null) {
|
||||
player.seek(isForward ? player.book!.duration : Duration.zero);
|
||||
return;
|
||||
}
|
||||
if (isForward) {
|
||||
seekForward();
|
||||
} else {
|
||||
seekBackward();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:whispering_pages/constants/sizes.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
|
||||
class AudiobookPlayerSeekButton extends HookConsumerWidget {
|
||||
const AudiobookPlayerSeekButton({
|
||||
super.key,
|
||||
required this.isForward,
|
||||
});
|
||||
|
||||
/// if true, the button seeks forward, else it seeks backwards
|
||||
final bool isForward;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
isForward ? Icons.forward_30 : Icons.replay_30,
|
||||
size: AppElementSizes.iconSizeSmall,
|
||||
),
|
||||
onPressed: () {
|
||||
if (isForward) {
|
||||
player.seek(player.positionInBook + const Duration(seconds: 30));
|
||||
} else {
|
||||
player.seek(player.positionInBook - const Duration(seconds: 30));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:whispering_pages/constants/sizes.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
|
||||
class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
|
||||
const AudiobookPlayerSeekChapterButton({
|
||||
super.key,
|
||||
required this.isForward,
|
||||
});
|
||||
|
||||
/// if true, the button seeks forward, else it seeks backwards
|
||||
final bool isForward;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
|
||||
// add a small offset so the display does not show the previous chapter for a split second
|
||||
const offset = Duration(milliseconds: 10);
|
||||
|
||||
/// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
||||
const doNotSeekBackIfLessThan = Duration(seconds: 5);
|
||||
|
||||
/// seek forward to the next chapter
|
||||
void seekForward() {
|
||||
final index = player.book!.chapters.indexOf(player.currentChapter!);
|
||||
if (index < player.book!.chapters.length - 1) {
|
||||
player.seek(
|
||||
player.book!.chapters[index + 1].start + offset,
|
||||
);
|
||||
} else {
|
||||
player.seek(player.currentChapter!.end);
|
||||
}
|
||||
}
|
||||
|
||||
/// seek backward to the previous chapter or the start of the current chapter
|
||||
void seekBackward() {
|
||||
final currentPlayingChapterIndex =
|
||||
player.book!.chapters.indexOf(player.currentChapter!);
|
||||
final chapterPosition =
|
||||
player.positionInBook - player.currentChapter!.start;
|
||||
BookChapter chapterToSeekTo;
|
||||
// if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||
if (chapterPosition < doNotSeekBackIfLessThan &&
|
||||
currentPlayingChapterIndex > 0) {
|
||||
chapterToSeekTo = player.book!.chapters[currentPlayingChapterIndex - 1];
|
||||
} else {
|
||||
chapterToSeekTo = player.currentChapter!;
|
||||
}
|
||||
player.seek(
|
||||
chapterToSeekTo.start + offset,
|
||||
);
|
||||
}
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
isForward ? Icons.skip_next : Icons.skip_previous,
|
||||
size: AppElementSizes.iconSizeSmall,
|
||||
),
|
||||
onPressed: () {
|
||||
if (player.book == null) {
|
||||
return;
|
||||
}
|
||||
// if chapter does not exist, go to the start or end of the book
|
||||
if (player.currentChapter == null) {
|
||||
player.seek(isForward ? player.book!.duration : Duration.zero);
|
||||
return;
|
||||
}
|
||||
if (isForward) {
|
||||
seekForward();
|
||||
} else {
|
||||
seekBackward();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/player/view/widgets/speed_selector.dart';
|
||||
|
||||
class PlayerSpeedAdjustButton extends HookConsumerWidget {
|
||||
const PlayerSpeedAdjustButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
final notifier = ref.watch(audiobookPlayerProvider.notifier);
|
||||
return TextButton(
|
||||
child: Text('${player.speed}x'),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
barrierLabel: 'Select Speed',
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 225,
|
||||
),
|
||||
builder: (context) {
|
||||
return SpeedSelector(
|
||||
onSpeedSelected: (speed) {
|
||||
notifier.setSpeed(speed);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
205
lib/features/player/view/widgets/speed_selector.dart
Normal file
205
lib/features/player/view/widgets/speed_selector.dart
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
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:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||
|
||||
class SpeedSelector extends HookConsumerWidget {
|
||||
const SpeedSelector({
|
||||
super.key,
|
||||
required this.onSpeedSelected,
|
||||
});
|
||||
|
||||
final void Function(double speed) onSpeedSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final appSettings = ref.watch(appSettingsProvider);
|
||||
final speeds = appSettings.playerSettings.speedOptions;
|
||||
final currentSpeed = ref.watch(audiobookPlayerProvider).speed;
|
||||
final speedState = useState(currentSpeed);
|
||||
|
||||
// hook the onSpeedSelected function to the state
|
||||
useEffect(
|
||||
() {
|
||||
onSpeedSelected(speedState.value);
|
||||
return null;
|
||||
},
|
||||
[speedState.value],
|
||||
);
|
||||
|
||||
// the speed options
|
||||
const minSpeed = 0.1;
|
||||
const maxSpeed = 4.0;
|
||||
const speedIncrement = 0.05;
|
||||
final availableSpeeds = ((maxSpeed - minSpeed) / speedIncrement).ceil();
|
||||
final availableSpeedsList = List.generate(
|
||||
availableSpeeds,
|
||||
(index) {
|
||||
// need to round to 2 decimal place to avoid floating point errors
|
||||
return double.parse(
|
||||
(minSpeed + index * speedIncrement).toStringAsFixed(2),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final scrollController = FixedExtentScrollController(
|
||||
initialItem: availableSpeedsList.indexOf(currentSpeed),
|
||||
);
|
||||
const double itemExtent = 25;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Playback Speed: ${speedState.value}x',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// a minus button to decrease the speed
|
||||
IconButton.filledTonal(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: () {
|
||||
// animate to index - 1
|
||||
final index = availableSpeedsList.indexOf(speedState.value);
|
||||
if (index > 0) {
|
||||
scrollController.animateToItem(
|
||||
index - 1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: ListWheelScrollViewX(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemExtent: itemExtent,
|
||||
diameterRatio: 1.5, squeeze: 1.2,
|
||||
// useMagnifier: true,
|
||||
// magnification: 1.5,
|
||||
physics: const FixedExtentScrollPhysics(),
|
||||
children: availableSpeedsList
|
||||
.map(
|
||||
(speed) => Column(
|
||||
children: [
|
||||
// a vertical line
|
||||
Container(
|
||||
height: itemExtent * 2,
|
||||
// thick if multiple of 1, thin if multiple of 0.5 and transparent if multiple of 0.05
|
||||
width: speed % 0.5 == 0
|
||||
? 3
|
||||
: speed % 0.25 == 0
|
||||
? 2
|
||||
: 0.5,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
// the speed text but only at .5 increments of speed
|
||||
if (speed % 0.25 == 0)
|
||||
Text(
|
||||
speed.toString(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onSelectedItemChanged: (index) {
|
||||
speedState.value = availableSpeedsList[index];
|
||||
// onSpeedSelected(availableSpeedsList[index]);
|
||||
// call after 500ms to avoid the scrollview from scrolling to the selected speed
|
||||
// Future.delayed(
|
||||
// const Duration(milliseconds: 100),
|
||||
// () => onSpeedSelected(availableSpeedsList[index]),
|
||||
// );
|
||||
},
|
||||
),
|
||||
),
|
||||
// a plus button to increase the speed
|
||||
IconButton.filledTonal(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
// animate to index + 1
|
||||
final index = availableSpeedsList.indexOf(speedState.value);
|
||||
if (index < availableSpeedsList.length - 1) {
|
||||
scrollController.animateToItem(
|
||||
index + 1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: speeds
|
||||
.map(
|
||||
(speed) => Flexible(
|
||||
// the text button should be highlighted if the speed is selected
|
||||
child: TextButton(
|
||||
style: speed == speedState.value
|
||||
? TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer,
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
)
|
||||
: null,
|
||||
onPressed: () async {
|
||||
// animate the wheel to the selected speed
|
||||
var index = availableSpeedsList.indexOf(speed);
|
||||
// if the speed is not in the list
|
||||
if (index == -1) {
|
||||
// find the nearest speed
|
||||
final nearestSpeed = availableSpeedsList.firstWhere(
|
||||
(element) => element > speed,
|
||||
orElse: () => availableSpeedsList.last,
|
||||
);
|
||||
index = availableSpeedsList.indexOf(nearestSpeed);
|
||||
}
|
||||
await scrollController.animateToItem(
|
||||
index,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
|
||||
// call the onSpeedSelected function
|
||||
speedState.value = speed;
|
||||
},
|
||||
child: Text('$speed'),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
89
lib/features/sleep_timer/core/sleep_timer.dart
Normal file
89
lib/features/sleep_timer/core/sleep_timer.dart
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:whispering_pages/features/player/core/audiobook_player.dart';
|
||||
|
||||
/// this timer pauses the music player after a certain duration
|
||||
///
|
||||
/// watches the state of the music player and pauses it when the timer is up
|
||||
/// timer is cancelled when the music player is paused or stopped
|
||||
class SleepTimer {
|
||||
/// The duration after which the music player will be paused
|
||||
final Duration duration;
|
||||
|
||||
/// The player to be paused
|
||||
final AudiobookPlayer player;
|
||||
|
||||
/// The timer that will pause the player
|
||||
Timer? timer;
|
||||
|
||||
/// for internal use only
|
||||
/// when the timer was started
|
||||
DateTime? startedAt;
|
||||
|
||||
SleepTimer({required this.duration, required this.player}) {
|
||||
player.playbackEventStream.listen((event) {
|
||||
if (event.processingState == ProcessingState.completed ||
|
||||
event.processingState == ProcessingState.idle) {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
/// pause the player when the timer is up
|
||||
player.playerStateStream.listen((state) {
|
||||
if (state.playing && timer == null) {
|
||||
startTimer();
|
||||
} else if (!state.playing) {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
debugPrint('SleepTimer created with duration: $duration');
|
||||
}
|
||||
|
||||
/// resets the timer
|
||||
void reset() {
|
||||
if (timer != null) {
|
||||
timer!.cancel();
|
||||
debugPrint(
|
||||
'SleepTimer cancelled timer, remaining time: $remainingTime, duration: $duration',
|
||||
);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// starts the timer
|
||||
void startTimer() {
|
||||
reset();
|
||||
timer = Timer(duration, () {
|
||||
player.pause();
|
||||
reset();
|
||||
debugPrint('SleepTimer paused player after $duration');
|
||||
});
|
||||
startedAt = DateTime.now();
|
||||
debugPrint('SleepTimer started for $duration at $startedAt');
|
||||
}
|
||||
|
||||
Duration get remainingTime {
|
||||
if (timer == null) {
|
||||
return Duration.zero;
|
||||
}
|
||||
final elapsed = DateTime.now().difference(startedAt!);
|
||||
return duration - elapsed;
|
||||
}
|
||||
|
||||
/// a stream that emits the remaining time every second
|
||||
Stream<Duration> get remainingTimeStream async* {
|
||||
while (timer != null) {
|
||||
yield remainingTime;
|
||||
await Future.delayed(0.5.seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// dispose the timer
|
||||
void dispose() {
|
||||
reset();
|
||||
debugPrint('SleepTimer disposed');
|
||||
}
|
||||
}
|
||||
19
lib/features/sleep_timer/providers/sleep_timer_provider.dart
Normal file
19
lib/features/sleep_timer/providers/sleep_timer_provider.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/sleep_timer/core/sleep_timer.dart';
|
||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||
|
||||
part 'sleep_timer_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
SleepTimer? sleepTimer(SleepTimerRef ref) {
|
||||
final appSettings = ref.watch(appSettingsProvider);
|
||||
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
||||
var sleepTimer = SleepTimer(
|
||||
// duration: sleepTimerSettings.defaultDuration,
|
||||
duration: const Duration(seconds: 5),
|
||||
player: ref.watch(simpleAudiobookPlayerProvider),
|
||||
);
|
||||
ref.onDispose(sleepTimer.dispose);
|
||||
return sleepTimer;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sleep_timer_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$sleepTimerHash() => r'79646b12412f3300166db29328664a5e58e405bd';
|
||||
|
||||
/// See also [sleepTimer].
|
||||
@ProviderFor(sleepTimer)
|
||||
final sleepTimerProvider = Provider<SleepTimer?>.internal(
|
||||
sleepTimer,
|
||||
name: r'sleepTimerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$sleepTimerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef SleepTimerRef = ProviderRef<SleepTimer?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
@ -7,6 +7,8 @@ import 'package:just_audio_media_kit/just_audio_media_kit.dart'
|
|||
show JustAudioMediaKit;
|
||||
import 'package:whispering_pages/api/server_provider.dart';
|
||||
import 'package:whispering_pages/db/storage.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/sleep_timer/providers/sleep_timer_provider.dart';
|
||||
import 'package:whispering_pages/router/router.dart';
|
||||
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||
|
|
@ -56,7 +58,8 @@ class MyApp extends ConsumerWidget {
|
|||
routerConfig.goNamed(Routes.onboarding.name);
|
||||
}
|
||||
|
||||
return MaterialApp.router(
|
||||
return _EagerInitialization(
|
||||
child: MaterialApp.router(
|
||||
// debugShowCheckedModeBanner: false,
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
|
|
@ -64,6 +67,27 @@ class MyApp extends ConsumerWidget {
|
|||
? ThemeMode.dark
|
||||
: ThemeMode.light,
|
||||
routerConfig: routerConfig,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// https://riverpod.dev/docs/essentials/eager_initialization
|
||||
// Eagerly initialize providers by watching them.
|
||||
class _EagerInitialization extends ConsumerWidget {
|
||||
const _EagerInitialization({required this.child});
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Eagerly initialize providers by watching them.
|
||||
// By using "watch", the provider will stay alive and not be disposed.
|
||||
try {
|
||||
ref.watch(simpleAudiobookPlayerProvider);
|
||||
ref.watch(sleepTimerProvider);
|
||||
} catch (e) {
|
||||
debugPrintStack(stackTrace: StackTrace.current, label: e.toString());
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:whispering_pages/settings/models/app_settings.dart' as model;
|
||||
import 'package:whispering_pages/db/available_boxes.dart';
|
||||
import 'package:whispering_pages/settings/models/app_settings.dart' as model;
|
||||
|
||||
part 'app_settings_provider.g.dart';
|
||||
|
||||
final _box = AvailableHiveBoxes.userPrefsBox;
|
||||
|
||||
@riverpod
|
||||
@Riverpod(keepAlive: true)
|
||||
class AppSettings extends _$AppSettings {
|
||||
@override
|
||||
model.AppSettings build() {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ part of 'app_settings_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$appSettingsHash() => r'da2cd1bb0da6e136e906bc61f29da89d0c5f53fb';
|
||||
String _$appSettingsHash() => r'6716bc568850ffd373fd8572c5781beefafbb9ee';
|
||||
|
||||
/// See also [AppSettings].
|
||||
@ProviderFor(AppSettings)
|
||||
final appSettingsProvider =
|
||||
AutoDisposeNotifierProvider<AppSettings, model.AppSettings>.internal(
|
||||
NotifierProvider<AppSettings, model.AppSettings>.internal(
|
||||
AppSettings.new,
|
||||
name: r'appSettingsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
|
|
@ -20,6 +20,6 @@ final appSettingsProvider =
|
|||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AppSettings = AutoDisposeNotifier<model.AppSettings>;
|
||||
typedef _$AppSettings = Notifier<model.AppSettings>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class PlayerSettings with _$PlayerSettings {
|
|||
@Default(1) double preferredDefaultVolume,
|
||||
@Default(1) double preferredDefaultSpeed,
|
||||
@Default([0.75, 1, 1.25, 1.5, 1.75, 2]) List<double> speedOptions,
|
||||
@Default(Duration(minutes: 15)) Duration sleepTimer,
|
||||
@Default(SleepTimerSettings()) SleepTimerSettings sleepTimerSettings,
|
||||
}) = _PlayerSettings;
|
||||
|
||||
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||
|
|
@ -57,3 +57,50 @@ class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
|
|||
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$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(false) bool fadeOutAudio,
|
||||
@Default(0.5) double shakeDetectThreshold,
|
||||
|
||||
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||
@Default(false) bool autoRewindWhenStopped,
|
||||
|
||||
/// the key is the duration in minutes
|
||||
@Default({
|
||||
5: Duration(seconds: 10),
|
||||
15: Duration(seconds: 30),
|
||||
45: Duration(seconds: 45),
|
||||
60: Duration(minutes: 1),
|
||||
120: Duration(minutes: 2),
|
||||
})
|
||||
Map<int, Duration> autoRewindDurations,
|
||||
|
||||
/// auto turn on timer settings
|
||||
@Default(false) bool autoTurnOnTimer,
|
||||
|
||||
/// always auto turn on timer settings or during specific times
|
||||
@Default(true) bool alwaysAutoTurnOnTimer,
|
||||
|
||||
/// auto timer settings, only used if [alwaysAutoTurnOnTimer] is false
|
||||
///
|
||||
/// duration is the time from 00:00
|
||||
@Default(Duration(hours: 22, minutes: 0)) Duration autoTurnOnTime,
|
||||
@Default(Duration(hours: 6, minutes: 0)) Duration autoTurnOffTime,
|
||||
}) = _SleepTimerSettings;
|
||||
|
||||
factory SleepTimerSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$SleepTimerSettingsFromJson(json);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,7 +229,8 @@ mixin _$PlayerSettings {
|
|||
double get preferredDefaultVolume => throw _privateConstructorUsedError;
|
||||
double get preferredDefaultSpeed => throw _privateConstructorUsedError;
|
||||
List<double> get speedOptions => throw _privateConstructorUsedError;
|
||||
Duration get sleepTimer => throw _privateConstructorUsedError;
|
||||
SleepTimerSettings get sleepTimerSettings =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
|
@ -249,10 +250,11 @@ abstract class $PlayerSettingsCopyWith<$Res> {
|
|||
double preferredDefaultVolume,
|
||||
double preferredDefaultSpeed,
|
||||
List<double> speedOptions,
|
||||
Duration sleepTimer});
|
||||
SleepTimerSettings sleepTimerSettings});
|
||||
|
||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||
$SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -273,7 +275,7 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
Object? preferredDefaultVolume = null,
|
||||
Object? preferredDefaultSpeed = null,
|
||||
Object? speedOptions = null,
|
||||
Object? sleepTimer = null,
|
||||
Object? sleepTimerSettings = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
miniPlayerSettings: null == miniPlayerSettings
|
||||
|
|
@ -296,10 +298,10 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
? _value.speedOptions
|
||||
: speedOptions // ignore: cast_nullable_to_non_nullable
|
||||
as List<double>,
|
||||
sleepTimer: null == sleepTimer
|
||||
? _value.sleepTimer
|
||||
: sleepTimer // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
sleepTimerSettings: null == sleepTimerSettings
|
||||
? _value.sleepTimerSettings
|
||||
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as SleepTimerSettings,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
|
|
@ -320,6 +322,15 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
return _then(_value.copyWith(expandedPlayerSettings: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings {
|
||||
return $SleepTimerSettingsCopyWith<$Res>(_value.sleepTimerSettings,
|
||||
(value) {
|
||||
return _then(_value.copyWith(sleepTimerSettings: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -336,12 +347,14 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res>
|
|||
double preferredDefaultVolume,
|
||||
double preferredDefaultSpeed,
|
||||
List<double> speedOptions,
|
||||
Duration sleepTimer});
|
||||
SleepTimerSettings sleepTimerSettings});
|
||||
|
||||
@override
|
||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||
@override
|
||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||
@override
|
||||
$SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -360,7 +373,7 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
Object? preferredDefaultVolume = null,
|
||||
Object? preferredDefaultSpeed = null,
|
||||
Object? speedOptions = null,
|
||||
Object? sleepTimer = null,
|
||||
Object? sleepTimerSettings = null,
|
||||
}) {
|
||||
return _then(_$PlayerSettingsImpl(
|
||||
miniPlayerSettings: null == miniPlayerSettings
|
||||
|
|
@ -383,10 +396,10 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
? _value._speedOptions
|
||||
: speedOptions // ignore: cast_nullable_to_non_nullable
|
||||
as List<double>,
|
||||
sleepTimer: null == sleepTimer
|
||||
? _value.sleepTimer
|
||||
: sleepTimer // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
sleepTimerSettings: null == sleepTimerSettings
|
||||
? _value.sleepTimerSettings
|
||||
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as SleepTimerSettings,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -400,7 +413,7 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
this.preferredDefaultVolume = 1,
|
||||
this.preferredDefaultSpeed = 1,
|
||||
final List<double> speedOptions = const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
this.sleepTimer = const Duration(minutes: 15)})
|
||||
this.sleepTimerSettings = const SleepTimerSettings()})
|
||||
: _speedOptions = speedOptions;
|
||||
|
||||
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
|
|
@ -429,11 +442,11 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration sleepTimer;
|
||||
final SleepTimerSettings sleepTimerSettings;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimer: $sleepTimer)';
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -451,8 +464,8 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
other.preferredDefaultSpeed == preferredDefaultSpeed) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._speedOptions, _speedOptions) &&
|
||||
(identical(other.sleepTimer, sleepTimer) ||
|
||||
other.sleepTimer == sleepTimer));
|
||||
(identical(other.sleepTimerSettings, sleepTimerSettings) ||
|
||||
other.sleepTimerSettings == sleepTimerSettings));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
|
|
@ -464,7 +477,7 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
preferredDefaultVolume,
|
||||
preferredDefaultSpeed,
|
||||
const DeepCollectionEquality().hash(_speedOptions),
|
||||
sleepTimer);
|
||||
sleepTimerSettings);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
|
|
@ -488,7 +501,7 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
final double preferredDefaultVolume,
|
||||
final double preferredDefaultSpeed,
|
||||
final List<double> speedOptions,
|
||||
final Duration sleepTimer}) = _$PlayerSettingsImpl;
|
||||
final SleepTimerSettings sleepTimerSettings}) = _$PlayerSettingsImpl;
|
||||
|
||||
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||
_$PlayerSettingsImpl.fromJson;
|
||||
|
|
@ -504,7 +517,7 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
@override
|
||||
List<double> get speedOptions;
|
||||
@override
|
||||
Duration get sleepTimer;
|
||||
SleepTimerSettings get sleepTimerSettings;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||
|
|
@ -821,3 +834,486 @@ abstract class _MinimizedPlayerSettings implements MinimizedPlayerSettings {
|
|||
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
SleepTimerSettings _$SleepTimerSettingsFromJson(Map<String, dynamic> json) {
|
||||
return _SleepTimerSettings.fromJson(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;
|
||||
bool get fadeOutAudio => throw _privateConstructorUsedError;
|
||||
double get shakeDetectThreshold => throw _privateConstructorUsedError;
|
||||
|
||||
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||
bool get autoRewindWhenStopped => throw _privateConstructorUsedError;
|
||||
|
||||
/// the key is the duration in minutes
|
||||
Map<int, Duration> get autoRewindDurations =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// auto turn on timer settings
|
||||
bool get autoTurnOnTimer => throw _privateConstructorUsedError;
|
||||
|
||||
/// always auto turn on timer settings or during specific times
|
||||
bool get alwaysAutoTurnOnTimer => throw _privateConstructorUsedError;
|
||||
|
||||
/// auto timer settings, only used if [alwaysAutoTurnOnTimer] is false
|
||||
///
|
||||
/// duration is the time from 00:00
|
||||
Duration get autoTurnOnTime => throw _privateConstructorUsedError;
|
||||
Duration get autoTurnOffTime => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$SleepTimerSettingsCopyWith<SleepTimerSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SleepTimerSettingsCopyWith<$Res> {
|
||||
factory $SleepTimerSettingsCopyWith(
|
||||
SleepTimerSettings value, $Res Function(SleepTimerSettings) then) =
|
||||
_$SleepTimerSettingsCopyWithImpl<$Res, SleepTimerSettings>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Duration defaultDuration,
|
||||
SleepTimerShakeSenseMode shakeSenseMode,
|
||||
Duration shakeSenseDuration,
|
||||
bool vibrateWhenReset,
|
||||
bool beepWhenReset,
|
||||
bool fadeOutAudio,
|
||||
double shakeDetectThreshold,
|
||||
bool autoRewindWhenStopped,
|
||||
Map<int, Duration> autoRewindDurations,
|
||||
bool autoTurnOnTimer,
|
||||
bool alwaysAutoTurnOnTimer,
|
||||
Duration autoTurnOnTime,
|
||||
Duration autoTurnOffTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SleepTimerSettingsCopyWithImpl<$Res, $Val extends SleepTimerSettings>
|
||||
implements $SleepTimerSettingsCopyWith<$Res> {
|
||||
_$SleepTimerSettingsCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? defaultDuration = null,
|
||||
Object? shakeSenseMode = null,
|
||||
Object? shakeSenseDuration = null,
|
||||
Object? vibrateWhenReset = null,
|
||||
Object? beepWhenReset = null,
|
||||
Object? fadeOutAudio = null,
|
||||
Object? shakeDetectThreshold = null,
|
||||
Object? autoRewindWhenStopped = null,
|
||||
Object? autoRewindDurations = null,
|
||||
Object? autoTurnOnTimer = null,
|
||||
Object? alwaysAutoTurnOnTimer = null,
|
||||
Object? autoTurnOnTime = null,
|
||||
Object? autoTurnOffTime = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
defaultDuration: null == defaultDuration
|
||||
? _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
|
||||
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,
|
||||
autoRewindWhenStopped: null == autoRewindWhenStopped
|
||||
? _value.autoRewindWhenStopped
|
||||
: autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
autoRewindDurations: null == autoRewindDurations
|
||||
? _value.autoRewindDurations
|
||||
: autoRewindDurations // ignore: cast_nullable_to_non_nullable
|
||||
as Map<int, Duration>,
|
||||
autoTurnOnTimer: null == autoTurnOnTimer
|
||||
? _value.autoTurnOnTimer
|
||||
: autoTurnOnTimer // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
alwaysAutoTurnOnTimer: null == alwaysAutoTurnOnTimer
|
||||
? _value.alwaysAutoTurnOnTimer
|
||||
: alwaysAutoTurnOnTimer // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
autoTurnOnTime: null == autoTurnOnTime
|
||||
? _value.autoTurnOnTime
|
||||
: autoTurnOnTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
autoTurnOffTime: null == autoTurnOffTime
|
||||
? _value.autoTurnOffTime
|
||||
: autoTurnOffTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SleepTimerSettingsImplCopyWith<$Res>
|
||||
implements $SleepTimerSettingsCopyWith<$Res> {
|
||||
factory _$$SleepTimerSettingsImplCopyWith(_$SleepTimerSettingsImpl value,
|
||||
$Res Function(_$SleepTimerSettingsImpl) then) =
|
||||
__$$SleepTimerSettingsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{Duration defaultDuration,
|
||||
SleepTimerShakeSenseMode shakeSenseMode,
|
||||
Duration shakeSenseDuration,
|
||||
bool vibrateWhenReset,
|
||||
bool beepWhenReset,
|
||||
bool fadeOutAudio,
|
||||
double shakeDetectThreshold,
|
||||
bool autoRewindWhenStopped,
|
||||
Map<int, Duration> autoRewindDurations,
|
||||
bool autoTurnOnTimer,
|
||||
bool alwaysAutoTurnOnTimer,
|
||||
Duration autoTurnOnTime,
|
||||
Duration autoTurnOffTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SleepTimerSettingsImplCopyWithImpl<$Res>
|
||||
extends _$SleepTimerSettingsCopyWithImpl<$Res, _$SleepTimerSettingsImpl>
|
||||
implements _$$SleepTimerSettingsImplCopyWith<$Res> {
|
||||
__$$SleepTimerSettingsImplCopyWithImpl(_$SleepTimerSettingsImpl _value,
|
||||
$Res Function(_$SleepTimerSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? defaultDuration = null,
|
||||
Object? shakeSenseMode = null,
|
||||
Object? shakeSenseDuration = null,
|
||||
Object? vibrateWhenReset = null,
|
||||
Object? beepWhenReset = null,
|
||||
Object? fadeOutAudio = null,
|
||||
Object? shakeDetectThreshold = null,
|
||||
Object? autoRewindWhenStopped = null,
|
||||
Object? autoRewindDurations = null,
|
||||
Object? autoTurnOnTimer = null,
|
||||
Object? alwaysAutoTurnOnTimer = null,
|
||||
Object? autoTurnOnTime = null,
|
||||
Object? autoTurnOffTime = null,
|
||||
}) {
|
||||
return _then(_$SleepTimerSettingsImpl(
|
||||
defaultDuration: null == defaultDuration
|
||||
? _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
|
||||
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,
|
||||
autoRewindWhenStopped: null == autoRewindWhenStopped
|
||||
? _value.autoRewindWhenStopped
|
||||
: autoRewindWhenStopped // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
autoRewindDurations: null == autoRewindDurations
|
||||
? _value._autoRewindDurations
|
||||
: autoRewindDurations // ignore: cast_nullable_to_non_nullable
|
||||
as Map<int, Duration>,
|
||||
autoTurnOnTimer: null == autoTurnOnTimer
|
||||
? _value.autoTurnOnTimer
|
||||
: autoTurnOnTimer // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
alwaysAutoTurnOnTimer: null == alwaysAutoTurnOnTimer
|
||||
? _value.alwaysAutoTurnOnTimer
|
||||
: alwaysAutoTurnOnTimer // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
autoTurnOnTime: null == autoTurnOnTime
|
||||
? _value.autoTurnOnTime
|
||||
: autoTurnOnTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
autoTurnOffTime: null == autoTurnOffTime
|
||||
? _value.autoTurnOffTime
|
||||
: autoTurnOffTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
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,
|
||||
this.fadeOutAudio = false,
|
||||
this.shakeDetectThreshold = 0.5,
|
||||
this.autoRewindWhenStopped = false,
|
||||
final Map<int, Duration> autoRewindDurations = const {
|
||||
5: Duration(seconds: 10),
|
||||
15: Duration(seconds: 30),
|
||||
45: Duration(seconds: 45),
|
||||
60: Duration(minutes: 1),
|
||||
120: Duration(minutes: 2)
|
||||
},
|
||||
this.autoTurnOnTimer = false,
|
||||
this.alwaysAutoTurnOnTimer = true,
|
||||
this.autoTurnOnTime = const Duration(hours: 22, minutes: 0),
|
||||
this.autoTurnOffTime = const Duration(hours: 6, minutes: 0)})
|
||||
: _autoRewindDurations = autoRewindDurations;
|
||||
|
||||
factory _$SleepTimerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SleepTimerSettingsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration defaultDuration;
|
||||
@override
|
||||
@JsonKey()
|
||||
final 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]
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration shakeSenseDuration;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool vibrateWhenReset;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool beepWhenReset;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool fadeOutAudio;
|
||||
@override
|
||||
@JsonKey()
|
||||
final double shakeDetectThreshold;
|
||||
|
||||
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool autoRewindWhenStopped;
|
||||
|
||||
/// the key is the duration in minutes
|
||||
final Map<int, Duration> _autoRewindDurations;
|
||||
|
||||
/// the key is the duration in minutes
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<int, Duration> get autoRewindDurations {
|
||||
if (_autoRewindDurations is EqualUnmodifiableMapView)
|
||||
return _autoRewindDurations;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_autoRewindDurations);
|
||||
}
|
||||
|
||||
/// auto turn on timer settings
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool autoTurnOnTimer;
|
||||
|
||||
/// always auto turn on timer settings or during specific times
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool alwaysAutoTurnOnTimer;
|
||||
|
||||
/// auto timer settings, only used if [alwaysAutoTurnOnTimer] is false
|
||||
///
|
||||
/// duration is the time from 00:00
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration autoTurnOnTime;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration autoTurnOffTime;
|
||||
|
||||
@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)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
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) &&
|
||||
(identical(other.fadeOutAudio, fadeOutAudio) ||
|
||||
other.fadeOutAudio == fadeOutAudio) &&
|
||||
(identical(other.shakeDetectThreshold, shakeDetectThreshold) ||
|
||||
other.shakeDetectThreshold == shakeDetectThreshold) &&
|
||||
(identical(other.autoRewindWhenStopped, autoRewindWhenStopped) ||
|
||||
other.autoRewindWhenStopped == autoRewindWhenStopped) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._autoRewindDurations, _autoRewindDurations) &&
|
||||
(identical(other.autoTurnOnTimer, autoTurnOnTimer) ||
|
||||
other.autoTurnOnTimer == autoTurnOnTimer) &&
|
||||
(identical(other.alwaysAutoTurnOnTimer, alwaysAutoTurnOnTimer) ||
|
||||
other.alwaysAutoTurnOnTimer == alwaysAutoTurnOnTimer) &&
|
||||
(identical(other.autoTurnOnTime, autoTurnOnTime) ||
|
||||
other.autoTurnOnTime == autoTurnOnTime) &&
|
||||
(identical(other.autoTurnOffTime, autoTurnOffTime) ||
|
||||
other.autoTurnOffTime == autoTurnOffTime));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
defaultDuration,
|
||||
shakeSenseMode,
|
||||
shakeSenseDuration,
|
||||
vibrateWhenReset,
|
||||
beepWhenReset,
|
||||
fadeOutAudio,
|
||||
shakeDetectThreshold,
|
||||
autoRewindWhenStopped,
|
||||
const DeepCollectionEquality().hash(_autoRewindDurations),
|
||||
autoTurnOnTimer,
|
||||
alwaysAutoTurnOnTimer,
|
||||
autoTurnOnTime,
|
||||
autoTurnOffTime);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SleepTimerSettingsImplCopyWith<_$SleepTimerSettingsImpl> get copyWith =>
|
||||
__$$SleepTimerSettingsImplCopyWithImpl<_$SleepTimerSettingsImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$SleepTimerSettingsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SleepTimerSettings implements SleepTimerSettings {
|
||||
const factory _SleepTimerSettings(
|
||||
{final Duration defaultDuration,
|
||||
final SleepTimerShakeSenseMode shakeSenseMode,
|
||||
final Duration shakeSenseDuration,
|
||||
final bool vibrateWhenReset,
|
||||
final bool beepWhenReset,
|
||||
final bool fadeOutAudio,
|
||||
final double shakeDetectThreshold,
|
||||
final bool autoRewindWhenStopped,
|
||||
final Map<int, Duration> autoRewindDurations,
|
||||
final bool autoTurnOnTimer,
|
||||
final bool alwaysAutoTurnOnTimer,
|
||||
final Duration autoTurnOnTime,
|
||||
final Duration autoTurnOffTime}) = _$SleepTimerSettingsImpl;
|
||||
|
||||
factory _SleepTimerSettings.fromJson(Map<String, dynamic> json) =
|
||||
_$SleepTimerSettingsImpl.fromJson;
|
||||
|
||||
@override
|
||||
Duration get defaultDuration;
|
||||
@override
|
||||
SleepTimerShakeSenseMode get shakeSenseMode;
|
||||
@override
|
||||
|
||||
/// 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;
|
||||
@override
|
||||
bool get vibrateWhenReset;
|
||||
@override
|
||||
bool get beepWhenReset;
|
||||
@override
|
||||
bool get fadeOutAudio;
|
||||
@override
|
||||
double get shakeDetectThreshold;
|
||||
@override
|
||||
|
||||
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||
bool get autoRewindWhenStopped;
|
||||
@override
|
||||
|
||||
/// the key is the duration in minutes
|
||||
Map<int, Duration> get autoRewindDurations;
|
||||
@override
|
||||
|
||||
/// auto turn on timer settings
|
||||
bool get autoTurnOnTimer;
|
||||
@override
|
||||
|
||||
/// always auto turn on timer settings or during specific times
|
||||
bool get alwaysAutoTurnOnTimer;
|
||||
@override
|
||||
|
||||
/// auto timer settings, only used if [alwaysAutoTurnOnTimer] is false
|
||||
///
|
||||
/// duration is the time from 00:00
|
||||
Duration get autoTurnOnTime;
|
||||
@override
|
||||
Duration get autoTurnOffTime;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$SleepTimerSettingsImplCopyWith<_$SleepTimerSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map<String, dynamic> json) =>
|
|||
?.map((e) => (e as num).toDouble())
|
||||
.toList() ??
|
||||
const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
sleepTimer: json['sleepTimer'] == null
|
||||
? const Duration(minutes: 15)
|
||||
: Duration(microseconds: (json['sleepTimer'] as num).toInt()),
|
||||
sleepTimerSettings: json['sleepTimerSettings'] == null
|
||||
? const SleepTimerSettings()
|
||||
: SleepTimerSettings.fromJson(
|
||||
json['sleepTimerSettings'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
||||
|
|
@ -55,7 +56,7 @@ Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
|||
'preferredDefaultVolume': instance.preferredDefaultVolume,
|
||||
'preferredDefaultSpeed': instance.preferredDefaultSpeed,
|
||||
'speedOptions': instance.speedOptions,
|
||||
'sleepTimer': instance.sleepTimer.inMicroseconds,
|
||||
'sleepTimerSettings': instance.sleepTimerSettings,
|
||||
};
|
||||
|
||||
_$ExpandedPlayerSettingsImpl _$$ExpandedPlayerSettingsImplFromJson(
|
||||
|
|
@ -83,3 +84,69 @@ Map<String, dynamic> _$$MinimizedPlayerSettingsImplToJson(
|
|||
<String, dynamic>{
|
||||
'useChapterInfo': instance.useChapterInfo,
|
||||
};
|
||||
|
||||
_$SleepTimerSettingsImpl _$$SleepTimerSettingsImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$SleepTimerSettingsImpl(
|
||||
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,
|
||||
fadeOutAudio: json['fadeOutAudio'] as bool? ?? false,
|
||||
shakeDetectThreshold:
|
||||
(json['shakeDetectThreshold'] as num?)?.toDouble() ?? 0.5,
|
||||
autoRewindWhenStopped: json['autoRewindWhenStopped'] as bool? ?? false,
|
||||
autoRewindDurations:
|
||||
(json['autoRewindDurations'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(
|
||||
int.parse(k), Duration(microseconds: (e as num).toInt())),
|
||||
) ??
|
||||
const {
|
||||
5: Duration(seconds: 10),
|
||||
15: Duration(seconds: 30),
|
||||
45: Duration(seconds: 45),
|
||||
60: Duration(minutes: 1),
|
||||
120: Duration(minutes: 2)
|
||||
},
|
||||
autoTurnOnTimer: json['autoTurnOnTimer'] as bool? ?? false,
|
||||
alwaysAutoTurnOnTimer: json['alwaysAutoTurnOnTimer'] as bool? ?? true,
|
||||
autoTurnOnTime: json['autoTurnOnTime'] == null
|
||||
? const Duration(hours: 22, minutes: 0)
|
||||
: Duration(microseconds: (json['autoTurnOnTime'] as num).toInt()),
|
||||
autoTurnOffTime: json['autoTurnOffTime'] == null
|
||||
? const Duration(hours: 6, minutes: 0)
|
||||
: Duration(microseconds: (json['autoTurnOffTime'] as num).toInt()),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SleepTimerSettingsImplToJson(
|
||||
_$SleepTimerSettingsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'defaultDuration': instance.defaultDuration.inMicroseconds,
|
||||
'shakeSenseMode':
|
||||
_$SleepTimerShakeSenseModeEnumMap[instance.shakeSenseMode]!,
|
||||
'shakeSenseDuration': instance.shakeSenseDuration.inMicroseconds,
|
||||
'vibrateWhenReset': instance.vibrateWhenReset,
|
||||
'beepWhenReset': instance.beepWhenReset,
|
||||
'fadeOutAudio': instance.fadeOutAudio,
|
||||
'shakeDetectThreshold': instance.shakeDetectThreshold,
|
||||
'autoRewindWhenStopped': instance.autoRewindWhenStopped,
|
||||
'autoRewindDurations': instance.autoRewindDurations
|
||||
.map((k, e) => MapEntry(k.toString(), e.inMicroseconds)),
|
||||
'autoTurnOnTimer': instance.autoTurnOnTimer,
|
||||
'alwaysAutoTurnOnTimer': instance.alwaysAutoTurnOnTimer,
|
||||
'autoTurnOnTime': instance.autoTurnOnTime.inMicroseconds,
|
||||
'autoTurnOffTime': instance.autoTurnOffTime.inMicroseconds,
|
||||
};
|
||||
|
||||
const _$SleepTimerShakeSenseModeEnumMap = {
|
||||
SleepTimerShakeSenseMode.never: 'never',
|
||||
SleepTimerShakeSenseMode.always: 'always',
|
||||
SleepTimerShakeSenseMode.nearEnds: 'nearEnds',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ dependencies:
|
|||
riverpod_annotation: ^2.3.5
|
||||
rxdart: ^0.27.7
|
||||
scroll_loop_auto_scroll: ^0.0.5
|
||||
# sensors_plus: ^5.0.1
|
||||
shelfsdk:
|
||||
path: ../../_dart/shelfsdk
|
||||
shimmer: ^3.0.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue