Files
Ensemble/lib/providers/sleep_timer_provider.dart
ChrisandClaude Opus 4.6 ccfea4168c Fix critical and high priority issues from codebase audit
Critical fixes:
- Remove empty setState() in debug_log_screen.dart
- Add disposed flag to sleep_timer_provider.dart to prevent
  notifyListeners() after dispose

High priority fixes:
- Convert .then() chains to async/await for better error handling
  in playlist_details_screen.dart, album_details_screen.dart,
  expandable_player.dart, player_reveal_overlay.dart,
  queue_panel.dart, and all card widgets
- Extract _loadHintSettings() and _fetchAuthorImage() methods

Also includes:
- CODEBASE_AUDIT_REPORT.md with comprehensive audit findings

Tested on device - no regressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 10:54:08 +00:00

134 lines
3.9 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import '../services/debug_logger.dart';
/// Callback type for when sleep timer expires and should pause playback
typedef OnSleepTimerExpired = void Function();
/// Provider for sleep timer functionality.
///
/// Extracted from MusicAssistantProvider to reduce complexity and improve testability.
/// This provider manages:
/// - Timed sleep (pause after X minutes)
/// - End of track sleep (pause when current track ends)
class SleepTimerProvider with ChangeNotifier {
final DebugLogger _logger = DebugLogger();
Timer? _sleepTimer;
DateTime? _sleepTimerEndTime;
int? _sleepTimerMinutes; // null = off, -1 = end of track, positive = minutes
Timer? _sleepTimerDisplayTimer;
bool _disposed = false;
/// Callback to pause playback when timer expires
OnSleepTimerExpired? onExpired;
// ============================================================================
// GETTERS
// ============================================================================
/// Whether a sleep timer is currently active
bool get isActive => _sleepTimerMinutes != null;
/// Current timer setting: null = off, -1 = end of track, positive = minutes
int? get minutes => _sleepTimerMinutes;
/// Time remaining until sleep timer expires (null if end-of-track mode or inactive)
Duration? get remaining {
if (_sleepTimerEndTime == null) return null;
final remaining = _sleepTimerEndTime!.difference(DateTime.now());
return remaining.isNegative ? Duration.zero : remaining;
}
// ============================================================================
// TIMER CONTROL
// ============================================================================
/// Set sleep timer. Pass null to turn off, -1 for end of track, or minutes.
void setTimer(int? minutes) {
// Cancel any existing timer
_cancelInternal();
_sleepTimerMinutes = minutes;
if (minutes == null) {
// Timer off
_logger.log('😴 Sleep timer: OFF');
notifyListeners();
return;
}
if (minutes == -1) {
// End of track - handled in player state updates
_sleepTimerEndTime = null; // No fixed end time
_logger.log('😴 Sleep timer: End of track');
notifyListeners();
return;
}
// Set timer for specified minutes
_sleepTimerEndTime = DateTime.now().add(Duration(minutes: minutes));
_logger.log('😴 Sleep timer: $minutes minutes (until $_sleepTimerEndTime)');
// Create the actual timer
_sleepTimer = Timer(Duration(minutes: minutes), _onTimerExpired);
// Start display update timer (every second for countdown)
_sleepTimerDisplayTimer = Timer.periodic(const Duration(seconds: 1), (_) {
if (!_disposed) {
notifyListeners(); // Update UI with remaining time
}
});
notifyListeners();
}
/// Cancel sleep timer
void cancel() {
_cancelInternal();
_sleepTimerMinutes = null;
_sleepTimerEndTime = null;
_logger.log('😴 Sleep timer: Cancelled');
notifyListeners();
}
void _cancelInternal() {
_sleepTimer?.cancel();
_sleepTimer = null;
_sleepTimerDisplayTimer?.cancel();
_sleepTimerDisplayTimer = null;
}
void _onTimerExpired() {
_logger.log('😴 Sleep timer expired - pausing playback');
_cancelInternal();
_sleepTimerMinutes = null;
_sleepTimerEndTime = null;
// Trigger the callback to pause playback
onExpired?.call();
notifyListeners();
}
/// Called when track ends - checks if "end of track" sleep timer is active
void checkEndOfTrack() {
if (_sleepTimerMinutes == -1) {
_logger.log('😴 End of track sleep timer triggered');
_sleepTimerMinutes = null;
// Trigger the callback to pause playback
onExpired?.call();
notifyListeners();
}
}
@override
void dispose() {
_disposed = true;
_cancelInternal();
super.dispose();
}
}