Merge the deduplication and dead-code removal pass

Consolidates duplicated logic behind shared implementations — paginated
grid tabs, focus chrome, cached remote stores, sheet selection columns,
the server artifact store and a test fixture layer — and removes code
that had become unreachable. Net reduction of about 5,500 lines with no
behaviour change.

Where a fix had landed separately in code that moved into a shared
helper, the fix was re-applied inside the helper rather than left behind
in the copy that went away.
This commit is contained in:
edde746
2026-07-26 19:41:23 +02:00
425 changed files with 12045 additions and 17543 deletions
@@ -14,6 +14,7 @@ import '../../mpv/player/player.dart';
import '../../utils/app_logger.dart';
import '../../utils/notification_permission.dart';
import '../../utils/platform_detector.dart';
import '../media_control_router.dart';
import '../media_controls_manager.dart';
import '../multi_server_manager.dart';
import '../offline_watch_sync_service.dart';
@@ -172,9 +173,6 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO
// Getters
// ---------------------------------------------------------------------
@override
bool get isAvailable => true;
@override
MediaItem? get currentTrack => _currentTrack;
@@ -810,27 +808,32 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO
);
}
/// OS transport commands. Music has no authorization gate: the session only
/// exists while a track is loaded, and that is checked in [_onControlEvent].
late final _mediaControlRouter = MediaControlRouter(
canControlPlayback: () => true,
canNavigateMediaItems: () => true,
onPlay: () => unawaited(play()),
onPause: () => unawaited(pause()),
onTogglePlayPause: () => unawaited(togglePlayPause()),
onSeek: (position) => unawaited(seek(position)),
onNext: () => unawaited(next()),
onPrevious: () => unawaited(previous()),
onStop: () => unawaited(stop()),
onSkipForward: (interval) => unawaited(_seekRelative(interval ?? _defaultSkipInterval)),
onSkipBackward: (interval) => unawaited(_seekRelative(-(interval ?? _defaultSkipInterval))),
// Speed is deliberately ignored: music always plays at 1.0 and the control
// is not advertised — but Linux MPRIS exposes an always-writable Rate
// property, so the event can still arrive. The periodic playback-state
// update reasserts speed 1.0.
onSetSpeed: (_) {},
);
void _onControlEvent(MediaControlEvent event) {
if (_disposed || _currentTrack == null) return;
if (event is PlayEvent) {
unawaited(play());
} else if (event is PauseEvent) {
unawaited(pause());
} else if (event is TogglePlayPauseEvent) {
unawaited(togglePlayPause());
} else if (event is NextTrackEvent) {
unawaited(next());
} else if (event is PreviousTrackEvent) {
unawaited(previous());
} else if (event is SeekEvent) {
unawaited(seek(event.position));
} else if (event is StopEvent) {
unawaited(stop());
} else if (event is SkipForwardEvent) {
unawaited(_seekRelative(event.interval ?? _defaultSkipInterval));
} else if (event is SkipBackwardEvent) {
unawaited(_seekRelative(-(event.interval ?? _defaultSkipInterval)));
} else if (event is AudioInterruptionBeganEvent || event is AudioRouteOldDeviceUnavailableEvent) {
if (_mediaControlRouter.route(event)) return;
if (event is AudioInterruptionBeganEvent || event is AudioRouteOldDeviceUnavailableEvent) {
// Remember whether we were playing so interruption-end/route-return
// can resume. Unlike video, music resumes even while backgrounded —
// background audio is the product.
@@ -849,10 +852,6 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO
unawaited(play());
}
}
// SetSpeedEvent is deliberately unhandled: music always plays at 1.0 and
// the control is not advertised — but Linux MPRIS exposes an always-
// writable Rate property, so the event can still arrive. The periodic
// playback-state update reasserts speed 1.0.
}
static const _defaultSkipInterval = Duration(seconds: 15);
@@ -1175,10 +1174,7 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO
_queueSessionRevision++;
_generation++;
_invalidateArmRequests();
_completedConfirmTimer?.cancel();
_completedConfirmTimer = null;
_cancelSleepTimer();
_finalizeCurrentTrack();
_cancelTimersAndFinalizeTrack();
_queue.clear();
_currentTrack = null;
_currentSource = null;
@@ -1188,27 +1184,56 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO
_resumeAfterInterruption = false;
_setStatus(endStatus, forceNotify: true);
final player = _player;
_player = null;
await _teardownPlayerAndControls(awaitStop: true);
}
/// Kills the completion/sleep timers and flushes the track's final progress
/// report — done before [_setStatus] so listeners never see a live timer.
void _cancelTimersAndFinalizeTrack() {
_completedConfirmTimer?.cancel();
_completedConfirmTimer = null;
_cancelSleepTimer();
_finalizeCurrentTrack();
}
/// Detaches the player streams, shuts the player down and drops the OS media
/// session — the teardown shared by [_stopSession] and [dispose].
///
/// [awaitStop] stops the player and awaits every step, so callers know the
/// audio core is gone once the future resolves. The `false` path must never
/// suspend: [dispose] is a synchronous override and needs the whole teardown
/// to run in the caller's turn, before `super.dispose()`.
Future<void> _teardownPlayerAndControls({required bool awaitStop}) async {
for (final sub in _playerSubs) {
unawaited(sub.cancel());
}
_playerSubs.clear();
final player = _player;
_player = null;
if (player != null && !player.disposed) {
try {
await player.stop();
} catch (e) {
appLogger.d('Audio player stop failed during session teardown', error: e);
}
try {
await player.abandonAudioFocus();
} catch (e) {
appLogger.d('Audio focus abandon failed during session teardown', error: e);
}
try {
await player.dispose();
} catch (e) {
appLogger.w('Audio player dispose failed during session teardown', error: e);
if (awaitStop) {
try {
await player.stop();
} catch (e) {
appLogger.d('Audio player stop failed during session teardown', error: e);
}
try {
await player.abandonAudioFocus();
} catch (e) {
appLogger.d('Audio focus abandon failed during session teardown', error: e);
}
try {
await player.dispose();
} catch (e) {
appLogger.w('Audio player dispose failed during session teardown', error: e);
}
} else {
unawaited(
player.abandonAudioFocus().catchError((Object e) {
appLogger.d('Audio focus abandon failed during dispose', error: e);
}),
);
unawaited(player.dispose());
}
}
@@ -1256,33 +1281,9 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO
_observesLifecycle = false;
}
_coordinator.unregisterMusicSession(_stopForVideoClaim);
_completedConfirmTimer?.cancel();
_completedConfirmTimer = null;
_cancelSleepTimer();
_finalizeCurrentTrack();
for (final sub in _playerSubs) {
unawaited(sub.cancel());
}
_playerSubs.clear();
unawaited(_controlEventsSub?.cancel());
_controlEventsSub = null;
final player = _player;
_player = null;
if (player != null && !player.disposed) {
unawaited(
player.abandonAudioFocus().catchError((Object e) {
appLogger.d('Audio focus abandon failed during dispose', error: e);
}),
);
unawaited(player.dispose());
}
final controls = _mediaControls;
_mediaControls = null;
if (controls != null) {
unawaited(controls.setBackgroundMode(false));
unawaited(controls.clear());
controls.dispose();
}
_cancelTimersAndFinalizeTrack();
// Runs to completion synchronously — see the awaitStop: false contract.
unawaited(_teardownPlayerAndControls(awaitStop: false));
unawaited(_positionController.close());
unawaited(_errorsController.close());
_volumeNotifier.dispose();