@@ -305,7 +305,6 @@ class _PlayerScreenState extends State<PlayerScreen> {
305305 _currentPosition = position;
306306 });
307307 _checkChapterBoundary (position);
308- _checkSleepTimer ();
309308 _checkPauseTrigger ();
310309 _updateCurrentSubtitle ();
311310 if (_isPlaying && position.inSeconds % 10 == 0 ) {
@@ -849,46 +848,81 @@ class _PlayerScreenState extends State<PlayerScreen> {
849848 }).toList ();
850849 }
851850
852- void _checkSleepTimer () {
853- if (_sleepDuration == null ) return ;
854- if (_sleepDuration == Duration .zero) {
855- final chapter = _currentAudiobook! .chapters[_currentChapterIndex];
856- if (_currentPosition >= chapter.endTime) {
857- exit (0 );
858- }
859- } else if (_sleepDuration! .inMinutes == - 1 ) {
860- if (_currentPosition >= _totalDuration) {
861- exit (0 );
862- }
863- }
864- }
865-
866851 void _setSleepTimer (Duration ? duration) {
867852 _sleepTimer? .cancel ();
868- if (duration == null ) {
853+ _sleepTimer = null ;
854+
855+ if (duration == null || duration.inSeconds == - 1 ) {
869856 setState (() {
870857 _sleepDuration = null ;
871858 });
859+ if (mounted) {
860+ ScaffoldMessenger .of (context).showSnackBar (
861+ const SnackBar (
862+ content: Text ('Sleep timer off' ),
863+ duration: Duration (seconds: 1 ),
864+ ),
865+ );
866+ }
872867 return ;
873868 }
869+
874870 if (duration == Duration .zero) {
871+ if (_currentAudiobook == null ) return ;
872+ final currentChapter = _currentAudiobook! .chapters[_currentChapterIndex];
873+ final timeUntilChapterEnd = currentChapter.endTime - _currentPosition;
875874 setState (() {
876875 _sleepDuration = Duration .zero;
877876 });
877+ _sleepTimer = Timer (timeUntilChapterEnd, () {
878+ exit (0 );
879+ });
880+ if (mounted) {
881+ ScaffoldMessenger .of (context).showSnackBar (
882+ const SnackBar (
883+ content: Text ('Sleep timer: Chapter end' ),
884+ duration: Duration (seconds: 1 ),
885+ ),
886+ );
887+ }
878888 return ;
879889 }
890+
880891 if (duration.inMinutes == - 1 ) {
892+ if (_currentAudiobook == null ) return ;
893+ final lastChapter = _currentAudiobook! .chapters.last;
894+ final timeUntilBookEnd = lastChapter.endTime - _currentPosition;
881895 setState (() {
882896 _sleepDuration = Duration (minutes: - 1 );
883897 });
898+ _sleepTimer = Timer (timeUntilBookEnd, () {
899+ exit (0 );
900+ });
901+ if (mounted) {
902+ ScaffoldMessenger .of (context).showSnackBar (
903+ const SnackBar (
904+ content: Text ('Sleep timer: End of audiobook' ),
905+ duration: Duration (seconds: 1 ),
906+ ),
907+ );
908+ }
884909 return ;
885910 }
911+
886912 setState (() {
887913 _sleepDuration = duration;
888914 });
889915 _sleepTimer = Timer (duration, () {
890916 exit (0 );
891917 });
918+ if (mounted) {
919+ ScaffoldMessenger .of (context).showSnackBar (
920+ SnackBar (
921+ content: Text ('Sleep timer: ${duration .inMinutes } minutes' ),
922+ duration: const Duration (seconds: 1 ),
923+ ),
924+ );
925+ }
892926 }
893927
894928 Color _adjustColorIfBright (String hexColor) {
@@ -1820,18 +1854,28 @@ class _PlayerScreenState extends State<PlayerScreen> {
18201854
18211855 Future <void > _addBookmark () async {
18221856 if (_currentAudiobook == null ) return ;
1857+
18231858 final currentChapter = _currentAudiobook! .chapters[_currentChapterIndex];
1859+ final timeFromChapterStart = _currentPosition - currentChapter.startTime;
18241860 final timeUntilChapterEnd = currentChapter.endTime - _currentPosition;
1861+
18251862 Duration bookmarkPosition = _currentPosition;
18261863 int bookmarkChapterIndex = _currentChapterIndex;
18271864 String bookmarkChapterTitle = currentChapter.title;
1828- if (timeUntilChapterEnd.inSeconds <= 10 &&
1865+
1866+ if (timeFromChapterStart.inSeconds <= 10 ) {
1867+ bookmarkPosition = currentChapter.startTime;
1868+ bookmarkChapterTitle = currentChapter.title;
1869+ bookmarkChapterIndex = _currentChapterIndex;
1870+ }
1871+ else if (timeUntilChapterEnd.inSeconds <= 10 &&
18291872 _currentChapterIndex < _currentAudiobook! .chapters.length - 1 ) {
18301873 bookmarkChapterIndex = _currentChapterIndex + 1 ;
18311874 final nextChapter = _currentAudiobook! .chapters[bookmarkChapterIndex];
18321875 bookmarkPosition = nextChapter.startTime;
18331876 bookmarkChapterTitle = nextChapter.title;
18341877 }
1878+
18351879 final bookmark = Bookmark (
18361880 audiobookPath: _currentAudiobook! .path,
18371881 audiobookTitle: _currentAudiobook! .title,
@@ -1840,15 +1884,21 @@ class _PlayerScreenState extends State<PlayerScreen> {
18401884 position: bookmarkPosition,
18411885 created: DateTime .now (),
18421886 );
1887+
18431888 setState (() {
18441889 _bookmarks.insert (0 , bookmark);
18451890 });
18461891 await _saveBookmarks ();
1892+
18471893 if (mounted) {
18481894 ScaffoldMessenger .of (context).showSnackBar (
1849- const SnackBar (
1850- content: Text ('Bookmark added' ),
1851- duration: Duration (seconds: 1 ),
1895+ SnackBar (
1896+ content: Text (
1897+ timeFromChapterStart.inSeconds <= 10 || timeUntilChapterEnd.inSeconds <= 10
1898+ ? 'Bookmark added (snapped to chapter start)'
1899+ : 'Bookmark added'
1900+ ),
1901+ duration: const Duration (seconds: 1 ),
18521902 ),
18531903 );
18541904 }
@@ -3357,6 +3407,9 @@ class _PlayerScreenState extends State<PlayerScreen> {
33573407 } else if (event.logicalKey == LogicalKeyboardKey .keyY && event is KeyDownEvent ) {
33583408 _toggleFullscreen ();
33593409 return KeyEventResult .handled;
3410+ } else if (event.logicalKey == LogicalKeyboardKey .keyZ && event is KeyDownEvent ) {
3411+ _setSleepTimer (Duration .zero);
3412+ return KeyEventResult .handled;
33603413 } else if (event.logicalKey == LogicalKeyboardKey .keyA && event is KeyDownEvent ) {
33613414 _applyDefaultSettings ();
33623415 return KeyEventResult .handled;
@@ -4525,12 +4578,65 @@ class _PlayerScreenState extends State<PlayerScreen> {
45254578 return '' ;
45264579 }
45274580
4528- Future <void > _setPinNumber (int bookmarkIndex, int ? pinNumber) async {
4529- final bookmark = _bookmarks[bookmarkIndex].copyWith (pinNumber: pinNumber);
4581+ Future <void > _setPinNumber (int displayIndex, int ? pinNumber) async {
4582+ // Get the filtered bookmarks list
4583+ final filteredBookmarks = _getFilteredBookmarks ();
4584+
4585+ // The displayIndex from the UI might be 1-indexed or include a header
4586+ // Let's use the actual index directly
4587+ if (displayIndex >= filteredBookmarks.length) return ;
4588+
4589+ // Get the actual bookmark from the filtered list
4590+ final targetBookmark = filteredBookmarks[displayIndex];
4591+
4592+ // Find the index of this bookmark in the full _bookmarks list
4593+ final actualIndex = _bookmarks.indexWhere ((b) =>
4594+ b.audiobookPath == targetBookmark.audiobookPath &&
4595+ b.chapterIndex == targetBookmark.chapterIndex &&
4596+ b.position == targetBookmark.position &&
4597+ b.created == targetBookmark.created
4598+ );
4599+
4600+ if (actualIndex == - 1 ) return ;
4601+
4602+ // Debug print to verify
4603+ print ('Display index: $displayIndex , Actual index: $actualIndex ' );
4604+ print ('Target bookmark: ${targetBookmark .chapterTitle }' );
4605+
45304606 setState (() {
4531- _bookmarks[bookmarkIndex] = bookmark;
4607+ // If setting a pin number (not null), first check if another bookmark already has this pin
4608+ if (pinNumber != null ) {
4609+ // Find if any other bookmark has this pin number
4610+ for (int i = 0 ; i < _bookmarks.length; i++ ) {
4611+ if (i != actualIndex && _bookmarks[i].pinNumber == pinNumber) {
4612+ // Remove the pin from the other bookmark
4613+ _bookmarks[i] = _bookmarks[i].copyWith (clearPin: true );
4614+ }
4615+ }
4616+ }
4617+
4618+ // Now set the pin for the requested bookmark
4619+ if (pinNumber == null ) {
4620+ _bookmarks[actualIndex] = _bookmarks[actualIndex].copyWith (clearPin: true );
4621+ } else {
4622+ _bookmarks[actualIndex] = _bookmarks[actualIndex].copyWith (pinNumber: pinNumber);
4623+ }
45324624 });
4625+
45334626 await _saveBookmarks ();
4627+
4628+ if (mounted) {
4629+ ScaffoldMessenger .of (context).showSnackBar (
4630+ SnackBar (
4631+ content: Text (
4632+ pinNumber == null
4633+ ? 'Bookmark unpinned: ${targetBookmark .chapterTitle }'
4634+ : 'Bookmark pinned to $pinNumber : ${targetBookmark .chapterTitle }'
4635+ ),
4636+ duration: const Duration (seconds: 2 ),
4637+ ),
4638+ );
4639+ }
45344640 }
45354641
45364642 Future <void > _jumpToPinnedBookmark (int pinNumber) async {
@@ -4546,7 +4652,6 @@ class _PlayerScreenState extends State<PlayerScreen> {
45464652 Future <void > _skipToPreviousSubtitle () async {
45474653 if (_subtitles.isEmpty) return ;
45484654
4549- // Find current subtitle index first
45504655 int currentIndex = - 1 ;
45514656 for (int i = 0 ; i < _subtitles.length; i++ ) {
45524657 if (_subtitles[i].startTime <= _currentPosition &&
@@ -4556,7 +4661,6 @@ class _PlayerScreenState extends State<PlayerScreen> {
45564661 }
45574662 }
45584663
4559- // Go to previous subtitle if it exists
45604664 if (currentIndex > 0 ) {
45614665 await _seekTo (_subtitles[currentIndex - 1 ].startTime);
45624666 } else if (currentIndex == 0 ) {
0 commit comments