mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-07 03:29:29 +00:00
feat: Add duration_picker dependency to pubspec.yaml
This commit is contained in:
parent
b98188d7fb
commit
fbd789f989
13 changed files with 558 additions and 49 deletions
146
assets/images/undraw_set_preferences_kwia.svg
Normal file
146
assets/images/undraw_set_preferences_kwia.svg
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
<svg data-name="Layer 1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="845.87074"
|
||||||
|
height="569.98927"
|
||||||
|
viewBox="0 0 845.87074 569.98927"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<polygon
|
||||||
|
points="760.114 287.832 752.997 294.857 762.452 319.527 775.643 292.177 797.249 168.007 764.02 167.081 760.114 287.832"
|
||||||
|
fill="#9f616a" />
|
||||||
|
<polygon points="705.966 547.714 687.966 547.714 673.966 388.714 708.966 388.714 705.966 547.714"
|
||||||
|
fill="#9f616a" />
|
||||||
|
<polygon points="773.966 547.714 755.966 547.714 741.966 388.714 776.966 388.714 773.966 547.714"
|
||||||
|
fill="#9f616a" />
|
||||||
|
<path
|
||||||
|
d="M937.09032,733.461l-26.27966-.13983a10.60246,10.60246,0,0,1-5.18583-13.20591h0a10.60249,10.60249,0,0,1,8.18437-6.73544l4.96424-.82737,4.32555-5.50525a8.19029,8.19029,0,0,1,10.758-2.00034c4.14944,2.63357,9.90449,3.511,17,2.92223l5.52291,13.4861c1.09439,7.29591-3.90074,12.10883-11.188,13.25945h0A13.43132,13.43132,0,0,1,937.09032,733.461Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#2f2e41" />
|
||||||
|
<path
|
||||||
|
d="M869.09032,733.461l-26.27966-.13983a10.60246,10.60246,0,0,1-5.18583-13.20591h0a10.60249,10.60249,0,0,1,8.18437-6.73544l4.96424-.82737,4.80952-6.12121a7.45937,7.45937,0,0,1,10.01669-1.55131c4.16748,2.7633,10.01393,3.69017,17.25728,3.08916l5.52291,13.4861c1.09439,7.29591-3.90074,12.10883-11.188,13.25945h0A13.43132,13.43132,0,0,1,869.09032,733.461Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#2f2e41" />
|
||||||
|
<circle cx="710.9656"
|
||||||
|
cy="55.71363"
|
||||||
|
r="27"
|
||||||
|
fill="#9f616a" />
|
||||||
|
<path
|
||||||
|
d="M927.03023,272.719l-44,3c.15576-14.2391-.46558-27.13579-4-35l31-10C910.3279,243.25667,916.90486,257.50461,927.03023,272.719Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#9f616a" />
|
||||||
|
<path
|
||||||
|
d="M954.03023,467.719l-113-9c15.0769-50.26016,10.35876-116.07391-5-165.41577a28.4717,28.4717,0,0,1,18.28887-26.58848L883.03023,255.719l36,4,30.356,15.178a27.71585,27.71585,0,0,1,15.31608,25.30937C953.29231,345.83844,951.91774,404.51285,954.03023,467.719Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<path d="M869.03023,344.719l-51-9L822.42,301.69848a42.20258,42.20258,0,0,1,30.84455-35.34011l9.76571-2.63938Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<polygon
|
||||||
|
points="661.966 292.714 667.966 300.714 658.966 325.714 645.966 294.714 642.966 168.714 675.966 172.714 661.966 292.714"
|
||||||
|
fill="#9f616a" />
|
||||||
|
<path d="M925.03023,346.719l51-9-4.38975-34.02051a42.20256,42.20256,0,0,0-30.84455-35.34011l-9.7657-2.63938Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<path
|
||||||
|
d="M957.03023,558.719l-41.83858,2-12.16142-59-14.76772,55-44.23228,1-8.8417-71.06584a41.09716,41.09716,0,0,1,8.84169-30.93414v0l110,5Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#2f2e41" />
|
||||||
|
<path
|
||||||
|
d="M890.53023,250.219l20.19008-3.82345,12.0114-20.51949a27.28013,27.28013,0,0,0-3.19387-31.95038l0,0a27.28016,27.28016,0,0,0-24.2073-8.837l-26.16686,3.73812a17.27884,17.27884,0,0,0-14.64408,19.66833l17.62513,2.99868Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#2f2e41" />
|
||||||
|
<ellipse cx="701.4656"
|
||||||
|
cy="63.21363"
|
||||||
|
rx="5"
|
||||||
|
ry="6"
|
||||||
|
fill="#9f616a" />
|
||||||
|
<path
|
||||||
|
d="M303.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<path
|
||||||
|
d="M303.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#fff" />
|
||||||
|
<path
|
||||||
|
d="M385.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<path
|
||||||
|
d="M385.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#fff" />
|
||||||
|
<path
|
||||||
|
d="M467.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<path
|
||||||
|
d="M467.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#fff" />
|
||||||
|
<path
|
||||||
|
d="M549.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<path
|
||||||
|
d="M549.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#fff" />
|
||||||
|
<path
|
||||||
|
d="M631.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<path
|
||||||
|
d="M631.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#fff" />
|
||||||
|
<path
|
||||||
|
d="M322.2878,242.2493H290.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H322.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,322.2878,242.2493Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<path
|
||||||
|
d="M406.2878,275.2493H374.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H406.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,406.2878,275.2493Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<path
|
||||||
|
d="M486.2878,340.2493H454.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H486.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,486.2878,340.2493Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<path
|
||||||
|
d="M568.2878,222.2493H536.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H568.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,568.2878,222.2493Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<path
|
||||||
|
d="M650.2878,300.2493H618.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H650.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,650.2878,300.2493Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
<polygon points="845.475 569.989 578.074 569.989 578.074 567.804 845.871 567.804 845.475 569.989"
|
||||||
|
fill="#3f3d56" />
|
||||||
|
<circle cx="129.4656"
|
||||||
|
cy="275.21363"
|
||||||
|
r="13"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<circle cx="211.4656"
|
||||||
|
cy="275.21363"
|
||||||
|
r="13"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<circle cx="293.4656"
|
||||||
|
cy="275.21363"
|
||||||
|
r="13"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<circle cx="375.4656"
|
||||||
|
cy="275.21363"
|
||||||
|
r="13"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<circle cx="457.4656"
|
||||||
|
cy="275.21363"
|
||||||
|
r="13"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<path
|
||||||
|
d="M205.86092,165.00536a28.79631,28.79631,0,1,0,28.79632,28.79629A28.79629,28.79629,0,0,0,205.86092,165.00536Zm0,53.33839a24.54208,24.54208,0,1,1,24.5421-24.5421A24.54208,24.54208,0,0,1,205.86092,218.34375Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#e6e6e6" />
|
||||||
|
<path
|
||||||
|
d="M204.26992,203.64975a1.51846,1.51846,0,0,1-1.07424-.44513L194.99111,195a1.5192,1.5192,0,0,1,2.14848-2.14847l6.95228,6.95228,12.32319-16.80442a1.51945,1.51945,0,0,1,2.45057,1.79712l-13.37042,18.23239a1.52072,1.52072,0,0,1-1.10955.61665C204.34708,203.64856,204.3082,203.64975,204.26992,203.64975Z"
|
||||||
|
transform="translate(-177.06463 -165.00536)"
|
||||||
|
fill="#6c63ff" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.1 KiB |
1
assets/images/undraw_time_management_re_tk5w.svg
Normal file
1
assets/images/undraw_time_management_re_tk5w.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:duration_picker/duration_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
@ -7,7 +8,9 @@ import 'package:whispering_pages/constants/sizes.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/features/sleep_timer/core/sleep_timer.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/features/sleep_timer/providers/sleep_timer_provider.dart'
|
||||||
|
show sleepTimerProvider;
|
||||||
|
import 'package:whispering_pages/settings/app_settings_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_button.dart';
|
||||||
|
|
@ -227,17 +230,84 @@ class SleepTimerButton extends HookConsumerWidget {
|
||||||
final sleepTimer = ref.watch(sleepTimerProvider);
|
final sleepTimer = ref.watch(sleepTimerProvider);
|
||||||
// if sleep timer is not active, show the button with the sleep timer icon
|
// 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
|
// if the sleep timer is active, show the remaining time in a pill shaped container
|
||||||
return sleepTimer == null
|
return Tooltip(
|
||||||
? IconButton(
|
message: 'Sleep Timer',
|
||||||
color: sleepTimer != null
|
child: InkWell(
|
||||||
? Theme.of(context).colorScheme.primary
|
onTap: () async {
|
||||||
: Theme.of(context).colorScheme.onSurface,
|
// show the sleep timer dialog
|
||||||
icon: const Icon(Icons.timer_rounded),
|
final resultingDuration = await showDurationPicker(
|
||||||
onPressed: () {},
|
context: context,
|
||||||
)
|
initialTime: ref
|
||||||
: RemainingSleepTimeDisplay(
|
.watch(appSettingsProvider)
|
||||||
timer: sleepTimer,
|
.playerSettings
|
||||||
|
.sleepTimerSettings
|
||||||
|
.defaultDuration,
|
||||||
);
|
);
|
||||||
|
if (resultingDuration != null) {
|
||||||
|
// if 0 is selected, cancel the timer
|
||||||
|
if (resultingDuration.inSeconds == 0) {
|
||||||
|
ref.read(sleepTimerProvider.notifier).cancelTimer();
|
||||||
|
} else {
|
||||||
|
ref.read(sleepTimerProvider.notifier).setTimer(resultingDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: sleepTimer == null
|
||||||
|
? Icon(
|
||||||
|
Icons.timer_rounded,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
: RemainingSleepTimeDisplay(
|
||||||
|
timer: sleepTimer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SleepTimerDialog extends HookConsumerWidget {
|
||||||
|
const SleepTimerDialog({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final sleepTimer = ref.watch(sleepTimerProvider);
|
||||||
|
final sleepTimerSettings =
|
||||||
|
ref.watch(appSettingsProvider).playerSettings.sleepTimerSettings;
|
||||||
|
final timerDurationController = useTextEditingController(
|
||||||
|
text: sleepTimerSettings.defaultDuration.inMinutes.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Sleep Timer'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text('Set the duration for the sleep timer'),
|
||||||
|
TextField(
|
||||||
|
controller: timerDurationController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Duration in minutes',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
// sleepTimer.setTimer(
|
||||||
|
// Duration(
|
||||||
|
// minutes: int.tryParse(timerDurationController.text) ?? 0,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Set Timer'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,14 @@ import 'package:whispering_pages/features/player/core/audiobook_player.dart';
|
||||||
/// timer is cancelled when the music player is paused or stopped
|
/// timer is cancelled when the music player is paused or stopped
|
||||||
class SleepTimer {
|
class SleepTimer {
|
||||||
/// The duration after which the music player will be paused
|
/// The duration after which the music player will be paused
|
||||||
final Duration duration;
|
Duration _duration;
|
||||||
|
|
||||||
|
Duration get duration => _duration;
|
||||||
|
|
||||||
|
set duration(Duration value) {
|
||||||
|
_duration = value;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
/// The player to be paused
|
/// The player to be paused
|
||||||
final AudiobookPlayer player;
|
final AudiobookPlayer player;
|
||||||
|
|
@ -23,22 +30,30 @@ class SleepTimer {
|
||||||
/// when the timer was started
|
/// when the timer was started
|
||||||
DateTime? startedAt;
|
DateTime? startedAt;
|
||||||
|
|
||||||
SleepTimer({required this.duration, required this.player}) {
|
/// subscriptions
|
||||||
player.playbackEventStream.listen((event) {
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
if (event.processingState == ProcessingState.completed ||
|
|
||||||
event.processingState == ProcessingState.idle) {
|
SleepTimer({required duration, required this.player}) : _duration = duration {
|
||||||
reset();
|
_subscriptions.add(
|
||||||
}
|
player.playbackEventStream.listen((event) {
|
||||||
});
|
if (event.processingState == ProcessingState.completed ||
|
||||||
|
event.processingState == ProcessingState.idle) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
/// pause the player when the timer is up
|
/// pause the player when the timer is up
|
||||||
player.playerStateStream.listen((state) {
|
_subscriptions.add(
|
||||||
if (state.playing && timer == null) {
|
player.playerStateStream.listen((state) {
|
||||||
startTimer();
|
if (state.playing && timer == null) {
|
||||||
} else if (!state.playing) {
|
startTimer();
|
||||||
reset();
|
} else if (!state.playing) {
|
||||||
}
|
reset();
|
||||||
});
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
debugPrint('SleepTimer created with duration: $duration');
|
debugPrint('SleepTimer created with duration: $duration');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,9 +68,12 @@ class SleepTimer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// starts the timer
|
/// starts the timer with the given duration or the default duration
|
||||||
void startTimer() {
|
void startTimer([
|
||||||
|
Duration? forDuration,
|
||||||
|
]) {
|
||||||
reset();
|
reset();
|
||||||
|
duration = forDuration ?? duration;
|
||||||
timer = Timer(duration, () {
|
timer = Timer(duration, () {
|
||||||
player.pause();
|
player.pause();
|
||||||
reset();
|
reset();
|
||||||
|
|
@ -84,6 +102,9 @@ class SleepTimer {
|
||||||
/// dispose the timer
|
/// dispose the timer
|
||||||
void dispose() {
|
void dispose() {
|
||||||
reset();
|
reset();
|
||||||
|
for (var sub in _subscriptions) {
|
||||||
|
sub.cancel();
|
||||||
|
}
|
||||||
debugPrint('SleepTimer disposed');
|
debugPrint('SleepTimer disposed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,59 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:whispering_pages/features/player/providers/audiobook_player.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/features/sleep_timer/core/sleep_timer.dart'
|
||||||
|
as core;
|
||||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||||
|
import 'package:whispering_pages/shared/extensions/time_of_day.dart';
|
||||||
|
|
||||||
part 'sleep_timer_provider.g.dart';
|
part 'sleep_timer_provider.g.dart';
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
SleepTimer? sleepTimer(SleepTimerRef ref) {
|
class SleepTimer extends _$SleepTimer {
|
||||||
final appSettings = ref.watch(appSettingsProvider);
|
@override
|
||||||
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
core.SleepTimer? build() {
|
||||||
var sleepTimer = SleepTimer(
|
final appSettings = ref.watch(appSettingsProvider);
|
||||||
// duration: sleepTimerSettings.defaultDuration,
|
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
||||||
duration: const Duration(seconds: 5),
|
bool isEnabled = sleepTimerSettings.autoTurnOnTimer;
|
||||||
player: ref.watch(simpleAudiobookPlayerProvider),
|
if (!isEnabled) {
|
||||||
);
|
return null;
|
||||||
ref.onDispose(sleepTimer.dispose);
|
}
|
||||||
return sleepTimer;
|
|
||||||
|
if ((!sleepTimerSettings.alwaysAutoTurnOnTimer) &&
|
||||||
|
(sleepTimerSettings.autoTurnOnTime
|
||||||
|
.toTimeOfDay()
|
||||||
|
.isAfter(TimeOfDay.now()) &&
|
||||||
|
sleepTimerSettings.autoTurnOffTime
|
||||||
|
.toTimeOfDay()
|
||||||
|
.isBefore(TimeOfDay.now()))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sleepTimer = core.SleepTimer(
|
||||||
|
// duration: sleepTimerSettings.defaultDuration,
|
||||||
|
duration: const Duration(seconds: 5),
|
||||||
|
player: ref.watch(simpleAudiobookPlayerProvider),
|
||||||
|
);
|
||||||
|
ref.onDispose(sleepTimer.dispose);
|
||||||
|
return sleepTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTimer(Duration resultingDuration) {
|
||||||
|
if (state != null) {
|
||||||
|
state!.duration = resultingDuration;
|
||||||
|
ref.notifyListeners();
|
||||||
|
} else {
|
||||||
|
final timer = core.SleepTimer(
|
||||||
|
duration: resultingDuration,
|
||||||
|
player: ref.watch(simpleAudiobookPlayerProvider),
|
||||||
|
);
|
||||||
|
ref.onDispose(timer.dispose);
|
||||||
|
state = timer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelTimer() {
|
||||||
|
state?.dispose();
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ part of 'sleep_timer_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$sleepTimerHash() => r'79646b12412f3300166db29328664a5e58e405bd';
|
String _$sleepTimerHash() => r'de2f39febda3c2234e792f64199c51828206ea9b';
|
||||||
|
|
||||||
/// See also [sleepTimer].
|
/// See also [SleepTimer].
|
||||||
@ProviderFor(sleepTimer)
|
@ProviderFor(SleepTimer)
|
||||||
final sleepTimerProvider = Provider<SleepTimer?>.internal(
|
final sleepTimerProvider =
|
||||||
sleepTimer,
|
NotifierProvider<SleepTimer, core.SleepTimer?>.internal(
|
||||||
|
SleepTimer.new,
|
||||||
name: r'sleepTimerProvider',
|
name: r'sleepTimerProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
const bool.fromEnvironment('dart.vm.product') ? null : _$sleepTimerHash,
|
const bool.fromEnvironment('dart.vm.product') ? null : _$sleepTimerHash,
|
||||||
|
|
@ -19,6 +20,6 @@ final sleepTimerProvider = Provider<SleepTimer?>.internal(
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef SleepTimerRef = ProviderRef<SleepTimer?>;
|
typedef _$SleepTimer = Notifier<core.SleepTimer?>;
|
||||||
// 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
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,19 @@ class Routes {
|
||||||
pathParamName: 'itemId',
|
pathParamName: 'itemId',
|
||||||
name: 'libraryItem',
|
name: 'libraryItem',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// settings
|
||||||
static const settings = _SimpleRoute(
|
static const settings = _SimpleRoute(
|
||||||
pathName: 'config',
|
pathName: 'config',
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
);
|
);
|
||||||
|
static const autoSleepTimerSettings = _SimpleRoute(
|
||||||
|
pathName: 'autosleeptimer',
|
||||||
|
name: 'autoSleepTimerSettings',
|
||||||
|
// parentRoute: settings,
|
||||||
|
);
|
||||||
|
|
||||||
|
// search and explore
|
||||||
static const search = _SimpleRoute(
|
static const search = _SimpleRoute(
|
||||||
pathName: 'search',
|
pathName: 'search',
|
||||||
name: 'search',
|
name: 'search',
|
||||||
|
|
@ -51,9 +60,12 @@ class _SimpleRoute {
|
||||||
final String name;
|
final String name;
|
||||||
final _SimpleRoute? parentRoute;
|
final _SimpleRoute? parentRoute;
|
||||||
|
|
||||||
String get path =>
|
/// the full path of the route
|
||||||
'${parentRoute?.path ?? ''}${parentRoute != null ? '/' : ''}$localPath';
|
String get path {
|
||||||
|
return '${parentRoute?.path ?? ''}$localPath';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the local path of the route
|
||||||
String get localPath =>
|
String get localPath =>
|
||||||
'/$pathName${pathParamName != null ? '/:$pathParamName' : ''}';
|
'/$pathName${pathParamName != null ? '/:$pathParamName' : ''}';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ import 'package:whispering_pages/features/explore/view/explore_page.dart';
|
||||||
import 'package:whispering_pages/features/explore/view/search_result_page.dart';
|
import 'package:whispering_pages/features/explore/view/search_result_page.dart';
|
||||||
import 'package:whispering_pages/features/item_viewer/view/library_item_page.dart';
|
import 'package:whispering_pages/features/item_viewer/view/library_item_page.dart';
|
||||||
import 'package:whispering_pages/features/onboarding/view/onboarding_single_page.dart';
|
import 'package:whispering_pages/features/onboarding/view/onboarding_single_page.dart';
|
||||||
import 'package:whispering_pages/pages/app_settings.dart';
|
|
||||||
import 'package:whispering_pages/pages/home_page.dart';
|
import 'package:whispering_pages/pages/home_page.dart';
|
||||||
|
import 'package:whispering_pages/settings/view/app_settings_page.dart';
|
||||||
|
import 'package:whispering_pages/settings/view/auto_sleep_timer_settings_page.dart';
|
||||||
|
|
||||||
import 'scaffold_with_nav_bar.dart';
|
import 'scaffold_with_nav_bar.dart';
|
||||||
import 'transitions/slide.dart';
|
import 'transitions/slide.dart';
|
||||||
|
|
@ -128,6 +129,15 @@ class MyAppRouter {
|
||||||
// builder: (context, state) => const AppSettingsPage(),
|
// builder: (context, state) => const AppSettingsPage(),
|
||||||
pageBuilder: defaultPageBuilder(const AppSettingsPage()),
|
pageBuilder: defaultPageBuilder(const AppSettingsPage()),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.autoSleepTimerSettings.path,
|
||||||
|
name: Routes.autoSleepTimerSettings.name,
|
||||||
|
// builder: (context, state) =>
|
||||||
|
// const AutoSleepTimerSettingsPage(),
|
||||||
|
pageBuilder: defaultPageBuilder(
|
||||||
|
const AutoSleepTimerSettingsPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
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:flutter_settings_ui/flutter_settings_ui.dart';
|
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:whispering_pages/api/authenticated_user_provider.dart';
|
import 'package:whispering_pages/api/authenticated_user_provider.dart';
|
||||||
import 'package:whispering_pages/api/server_provider.dart';
|
import 'package:whispering_pages/api/server_provider.dart';
|
||||||
|
import 'package:whispering_pages/router/router.dart';
|
||||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||||
|
|
||||||
class AppSettingsPage extends HookConsumerWidget {
|
class AppSettingsPage extends HookConsumerWidget {
|
||||||
|
|
@ -19,6 +21,7 @@ class AppSettingsPage extends HookConsumerWidget {
|
||||||
final availableUsers = ref.watch(authenticatedUserProvider);
|
final availableUsers = ref.watch(authenticatedUserProvider);
|
||||||
final serverURIController = useTextEditingController();
|
final serverURIController = useTextEditingController();
|
||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -26,6 +29,7 @@ class AppSettingsPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
body: SettingsList(
|
body: SettingsList(
|
||||||
sections: [
|
sections: [
|
||||||
|
// Appearance section
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
margin: const EdgeInsetsDirectional.symmetric(
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
horizontal: 16.0,
|
horizontal: 16.0,
|
||||||
|
|
@ -66,6 +70,60 @@ class AppSettingsPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Sleep Timer section
|
||||||
|
SettingsSection(
|
||||||
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'Sleep Timer',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
tiles: [
|
||||||
|
SettingsTile.navigation(
|
||||||
|
// initialValue: sleepTimerSettings.autoTurnOnTimer,
|
||||||
|
title: const Text('Auto Turn On Timer'),
|
||||||
|
description: const Text(
|
||||||
|
'Automatically turn on the sleep timer based on the time of day',
|
||||||
|
),
|
||||||
|
leading: sleepTimerSettings.autoTurnOnTimer
|
||||||
|
? const Icon(Icons.timer)
|
||||||
|
: const Icon(Icons.timer_off),
|
||||||
|
onPressed: (context) {
|
||||||
|
// push the sleep timer settings page
|
||||||
|
context.pushNamed(Routes.autoSleepTimerSettings.name);
|
||||||
|
},
|
||||||
|
// a switch to enable or disable the auto turn off time
|
||||||
|
trailing: IntrinsicHeight(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
VerticalDivider(
|
||||||
|
color: Theme.of(context).dividerColor.withOpacity(0.5),
|
||||||
|
indent: 8.0,
|
||||||
|
endIndent: 8.0,
|
||||||
|
// width: 8.0,
|
||||||
|
// thickness: 2.0,
|
||||||
|
// height: 24.0,
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: sleepTimerSettings.autoTurnOnTimer,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref.read(appSettingsProvider.notifier).updateState(
|
||||||
|
appSettings.copyWith.playerSettings
|
||||||
|
.sleepTimerSettings(
|
||||||
|
autoTurnOnTimer: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
110
lib/settings/view/auto_sleep_timer_settings_page.dart
Normal file
110
lib/settings/view/auto_sleep_timer_settings_page.dart
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||||
|
import 'package:whispering_pages/shared/extensions/time_of_day.dart';
|
||||||
|
|
||||||
|
class AutoSleepTimerSettingsPage extends HookConsumerWidget {
|
||||||
|
const AutoSleepTimerSettingsPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final appSettings = ref.watch(appSettingsProvider);
|
||||||
|
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Auto Sleep Timer Settings'),
|
||||||
|
),
|
||||||
|
body: SettingsList(
|
||||||
|
sections: [
|
||||||
|
SettingsSection(
|
||||||
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
tiles: [
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
// initialValue: sleepTimerSettings.autoTurnOnTimer,
|
||||||
|
title: const Text('Auto Turn On Timer'),
|
||||||
|
description: const Text(
|
||||||
|
'Automatically turn on the sleep timer based on the time of day',
|
||||||
|
),
|
||||||
|
leading: sleepTimerSettings.autoTurnOnTimer
|
||||||
|
? const Icon(Icons.timer)
|
||||||
|
: const Icon(Icons.timer_off),
|
||||||
|
onToggle: (value) {
|
||||||
|
ref.read(appSettingsProvider.notifier).updateState(
|
||||||
|
appSettings.copyWith.playerSettings.sleepTimerSettings(
|
||||||
|
autoTurnOnTimer: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
initialValue: sleepTimerSettings.autoTurnOnTimer,
|
||||||
|
),
|
||||||
|
// auto turn on time settings, enabled only when autoTurnOnTimer is enabled
|
||||||
|
SettingsTile.navigation(
|
||||||
|
enabled: sleepTimerSettings.autoTurnOnTimer,
|
||||||
|
title: const Text('Auto Turn On Time'),
|
||||||
|
description: const Text(
|
||||||
|
'Turn on the sleep timer at the specified time',
|
||||||
|
),
|
||||||
|
onPressed: (context) async {
|
||||||
|
// navigate to the time picker
|
||||||
|
final selected = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime:
|
||||||
|
sleepTimerSettings.autoTurnOnTime.toTimeOfDay(),
|
||||||
|
);
|
||||||
|
if (selected != null) {
|
||||||
|
ref.read(appSettingsProvider.notifier).updateState(
|
||||||
|
appSettings.copyWith.playerSettings
|
||||||
|
.sleepTimerSettings(
|
||||||
|
autoTurnOnTime: selected.toDuration(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: Text(
|
||||||
|
sleepTimerSettings.autoTurnOnTime
|
||||||
|
.toTimeOfDay()
|
||||||
|
.format(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
title: const Text('Auto Turn Off Time'),
|
||||||
|
description: const Text(
|
||||||
|
'Turn off the sleep timer at the specified time',
|
||||||
|
),
|
||||||
|
enabled: sleepTimerSettings.autoTurnOnTimer,
|
||||||
|
onPressed: (context) async {
|
||||||
|
// navigate to the time picker
|
||||||
|
final selected = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime:
|
||||||
|
sleepTimerSettings.autoTurnOffTime.toTimeOfDay(),
|
||||||
|
);
|
||||||
|
if (selected != null) {
|
||||||
|
ref.read(appSettingsProvider.notifier).updateState(
|
||||||
|
appSettings.copyWith.playerSettings
|
||||||
|
.sleepTimerSettings(
|
||||||
|
autoTurnOffTime: selected.toDuration(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: Text(
|
||||||
|
sleepTimerSettings.autoTurnOffTime
|
||||||
|
.toTimeOfDay()
|
||||||
|
.format(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
lib/shared/extensions/time_of_day.dart
Normal file
31
lib/shared/extensions/time_of_day.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
extension ToTimeOfDay on Duration {
|
||||||
|
TimeOfDay toTimeOfDay() {
|
||||||
|
return TimeOfDay(hour: inHours, minute: inMinutes % 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ToDuration on TimeOfDay {
|
||||||
|
Duration toDuration() {
|
||||||
|
return Duration(hours: hour, minutes: minute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TimeOfDayExtension on TimeOfDay {
|
||||||
|
int compareTo(TimeOfDay other) {
|
||||||
|
if (hour < other.hour) return -1;
|
||||||
|
if (hour > other.hour) return 1;
|
||||||
|
if (minute < other.minute) return -1;
|
||||||
|
if (minute > other.minute) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <(TimeOfDay other) => compareTo(other) < 0;
|
||||||
|
bool operator >(TimeOfDay other) => compareTo(other) > 0;
|
||||||
|
bool operator <=(TimeOfDay other) => compareTo(other) <= 0;
|
||||||
|
bool operator >=(TimeOfDay other) => compareTo(other) >= 0;
|
||||||
|
|
||||||
|
bool isBefore(TimeOfDay other) => this < other;
|
||||||
|
bool isAfter(TimeOfDay other) => this > other;
|
||||||
|
}
|
||||||
|
|
@ -337,6 +337,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.6"
|
version: "2.3.6"
|
||||||
|
duration_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: duration_picker
|
||||||
|
sha256: e505a749c93f3218aa4194d339e5d5480d927df23a81f075b5282511f6ac11ab
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
easy_stepper:
|
easy_stepper:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ dependencies:
|
||||||
coast: ^2.0.2
|
coast: ^2.0.2
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
|
duration_picker: ^1.2.0
|
||||||
easy_stepper: ^0.8.4
|
easy_stepper: ^0.8.4
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue