diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt index b1a0cfc0..4c0b5de0 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt @@ -193,6 +193,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { private var subtitlePositionPercent: Int = 100 private var subtitleFontSize: Float = 55f private var lastSubtitleCues: List = emptyList() + // Tracks whether a text track was selected on the previous onTracksChanged so we // can detect the transition to "no subtitle" and clear the painted overlays (#1387). private var hadSelectedTextTrack: Boolean = false @@ -1783,8 +1784,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { selectionFlags = format.selectionFlags ) - private fun hasSelectedTextTrack(tracks: Tracks): Boolean = - tracks.groups.any { it.type == C.TRACK_TYPE_TEXT && it.isSelected } + private fun hasSelectedTextTrack(tracks: Tracks): Boolean = tracks.groups.any { it.type == C.TRACK_TYPE_TEXT && it.isSelected } private fun restorePendingDvTrackSelection(tracks: Tracks): Boolean { val pending = pendingDvTrackRestore ?: return false diff --git a/lib/providers/discover_provider.dart b/lib/providers/discover_provider.dart index 6af75ad9..3b808cad 100644 --- a/lib/providers/discover_provider.dart +++ b/lib/providers/discover_provider.dart @@ -126,11 +126,9 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin /// and merged in; already-loaded servers are not refetched. Future syncToOnlineServers(Set onlineServerIds) { if (onlineServerIds.isEmpty || isProfileBinding()) return Future.value(); - if ( - _onDeckState == DiscoverLoadState.loaded && - _hubsState == DiscoverLoadState.loaded && - _fullyLoadedServerIds.containsAll(onlineServerIds) - ) { + if (_onDeckState == DiscoverLoadState.loaded && + _hubsState == DiscoverLoadState.loaded && + _fullyLoadedServerIds.containsAll(onlineServerIds)) { return Future.value(); } // Nothing (or a failed pass) to merge into yet — run the full load. @@ -490,10 +488,12 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin try { final settings = await SettingsService.getInstance(); - final syncableOnDeck = onDeck.where((item) { - final serverId = item.serverId; - return serverId != null && _multiServer.getClientForServer(ServerId(serverId)) != null; - }).toList(growable: false); + final syncableOnDeck = onDeck + .where((item) { + final serverId = item.serverId; + return serverId != null && _multiServer.getClientForServer(ServerId(serverId)) != null; + }) + .toList(growable: false); await SystemShelfService().syncFromContinueWatching( syncableOnDeck, _clientForShelfItem, diff --git a/lib/screens/libraries/tabs/library_collections_tab.dart b/lib/screens/libraries/tabs/library_collections_tab.dart index 4e6789ec..66157edc 100644 --- a/lib/screens/libraries/tabs/library_collections_tab.dart +++ b/lib/screens/libraries/tabs/library_collections_tab.dart @@ -140,12 +140,8 @@ class _LibraryCollectionsTabState extends BaseLibraryTabState _buildMediaCardItem( - index, - isFirstRow: index == 0, - isFirstColumn: true, - disableScale: true, - ), + itemBuilder: (context, index) => + _buildMediaCardItem(index, isFirstRow: index == 0, isFirstColumn: true, disableScale: true), ), ); } diff --git a/lib/screens/libraries/tabs/library_playlists_tab.dart b/lib/screens/libraries/tabs/library_playlists_tab.dart index 48f177d3..aefb49cc 100644 --- a/lib/screens/libraries/tabs/library_playlists_tab.dart +++ b/lib/screens/libraries/tabs/library_playlists_tab.dart @@ -142,12 +142,8 @@ class _LibraryPlaylistsTabState extends BaseLibraryTabState _buildPlaylistCard( - index, - isFirstRow: index == 0, - isFirstColumn: true, - disableScale: true, - ), + itemBuilder: (context, index) => + _buildPlaylistCard(index, isFirstRow: index == 0, isFirstColumn: true, disableScale: true), ), ); } diff --git a/lib/screens/settings/subtitle_styling_screen.dart b/lib/screens/settings/subtitle_styling_screen.dart index 2766455a..7bf6458d 100644 --- a/lib/screens/settings/subtitle_styling_screen.dart +++ b/lib/screens/settings/subtitle_styling_screen.dart @@ -62,9 +62,10 @@ class SubtitleStylingScreen extends StatelessWidget { icon: Symbols.aspect_ratio_rounded, title: t.subtitlingStyling.renderResolution, subtitleBuilder: _renderResolutionLabel, - options: const [SubtitleRenderResolution.screen, SubtitleRenderResolution.video] - .map((v) => DialogOption(value: v, title: _renderResolutionLabel(v))) - .toList(), + options: const [ + SubtitleRenderResolution.screen, + SubtitleRenderResolution.video, + ].map((v) => DialogOption(value: v, title: _renderResolutionLabel(v))).toList(), decode: (v) => v, encode: (v) => v, ), diff --git a/lib/services/jellyfin_client/parts/live_tv.dart b/lib/services/jellyfin_client/parts/live_tv.dart index 01bbb140..fc7ae7bd 100644 --- a/lib/services/jellyfin_client/parts/live_tv.dart +++ b/lib/services/jellyfin_client/parts/live_tv.dart @@ -483,7 +483,11 @@ class _JellyfinLiveTvPlaybackSession implements LiveTvPlaybackSession { Future streamUrlAt({int? offsetSeconds}) async => offsetSeconds == null ? _url : null; @override - Future reportTimeline({required String state, required int positionMs, required int durationMs}) async { + Future reportTimeline({ + required String state, + required int positionMs, + required int durationMs, + }) async { await _tracker.report( client: _client, itemId: _channelKey, diff --git a/lib/services/track_manager.dart b/lib/services/track_manager.dart index 794594ef..c1bd219e 100644 --- a/lib/services/track_manager.dart +++ b/lib/services/track_manager.dart @@ -16,11 +16,7 @@ import '../utils/track_label_builder.dart'; /// stream indexes) or lack server-side stream selection leave this null. /// [trackType] is `'audio'` or `'subtitle'`. typedef TrackPreferencePersister = - Future Function({ - required int partId, - required String trackType, - int? streamID, - }); + Future Function({required int partId, required String trackType, int? streamID}); /// Manages track (audio + subtitle) lifecycle: external subtitle loading, /// automatic track selection, server preference sync, and cycling. diff --git a/lib/utils/failover_http_client.dart b/lib/utils/failover_http_client.dart index 44985222..3fcd837b 100644 --- a/lib/utils/failover_http_client.dart +++ b/lib/utils/failover_http_client.dart @@ -87,7 +87,13 @@ class FailoverHttpClient extends MediaServerHttpClient { final generation = _endpointManager?.generation; final MediaServerResponse response; try { - response = await super.get(path, queryParameters: queryParameters, headers: headers, timeout: timeout, abort: abort); + response = await super.get( + path, + queryParameters: queryParameters, + headers: headers, + timeout: timeout, + abort: abort, + ); } on MediaServerHttpException catch (e) { if (!allowEndpointFailover || !_shouldAttemptFailover(exception: e) || !_canFailover(generation)) { rethrow; @@ -102,7 +108,9 @@ class FailoverHttpClient extends MediaServerHttpClient { if (retried == null) rethrow; return retried; } - if (!allowEndpointFailover || !_shouldAttemptFailover(statusCode: response.statusCode) || !_canFailover(generation)) { + if (!allowEndpointFailover || + !_shouldAttemptFailover(statusCode: response.statusCode) || + !_canFailover(generation)) { return response; } return await _failoverOnce( diff --git a/lib/utils/json_utils.dart b/lib/utils/json_utils.dart index 077c5370..d3e0b1f3 100644 --- a/lib/utils/json_utils.dart +++ b/lib/utils/json_utils.dart @@ -56,7 +56,10 @@ List? flexibleList(Object? v) => switch (v) { List? flexibleStringList(Object? v) { final list = flexibleList(v); if (list == null) return null; - final result = [for (final e in list) if (e is String) e]; + final result = [ + for (final e in list) + if (e is String) e, + ]; return result.isEmpty ? null : result; } diff --git a/lib/utils/live_tv_player_navigation.dart b/lib/utils/live_tv_player_navigation.dart index b7ab6d88..12c92e99 100644 --- a/lib/utils/live_tv_player_navigation.dart +++ b/lib/utils/live_tv_player_navigation.dart @@ -68,11 +68,7 @@ Future navigateToLiveTv( settings: const RouteSettings(name: kVideoPlayerRouteName), pageBuilder: (context, animation, secondaryAnimation) => VideoPlayerScreen( metadata: placeholder, - live: LiveTvSessionArgs( - channel: channel, - channels: normalizedChannels, - currentChannelIndex: currentChannelIndex, - ), + live: LiveTvSessionArgs(channel: channel, channels: normalizedChannels, currentChannelIndex: currentChannelIndex), ), transitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero, diff --git a/lib/widgets/video_controls/parts/markers.dart b/lib/widgets/video_controls/parts/markers.dart index 8f6f5cd5..d110d430 100644 --- a/lib/widgets/video_controls/parts/markers.dart +++ b/lib/widgets/video_controls/parts/markers.dart @@ -202,12 +202,12 @@ extension _PlexVideoControlsMarkerMethods on _PlexVideoControlsState { } bool get _isSkipMarkerButtonVisible => shouldShowSkipMarkerButton( - hasFirstFrame: _hasRenderedFirstFrame, - hasMarker: _currentMarker != null, - hasPlayNextPrompt: widget.playNextFocusNode != null, - skipButtonDismissed: _skipButtonDismissed, - controlsVisible: _showControls, - ); + hasFirstFrame: _hasRenderedFirstFrame, + hasMarker: _currentMarker != null, + hasPlayNextPrompt: widget.playNextFocusNode != null, + skipButtonDismissed: _skipButtonDismissed, + controlsVisible: _showControls, + ); void _activateSkipMarker() { if (!_isSkipMarkerButtonVisible) return; diff --git a/lib/widgets/watched_indicator.dart b/lib/widgets/watched_indicator.dart index d1081e35..b1e45330 100644 --- a/lib/widgets/watched_indicator.dart +++ b/lib/widgets/watched_indicator.dart @@ -14,8 +14,24 @@ import 'unwatched_count_badge.dart'; /// [compact] for dense surfaces (folder tree rows, episode thumbnails). /// Add a preset here instead of hand-rolling a new overlay variant. enum WatchedIndicatorSize { - standard(checkInset: 4, checkPadding: 4, checkIconSize: 16, badgeSize: 24, badgeFontSize: 12, barRadius: 8, barMinHeight: 4), - compact(checkInset: 3, checkPadding: 2, checkIconSize: 12, badgeSize: 20, badgeFontSize: 10, barRadius: 6, barMinHeight: 3); + standard( + checkInset: 4, + checkPadding: 4, + checkIconSize: 16, + badgeSize: 24, + badgeFontSize: 12, + barRadius: 8, + barMinHeight: 4, + ), + compact( + checkInset: 3, + checkPadding: 2, + checkIconSize: 12, + badgeSize: 20, + badgeFontSize: 10, + barRadius: 6, + barMinHeight: 3, + ); const WatchedIndicatorSize({ required this.checkInset, diff --git a/test/providers/discover_provider_test.dart b/test/providers/discover_provider_test.dart index 27343e29..a414e4b4 100644 --- a/test/providers/discover_provider_test.dart +++ b/test/providers/discover_provider_test.dart @@ -28,17 +28,22 @@ MediaItem _item(String id, {String? parentId, String serverId = 'server_1'}) => parentId: parentId, ); -MediaHub _hub(String id, {String? identifier, String? libraryId, List? items, String serverId = 'server_1'}) => - MediaHub( - id: id, - title: id, - type: 'movie', - identifier: identifier, - items: items ?? [_item('$id-item', serverId: serverId)], - size: 1, - libraryId: libraryId, - serverId: serverId, - ); +MediaHub _hub( + String id, { + String? identifier, + String? libraryId, + List? items, + String serverId = 'server_1', +}) => MediaHub( + id: id, + title: id, + type: 'movie', + identifier: identifier, + items: items ?? [_item('$id-item', serverId: serverId)], + size: 1, + libraryId: libraryId, + serverId: serverId, +); /// Counting fake — the provider's fetch-cost policy is the contract under /// test: a watch event must cost exactly one on-deck call and zero hub @@ -80,10 +85,7 @@ class _FakeAggregationService extends DataAggregationService { }) async { hubCalls++; lastHubsServerIds = serverIds; - return ( - hubs: hubsResult(), - succeededServerIds: hubSucceededServerIds ?? serverIds ?? const {'server_1'}, - ); + return (hubs: hubsResult(), succeededServerIds: hubSucceededServerIds ?? serverIds ?? const {'server_1'}); } } @@ -215,16 +217,12 @@ void main() { }); test('library order change re-sorts hubs without any refetch', () async { - aggregation.hubsResult = () => [ - _hub('hub-lib2', libraryId: 'lib-2'), - _hub('hub-lib1', libraryId: 'lib-1'), - ]; + aggregation.hubsResult = () => [_hub('hub-lib2', libraryId: 'lib-2'), _hub('hub-lib1', libraryId: 'lib-1')]; await provider.load(); expect(provider.hubs.map((h) => h.id), ['hub-lib2', 'hub-lib1']); final hubCallsBefore = aggregation.hubCalls; - MediaLibrary lib(String id) => - MediaLibrary(id: id, backend: MediaBackend.plex, title: id, serverId: 'server_1'); + MediaLibrary lib(String id) => MediaLibrary(id: id, backend: MediaBackend.plex, title: id, serverId: 'server_1'); await libraries.updateLibraryOrder([lib('lib-1'), lib('lib-2')]); await pumpEventQueue(); diff --git a/test/providers/offline_watch_provider_test.dart b/test/providers/offline_watch_provider_test.dart index 8d62767a..b80ee248 100644 --- a/test/providers/offline_watch_provider_test.dart +++ b/test/providers/offline_watch_provider_test.dart @@ -25,7 +25,11 @@ void main() { serverManager = MultiServerManager(); syncService = OfflineWatchSyncService(database: db, serverManager: serverManager); - downloadManager = DownloadManagerService(database: db, storageService: DownloadStorageService.instance, clientResolver: (serverId, {clientScopeId}) => null); + downloadManager = DownloadManagerService( + database: db, + storageService: DownloadStorageService.instance, + clientResolver: (serverId, {clientScopeId}) => null, + ); downloadManager.recoveryFuture = Future.value(); downloadProvider = DownloadProvider.forTesting(downloadManager: downloadManager, database: db); await downloadProvider.ensureInitialized(); diff --git a/test/screens/downloads/downloads_screen_focus_test.dart b/test/screens/downloads/downloads_screen_focus_test.dart index 546a52e2..1d1e7ffe 100644 --- a/test/screens/downloads/downloads_screen_focus_test.dart +++ b/test/screens/downloads/downloads_screen_focus_test.dart @@ -44,7 +44,11 @@ void main() { PlexApiCache.initialize(db); JellyfinApiCache.initialize(db); - final downloadManager = DownloadManagerService(database: db, storageService: DownloadStorageService.instance, clientResolver: (serverId, {clientScopeId}) => null); + final downloadManager = DownloadManagerService( + database: db, + storageService: DownloadStorageService.instance, + clientResolver: (serverId, {clientScopeId}) => null, + ); downloadProvider = DownloadProvider.forTesting(downloadManager: downloadManager, database: db); await downloadProvider.ensureInitialized(); diff --git a/test/screens/downloads/sync_rules_screen_test.dart b/test/screens/downloads/sync_rules_screen_test.dart index b9d06acc..f73c96bb 100644 --- a/test/screens/downloads/sync_rules_screen_test.dart +++ b/test/screens/downloads/sync_rules_screen_test.dart @@ -103,7 +103,11 @@ void main() { db = AppDatabase.forTesting(NativeDatabase.memory()); PlexApiCache.initialize(db); JellyfinApiCache.initialize(db); - downloadManager = DownloadManagerService(database: db, storageService: DownloadStorageService.instance, clientResolver: (serverId, {clientScopeId}) => null); + downloadManager = DownloadManagerService( + database: db, + storageService: DownloadStorageService.instance, + clientResolver: (serverId, {clientScopeId}) => null, + ); downloadProvider = DownloadProvider.forTesting(downloadManager: downloadManager, database: db); await downloadProvider.ensureInitialized(); serverManager = MultiServerManager(); diff --git a/test/screens/playlist_detail_screen_test.dart b/test/screens/playlist_detail_screen_test.dart index db59fc47..cae18da9 100644 --- a/test/screens/playlist_detail_screen_test.dart +++ b/test/screens/playlist_detail_screen_test.dart @@ -159,7 +159,11 @@ Future<_PlaylistHarness> _createHarness(List items) async { PlexApiCache.initialize(db); JellyfinApiCache.initialize(db); - final downloadManager = DownloadManagerService(database: db, storageService: DownloadStorageService.instance, clientResolver: (serverId, {clientScopeId}) => null); + final downloadManager = DownloadManagerService( + database: db, + storageService: DownloadStorageService.instance, + clientResolver: (serverId, {clientScopeId}) => null, + ); downloadManager.recoveryFuture = Future.value(); final downloadProvider = DownloadProvider.forTesting(downloadManager: downloadManager, database: db); await downloadProvider.ensureInitialized(); diff --git a/test/services/api_cache_watch_state_test.dart b/test/services/api_cache_watch_state_test.dart index 81368ded..1a1b151d 100644 --- a/test/services/api_cache_watch_state_test.dart +++ b/test/services/api_cache_watch_state_test.dart @@ -1,4 +1,3 @@ - import 'package:drift/drift.dart' show Value; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/services/track_manager_test.dart b/test/services/track_manager_test.dart index 7b3a2250..cf413479 100644 --- a/test/services/track_manager_test.dart +++ b/test/services/track_manager_test.dart @@ -47,8 +47,11 @@ MediaItem _meta({String id = 'rk1'}) => MediaItem(id: id, backend: MediaBackend. /// Player that records calls and can be configured per-test. class _FakePlayer with PlayerStreamControllersMixin implements Player { PlayerState _state; - _FakePlayer({Tracks tracks = const Tracks(), TrackSelection track = const TrackSelection(), this.attachesExternalSubtitlesAtOpen = false}) - : _state = PlayerState(tracks: tracks, track: track); + _FakePlayer({ + Tracks tracks = const Tracks(), + TrackSelection track = const TrackSelection(), + this.attachesExternalSubtitlesAtOpen = false, + }) : _state = PlayerState(tracks: tracks, track: track); @override PlayerState get state => _state; @@ -126,13 +129,7 @@ MediaSourceInfo _mediaInfoWithSubtitles({bool selected = false}) { videoUrl: 'https://example.com/video.mp4', audioTracks: [MediaAudioTrack(id: 1, language: 'English', languageCode: 'eng', selected: true)], subtitleTracks: [ - MediaSubtitleTrack( - id: 10, - language: 'English', - languageCode: 'eng', - selected: selected, - forced: false, - ), + MediaSubtitleTrack(id: 10, language: 'English', languageCode: 'eng', selected: selected, forced: false), ], chapters: const [], ); @@ -352,7 +349,9 @@ void main() { test('waits for player subtitle tracks when Plex metadata advertises subtitles', () async { await SettingsService.getInstance(); final player = _FakePlayer( - tracks: const Tracks(audio: [AudioTrack(id: '1', language: 'eng')]), + tracks: const Tracks( + audio: [AudioTrack(id: '1', language: 'eng')], + ), ); final mgr = _make(player: player, mediaInfo: _mediaInfoWithSubtitles()); addTearDown(mgr.dispose); @@ -485,9 +484,7 @@ void main() { final mgr = _make(player: player); addTearDown(mgr.dispose); - mgr.cacheExternalSubtitles([ - SubtitleTrack.uri('https://example/fallback.srt', title: 'EN'), - ]); + mgr.cacheExternalSubtitles([SubtitleTrack.uri('https://example/fallback.srt', title: 'EN')]); await mgr.onBackendSwitched(); @@ -499,9 +496,7 @@ void main() { final mgr = _make(player: player); addTearDown(mgr.dispose); - mgr.cacheExternalSubtitles([ - SubtitleTrack.uri('https://example/fallback.srt', title: 'EN'), - ]); + mgr.cacheExternalSubtitles([SubtitleTrack.uri('https://example/fallback.srt', title: 'EN')]); await mgr.onBackendSwitched(); diff --git a/test/utils/failover_http_client_test.dart b/test/utils/failover_http_client_test.dart index b4d5d393..5f3d7bf4 100644 --- a/test/utils/failover_http_client_test.dart +++ b/test/utils/failover_http_client_test.dart @@ -18,12 +18,7 @@ void main() { http.Response ok([String id = 'ok']) => http.Response(jsonEncode({'id': id}), 200, headers: {'content-type': 'application/json'}); - ({ - FailoverHttpClient client, - List<({String url, bool persist})> switches, - List exhausted, - List requests, - }) + ({FailoverHttpClient client, List<({String url, bool persist})> switches, List exhausted, List requests}) build({ required Future Function(http.Request request, List seen) handler, List endpoints = const [primary, fallback],