mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-10 13:09: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:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:whispering_pages/api/api_provider.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)
|
// @Riverpod(keepAlive: true)
|
||||||
// abp.AudiobookPlayer audiobookPlayer(
|
// abp.AudiobookPlayer audiobookPlayer(
|
||||||
|
|
@ -19,12 +20,23 @@ part 'audiobook_player_provider.g.dart';
|
||||||
const playerId = 'audiobook_player';
|
const playerId = 'audiobook_player';
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class AudiobookPlayer extends _$AudiobookPlayer {
|
class SimpleAudiobookPlayer extends _$SimpleAudiobookPlayer {
|
||||||
@override
|
@override
|
||||||
abp.AudiobookPlayer build() {
|
abp.AudiobookPlayer build() {
|
||||||
final api = ref.watch(authenticatedApiProvider);
|
final api = ref.watch(authenticatedApiProvider);
|
||||||
final player =
|
final player = abp.AudiobookPlayer(api.token!, api.baseUrl);
|
||||||
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);
|
ref.onDispose(player.dispose);
|
||||||
|
|
||||||
|
|
@ -40,7 +52,7 @@ class AudiobookPlayer extends _$AudiobookPlayer {
|
||||||
ref.notifyListeners();
|
ref.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setSpeed(double speed) async {
|
Future<void> setSpeed(double speed) async {
|
||||||
await state.setSpeed(speed);
|
await state.setSpeed(speed);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,24 @@ part of 'audiobook_player.dart';
|
||||||
// RiverpodGenerator
|
// 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].
|
/// See also [AudiobookPlayer].
|
||||||
@ProviderFor(AudiobookPlayer)
|
@ProviderFor(AudiobookPlayer)
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/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/providers/currently_playing_provider.dart';
|
||||||
import 'package:whispering_pages/features/player/view/audiobook_player.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 '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 {
|
class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
const PlayerWhenExpanded({
|
const PlayerWhenExpanded({
|
||||||
super.key,
|
super.key,
|
||||||
|
|
@ -127,7 +131,7 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onBackground
|
.onSurface
|
||||||
.withOpacity(0.7),
|
.withOpacity(0.7),
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
|
@ -193,10 +197,7 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
// speed control
|
// speed control
|
||||||
const PlayerSpeedAdjustButton(),
|
const PlayerSpeedAdjustButton(),
|
||||||
// sleep timer
|
// sleep timer
|
||||||
IconButton(
|
const SleepTimerButton(),
|
||||||
icon: const Icon(Icons.timer),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
// chapter list
|
// chapter list
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.menu_book_rounded),
|
icon: const Icon(Icons.menu_book_rounded),
|
||||||
|
|
@ -216,334 +217,85 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlayerSpeedAdjustButton extends HookConsumerWidget {
|
class SleepTimerButton extends HookConsumerWidget {
|
||||||
const PlayerSpeedAdjustButton({
|
const SleepTimerButton({
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final sleepTimer = ref.watch(sleepTimerProvider);
|
||||||
final notifier = ref.watch(audiobookPlayerProvider.notifier);
|
// if sleep timer is not active, show the button with the sleep timer icon
|
||||||
return TextButton(
|
// if the sleep timer is active, show the remaining time in a pill shaped container
|
||||||
child: Text('${player.speed}x'),
|
return sleepTimer == null
|
||||||
onPressed: () {
|
? IconButton(
|
||||||
showModalBottomSheet(
|
color: sleepTimer != null
|
||||||
context: context,
|
? Theme.of(context).colorScheme.primary
|
||||||
barrierLabel: 'Select Speed',
|
: Theme.of(context).colorScheme.onSurface,
|
||||||
constraints: const BoxConstraints(
|
icon: const Icon(Icons.timer_rounded),
|
||||||
maxHeight: 225,
|
onPressed: () {},
|
||||||
),
|
)
|
||||||
builder: (context) {
|
: RemainingSleepTimeDisplay(
|
||||||
return SpeedSelector(
|
timer: sleepTimer,
|
||||||
onSpeedSelected: (speed) {
|
);
|
||||||
notifier.setSpeed(speed);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpeedSelector extends HookConsumerWidget {
|
class RemainingSleepTimeDisplay extends HookConsumerWidget {
|
||||||
const SpeedSelector({
|
const RemainingSleepTimeDisplay({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onSpeedSelected,
|
required this.timer,
|
||||||
});
|
});
|
||||||
|
|
||||||
final void Function(double speed) onSpeedSelected;
|
final SleepTimer timer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final appSettings = ref.watch(appSettingsProvider);
|
final remainingTime = useStream(timer.remainingTimeStream).data;
|
||||||
final speeds = appSettings.playerSettings.speedOptions;
|
return Container(
|
||||||
final currentSpeed = ref.watch(audiobookPlayerProvider).speed;
|
decoration: BoxDecoration(
|
||||||
final speedState = useState(currentSpeed);
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
// hook the onSpeedSelected function to the state
|
),
|
||||||
useEffect(
|
padding: const EdgeInsets.symmetric(
|
||||||
() {
|
horizontal: 8,
|
||||||
onSpeedSelected(speedState.value);
|
vertical: 4,
|
||||||
return null;
|
),
|
||||||
},
|
child: Text(
|
||||||
[speedState.value],
|
timer.timer == null
|
||||||
);
|
? timer.duration.formatSingleLargestUnit()
|
||||||
|
: remainingTime?.formatSingleLargestUnit() ?? '',
|
||||||
// the speed options
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
const minSpeed = 0.1;
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
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 {
|
extension DurationFormat on Duration {
|
||||||
const AudiobookPlayerSeekButton({
|
/// will return a number followed by h, m, or s depending on the duration
|
||||||
super.key,
|
/// only the largest unit will be shown
|
||||||
required this.isForward,
|
String formatSingleLargestUnit() {
|
||||||
});
|
if (inHours > 0) {
|
||||||
|
return '${inHours}h';
|
||||||
/// if true, the button seeks forward, else it seeks backwards
|
} else if (inMinutes > 0) {
|
||||||
final bool isForward;
|
return '${inMinutes}m';
|
||||||
|
} else {
|
||||||
@override
|
return '${inSeconds}s';
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void useInterval(VoidCallback callback, Duration delay) {
|
||||||
|
final savedCallback = useRef(callback);
|
||||||
|
savedCallback.value = callback;
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
final timer = Timer.periodic(delay, (_) => savedCallback.value());
|
||||||
|
return timer.cancel;
|
||||||
|
},
|
||||||
|
[delay],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
show JustAudioMediaKit;
|
||||||
import 'package:whispering_pages/api/server_provider.dart';
|
import 'package:whispering_pages/api/server_provider.dart';
|
||||||
import 'package:whispering_pages/db/storage.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/router/router.dart';
|
||||||
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
||||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||||
|
|
@ -56,14 +58,36 @@ class MyApp extends ConsumerWidget {
|
||||||
routerConfig.goNamed(Routes.onboarding.name);
|
routerConfig.goNamed(Routes.onboarding.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MaterialApp.router(
|
return _EagerInitialization(
|
||||||
// debugShowCheckedModeBanner: false,
|
child: MaterialApp.router(
|
||||||
theme: lightTheme,
|
// debugShowCheckedModeBanner: false,
|
||||||
darkTheme: darkTheme,
|
theme: lightTheme,
|
||||||
themeMode: ref.watch(appSettingsProvider).isDarkMode
|
darkTheme: darkTheme,
|
||||||
? ThemeMode.dark
|
themeMode: ref.watch(appSettingsProvider).isDarkMode
|
||||||
: ThemeMode.light,
|
? ThemeMode.dark
|
||||||
routerConfig: routerConfig,
|
: 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:flutter/material.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.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/db/available_boxes.dart';
|
||||||
|
import 'package:whispering_pages/settings/models/app_settings.dart' as model;
|
||||||
|
|
||||||
part 'app_settings_provider.g.dart';
|
part 'app_settings_provider.g.dart';
|
||||||
|
|
||||||
final _box = AvailableHiveBoxes.userPrefsBox;
|
final _box = AvailableHiveBoxes.userPrefsBox;
|
||||||
|
|
||||||
@riverpod
|
@Riverpod(keepAlive: true)
|
||||||
class AppSettings extends _$AppSettings {
|
class AppSettings extends _$AppSettings {
|
||||||
@override
|
@override
|
||||||
model.AppSettings build() {
|
model.AppSettings build() {
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ part of 'app_settings_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$appSettingsHash() => r'da2cd1bb0da6e136e906bc61f29da89d0c5f53fb';
|
String _$appSettingsHash() => r'6716bc568850ffd373fd8572c5781beefafbb9ee';
|
||||||
|
|
||||||
/// See also [AppSettings].
|
/// See also [AppSettings].
|
||||||
@ProviderFor(AppSettings)
|
@ProviderFor(AppSettings)
|
||||||
final appSettingsProvider =
|
final appSettingsProvider =
|
||||||
AutoDisposeNotifierProvider<AppSettings, model.AppSettings>.internal(
|
NotifierProvider<AppSettings, model.AppSettings>.internal(
|
||||||
AppSettings.new,
|
AppSettings.new,
|
||||||
name: r'appSettingsProvider',
|
name: r'appSettingsProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
|
|
@ -20,6 +20,6 @@ final appSettingsProvider =
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef _$AppSettings = AutoDisposeNotifier<model.AppSettings>;
|
typedef _$AppSettings = Notifier<model.AppSettings>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// 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 preferredDefaultVolume,
|
||||||
@Default(1) double preferredDefaultSpeed,
|
@Default(1) double preferredDefaultSpeed,
|
||||||
@Default([0.75, 1, 1.25, 1.5, 1.75, 2]) List<double> speedOptions,
|
@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;
|
}) = _PlayerSettings;
|
||||||
|
|
||||||
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
@ -57,3 +57,50 @@ class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
|
||||||
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
_$MinimizedPlayerSettingsFromJson(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 preferredDefaultVolume => throw _privateConstructorUsedError;
|
||||||
double get preferredDefaultSpeed => throw _privateConstructorUsedError;
|
double get preferredDefaultSpeed => throw _privateConstructorUsedError;
|
||||||
List<double> get speedOptions => throw _privateConstructorUsedError;
|
List<double> get speedOptions => throw _privateConstructorUsedError;
|
||||||
Duration get sleepTimer => throw _privateConstructorUsedError;
|
SleepTimerSettings get sleepTimerSettings =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
|
@ -249,10 +250,11 @@ abstract class $PlayerSettingsCopyWith<$Res> {
|
||||||
double preferredDefaultVolume,
|
double preferredDefaultVolume,
|
||||||
double preferredDefaultSpeed,
|
double preferredDefaultSpeed,
|
||||||
List<double> speedOptions,
|
List<double> speedOptions,
|
||||||
Duration sleepTimer});
|
SleepTimerSettings sleepTimerSettings});
|
||||||
|
|
||||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||||
|
$SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -273,7 +275,7 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
||||||
Object? preferredDefaultVolume = null,
|
Object? preferredDefaultVolume = null,
|
||||||
Object? preferredDefaultSpeed = null,
|
Object? preferredDefaultSpeed = null,
|
||||||
Object? speedOptions = null,
|
Object? speedOptions = null,
|
||||||
Object? sleepTimer = null,
|
Object? sleepTimerSettings = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
miniPlayerSettings: null == miniPlayerSettings
|
miniPlayerSettings: null == miniPlayerSettings
|
||||||
|
|
@ -296,10 +298,10 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
||||||
? _value.speedOptions
|
? _value.speedOptions
|
||||||
: speedOptions // ignore: cast_nullable_to_non_nullable
|
: speedOptions // ignore: cast_nullable_to_non_nullable
|
||||||
as List<double>,
|
as List<double>,
|
||||||
sleepTimer: null == sleepTimer
|
sleepTimerSettings: null == sleepTimerSettings
|
||||||
? _value.sleepTimer
|
? _value.sleepTimerSettings
|
||||||
: sleepTimer // ignore: cast_nullable_to_non_nullable
|
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration,
|
as SleepTimerSettings,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,6 +322,15 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
||||||
return _then(_value.copyWith(expandedPlayerSettings: value) as $Val);
|
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
|
/// @nodoc
|
||||||
|
|
@ -336,12 +347,14 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res>
|
||||||
double preferredDefaultVolume,
|
double preferredDefaultVolume,
|
||||||
double preferredDefaultSpeed,
|
double preferredDefaultSpeed,
|
||||||
List<double> speedOptions,
|
List<double> speedOptions,
|
||||||
Duration sleepTimer});
|
SleepTimerSettings sleepTimerSettings});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||||
@override
|
@override
|
||||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||||
|
@override
|
||||||
|
$SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -360,7 +373,7 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
||||||
Object? preferredDefaultVolume = null,
|
Object? preferredDefaultVolume = null,
|
||||||
Object? preferredDefaultSpeed = null,
|
Object? preferredDefaultSpeed = null,
|
||||||
Object? speedOptions = null,
|
Object? speedOptions = null,
|
||||||
Object? sleepTimer = null,
|
Object? sleepTimerSettings = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$PlayerSettingsImpl(
|
return _then(_$PlayerSettingsImpl(
|
||||||
miniPlayerSettings: null == miniPlayerSettings
|
miniPlayerSettings: null == miniPlayerSettings
|
||||||
|
|
@ -383,10 +396,10 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
||||||
? _value._speedOptions
|
? _value._speedOptions
|
||||||
: speedOptions // ignore: cast_nullable_to_non_nullable
|
: speedOptions // ignore: cast_nullable_to_non_nullable
|
||||||
as List<double>,
|
as List<double>,
|
||||||
sleepTimer: null == sleepTimer
|
sleepTimerSettings: null == sleepTimerSettings
|
||||||
? _value.sleepTimer
|
? _value.sleepTimerSettings
|
||||||
: sleepTimer // ignore: cast_nullable_to_non_nullable
|
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration,
|
as SleepTimerSettings,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -400,7 +413,7 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
this.preferredDefaultVolume = 1,
|
this.preferredDefaultVolume = 1,
|
||||||
this.preferredDefaultSpeed = 1,
|
this.preferredDefaultSpeed = 1,
|
||||||
final List<double> speedOptions = const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
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;
|
: _speedOptions = speedOptions;
|
||||||
|
|
||||||
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
@ -429,11 +442,11 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final Duration sleepTimer;
|
final SleepTimerSettings sleepTimerSettings;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
|
|
@ -451,8 +464,8 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
other.preferredDefaultSpeed == preferredDefaultSpeed) &&
|
other.preferredDefaultSpeed == preferredDefaultSpeed) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._speedOptions, _speedOptions) &&
|
.equals(other._speedOptions, _speedOptions) &&
|
||||||
(identical(other.sleepTimer, sleepTimer) ||
|
(identical(other.sleepTimerSettings, sleepTimerSettings) ||
|
||||||
other.sleepTimer == sleepTimer));
|
other.sleepTimerSettings == sleepTimerSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
|
@ -464,7 +477,7 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
preferredDefaultVolume,
|
preferredDefaultVolume,
|
||||||
preferredDefaultSpeed,
|
preferredDefaultSpeed,
|
||||||
const DeepCollectionEquality().hash(_speedOptions),
|
const DeepCollectionEquality().hash(_speedOptions),
|
||||||
sleepTimer);
|
sleepTimerSettings);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
|
|
@ -488,7 +501,7 @@ abstract class _PlayerSettings implements PlayerSettings {
|
||||||
final double preferredDefaultVolume,
|
final double preferredDefaultVolume,
|
||||||
final double preferredDefaultSpeed,
|
final double preferredDefaultSpeed,
|
||||||
final List<double> speedOptions,
|
final List<double> speedOptions,
|
||||||
final Duration sleepTimer}) = _$PlayerSettingsImpl;
|
final SleepTimerSettings sleepTimerSettings}) = _$PlayerSettingsImpl;
|
||||||
|
|
||||||
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||||
_$PlayerSettingsImpl.fromJson;
|
_$PlayerSettingsImpl.fromJson;
|
||||||
|
|
@ -504,7 +517,7 @@ abstract class _PlayerSettings implements PlayerSettings {
|
||||||
@override
|
@override
|
||||||
List<double> get speedOptions;
|
List<double> get speedOptions;
|
||||||
@override
|
@override
|
||||||
Duration get sleepTimer;
|
SleepTimerSettings get sleepTimerSettings;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||||
|
|
@ -821,3 +834,486 @@ abstract class _MinimizedPlayerSettings implements MinimizedPlayerSettings {
|
||||||
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||||
get copyWith => throw _privateConstructorUsedError;
|
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())
|
?.map((e) => (e as num).toDouble())
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||||
sleepTimer: json['sleepTimer'] == null
|
sleepTimerSettings: json['sleepTimerSettings'] == null
|
||||||
? const Duration(minutes: 15)
|
? const SleepTimerSettings()
|
||||||
: Duration(microseconds: (json['sleepTimer'] as num).toInt()),
|
: SleepTimerSettings.fromJson(
|
||||||
|
json['sleepTimerSettings'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
||||||
|
|
@ -55,7 +56,7 @@ Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
||||||
'preferredDefaultVolume': instance.preferredDefaultVolume,
|
'preferredDefaultVolume': instance.preferredDefaultVolume,
|
||||||
'preferredDefaultSpeed': instance.preferredDefaultSpeed,
|
'preferredDefaultSpeed': instance.preferredDefaultSpeed,
|
||||||
'speedOptions': instance.speedOptions,
|
'speedOptions': instance.speedOptions,
|
||||||
'sleepTimer': instance.sleepTimer.inMicroseconds,
|
'sleepTimerSettings': instance.sleepTimerSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$ExpandedPlayerSettingsImpl _$$ExpandedPlayerSettingsImplFromJson(
|
_$ExpandedPlayerSettingsImpl _$$ExpandedPlayerSettingsImplFromJson(
|
||||||
|
|
@ -83,3 +84,69 @@ Map<String, dynamic> _$$MinimizedPlayerSettingsImplToJson(
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'useChapterInfo': instance.useChapterInfo,
|
'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
|
riverpod_annotation: ^2.3.5
|
||||||
rxdart: ^0.27.7
|
rxdart: ^0.27.7
|
||||||
scroll_loop_auto_scroll: ^0.0.5
|
scroll_loop_auto_scroll: ^0.0.5
|
||||||
|
# sensors_plus: ^5.0.1
|
||||||
shelfsdk:
|
shelfsdk:
|
||||||
path: ../../_dart/shelfsdk
|
path: ../../_dart/shelfsdk
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue