refactor(screens): delete never-passed knobs, dead enum values, and the unconsumed context-menu onTap

plex_pin_auth_flow's QR knobs, ProfileNameField's navigation params, MusicDetailHeader.wideArtworkSize, and _buildReorderableList's bool were never passed; DownloadType.manage was never constructed; MediaContextMenu.onTap was declared but never invoked — removed with its four card passes (each card's own gesture handlers stay); the pin dialog's obscure chain was always true and _PinKey.label's icon branches unreachable; libraries_screen._isInitialLoad was write-only; LiveTvActionsMixin loses dead findChannel (the generic parameter stays — required by the on-clause, the report was wrong); add_jellyfin's focus-map removal branch ran only against an empty map; the recommended tab's hub-key null fallback was dead behind _ensureHubKeys. media_card also loses its always-false seasonal-rank comparison from the CatalogRankScope removal.
This commit is contained in:
edde746
2026-08-17 19:02:24 +02:00
parent ceab5a5b59
commit b38df008ec
15 changed files with 22 additions and 91 deletions
+2 -20
View File
@@ -42,14 +42,6 @@ class PlexPinAuthFlow extends StatefulWidget {
/// for a [PlexAccountConnection] (account label + servers list).
final Future<void> Function(String token) onTokenReceived;
/// QR size on mobile / narrow layouts.
final double mobileQrSize;
/// QR size on desktop / wide layouts (where the auth screen has more
/// horizontal room). The two-column login screen uses 300; the bottom-sheet
/// add-account screen uses 200.
final double desktopQrSize;
/// When `true` and running on TV, auto-start the QR flow on first build so
/// the user doesn't have to navigate to the QR button with the remote.
final bool autoStartQrOnTV;
@@ -57,11 +49,6 @@ class PlexPinAuthFlow extends StatefulWidget {
/// Test seam for rendering the initial actions without platform services.
final bool initializeService;
/// Override the QR-vs-browser default before any user interaction. Useful
/// for callers that want to force one mode (the add-account screen
/// auto-starts QR on TV; the legacy login screen offers both).
final bool? initialUseQr;
/// Optional builder for the initial action buttons. The default (`null`)
/// shows two buttons — "Sign in with Plex" (browser) and "Show QR Code".
/// Pass a custom builder when the parent wants to integrate the buttons
@@ -76,11 +63,8 @@ class PlexPinAuthFlow extends StatefulWidget {
const PlexPinAuthFlow({
super.key,
required this.onTokenReceived,
this.mobileQrSize = 200,
this.desktopQrSize = 300,
this.autoStartQrOnTV = true,
this.initializeService = true,
this.initialUseQr,
this.initialButtonsBuilder,
this.serviceFactory,
});
@@ -100,9 +84,7 @@ class _PlexPinAuthFlowState extends State<PlexPinAuthFlow> {
@override
void initState() {
super.initState();
_useQr =
widget.initialUseQr ??
(PlatformDetector.isTV() || plexSignInRequiresInAppQr(isAutomotive: PlatformDetector.isAutomotive()));
_useQr = PlatformDetector.isTV() || plexSignInRequiresInAppQr(isAutomotive: PlatformDetector.isAutomotive());
if (widget.initializeService) unawaited(_initService());
}
@@ -231,7 +213,7 @@ class _PlexPinAuthFlowState extends State<PlexPinAuthFlow> {
if (_isPolling) {
final isDesktop = MediaQuery.sizeOf(context).width > 700;
if (_useQr && _qrAuthUrl != null) {
return _buildQr(theme, isDesktop ? widget.desktopQrSize : widget.mobileQrSize);
return _buildQr(theme, isDesktop ? 300 : 200);
}
return _buildBrowserWaiting(theme);
}
+2 -3
View File
@@ -255,7 +255,7 @@ class DownloadsScreenState extends State<DownloadsScreen>
}
}
enum DownloadType { manage, tvShows, movies }
enum DownloadType { tvShows, movies }
/// Grid content for TV Shows and Movies tabs
class _DownloadsGridContent extends StatefulWidget {
@@ -388,12 +388,11 @@ class _DownloadedMusicContentState extends State<_DownloadedMusicContent>
bool suppressAutoFocusOf(_DownloadedMusicContent widget) => widget.suppressAutoFocus;
Future<void> _playAlbumFrom(List<MediaItem> albumTracks, MediaItem track) async {
final album = track.parentId;
await playTracks(
context,
tracks: albumTracks,
startTrack: track,
playContext: MusicPlayContext(id: album, title: track.albumTitle ?? '', kind: MusicPlayContextKind.album),
playContext: MusicPlayContext(title: track.albumTitle ?? '', kind: MusicPlayContextKind.album),
);
}
@@ -59,7 +59,6 @@ class _LibrariesScreenState extends State<LibrariesScreen>
String? _errorMessage;
String? _selectedLibraryGlobalKey;
bool _isInitialLoad = true;
/// Flag to prevent onTabChanged from focusing when we're programmatically changing tabs
bool _isRestoringTab = false;
@@ -441,11 +440,6 @@ class _LibrariesScreenState extends State<LibrariesScreen>
});
}
// Mark that initial load is complete
if (_isInitialLoad) {
_isInitialLoad = false;
}
// Save selected library key and restore saved tab (async — safe after state is consistent)
final storage = await StorageService.getInstance();
if (!mounted || _selectedLibraryGlobalKey != libraryGlobalKey) return;
@@ -289,7 +289,7 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<MediaHub, LibraryR
final usesContinueWatchingAction = _usesContinueWatchingAction(hub);
return HubSection(
key: index < _hubKeys.length ? _hubKeys[index] : null,
key: _hubKeys[index],
hub: hub,
focusMemory: _hubFocusMemory,
icon: hubIconFor(hub),
@@ -13,19 +13,11 @@ import 'program_details_sheet.dart';
/// Shared live-TV actions: channel lookup, tuning, and program-details sheet.
///
/// Implementers expose their channel list via [liveTvChannels] and invoke
/// [findChannel], [tuneChannel], and [showProgramDetails] as needed.
/// [findChannelForProgram], [tuneChannel], and [showProgramDetails] as needed.
mixin LiveTvActionsMixin<T extends StatefulWidget> on State<T> {
/// Channel list used for lookups and passed into the playback navigator.
List<LiveTvChannel> get liveTvChannels;
/// Look up a channel by identifier or key. Returns null if no match.
LiveTvChannel? findChannel(String? channelIdentifier) {
if (channelIdentifier == null) return null;
return liveTvChannels.where((ch) {
return ch.identifier == channelIdentifier || ch.key == channelIdentifier;
}).firstOrNull;
}
LiveTvChannel? findChannelForProgram(LiveTvProgram program) {
return liveTvChannels.where((channel) => liveTvProgramMatchesChannel(program, channel)).firstOrNull;
}
@@ -693,7 +693,7 @@ class _PlaylistDetailScreenState extends BaseMediaListDetailScreen<PlaylistDetai
buildFocusableGrid(items: items, onRefresh: updateItem, shape: _isAudioPlaylist ? CardShape.square : null)
else
// Plex regular playlists: sliver reorderable list
_buildReorderableList(isKeyboardMode),
_buildReorderableList(),
if (_continuation.isLoading || _continuation.error != null)
ContinuationStatusSliver(
error: _continuation.error,
@@ -743,7 +743,7 @@ class _PlaylistDetailScreenState extends BaseMediaListDetailScreen<PlaylistDetai
}
/// Build a reorderable list for regular playlists with focus support
Widget _buildReorderableList(bool _) {
Widget _buildReorderableList() {
return SliverReorderableList(
onReorderItem: _onReorder,
itemCount: items.length,
@@ -76,7 +76,6 @@ class _PlaylistItemCardState extends State<PlaylistItemCard> with ContextMenuTap
key: contextMenuKey,
item: item,
onRefresh: widget.onRefresh,
onTap: widget.onTap,
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: cardColor,
+8 -17
View File
@@ -484,7 +484,7 @@ class _TvPinInputState extends State<_TvPinInput> with ControllerDisposerMixin {
Widget _buildKeypadLayout(BuildContext context) {
return Column(
mainAxisSize: .min,
children: [_buildDigitRow(context, obscureDigits: true), const SizedBox(height: 18), _buildKeypad(context)],
children: [_buildDigitRow(context), const SizedBox(height: 18), _buildKeypad(context)],
);
}
@@ -553,7 +553,7 @@ class _TvPinInputState extends State<_TvPinInput> with ControllerDisposerMixin {
);
}
Widget _buildDigitRow(BuildContext _, {bool obscureDigits = false}) {
Widget _buildDigitRow(BuildContext _) {
return Row(
mainAxisSize: .min,
mainAxisAlignment: .center,
@@ -563,7 +563,6 @@ class _TvPinInputState extends State<_TvPinInput> with ControllerDisposerMixin {
_DigitBox(
digit: _digits[i],
isActive: (widget.isMobile ? _mobileFocusNode.hasFocus : true) && _activeIndex == i,
obscureDigit: obscureDigits,
),
],
],
@@ -602,7 +601,7 @@ class _TvPinInputState extends State<_TvPinInput> with ControllerDisposerMixin {
),
),
),
_buildDigitRow(context, obscureDigits: true),
_buildDigitRow(context),
],
),
);
@@ -612,9 +611,8 @@ class _TvPinInputState extends State<_TvPinInput> with ControllerDisposerMixin {
class _DigitBox extends StatelessWidget {
final int? digit;
final bool isActive;
final bool obscureDigit;
const _DigitBox({required this.digit, required this.isActive, this.obscureDigit = false});
const _DigitBox({required this.digit, required this.isActive});
@override
Widget build(BuildContext context) {
@@ -640,7 +638,7 @@ class _DigitBox extends StatelessWidget {
color: isActive ? focusColor.withValues(alpha: 0.08) : Colors.transparent,
),
child: Text(
digit != null ? (obscureDigit || !isActive ? '•' : digit.toString()) : '–',
digit != null ? '•' : '–',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: .bold,
color: digit != null
@@ -664,16 +662,9 @@ class _PinKey {
const _PinKey.backspace() : type = _PinKeyType.backspace, digit = null;
const _PinKey.close() : type = _PinKeyType.close, digit = null;
String get label {
switch (type) {
case _PinKeyType.digit:
return digit.toString();
case _PinKeyType.backspace:
return t.common.delete;
case _PinKeyType.close:
return t.common.cancel;
}
}
/// Only evaluated for digit keys — icon keys short-circuit on [icon] in
/// [_TvPinInputState._buildKeyContent].
String get label => digit.toString();
IconData? get icon {
switch (type) {
+1 -9
View File
@@ -14,10 +14,7 @@ class ProfileNameField extends StatelessWidget {
this.hintText,
this.trailing,
this.onChanged,
this.autofocus = false,
this.onNavigateUp,
this.onNavigateDown,
this.onNavigateLeft,
this.onNavigateRight,
});
@@ -26,10 +23,7 @@ class ProfileNameField extends StatelessWidget {
final String? hintText;
final Widget? trailing;
final VoidCallback? onChanged;
final bool autofocus;
final VoidCallback? onNavigateUp;
final VoidCallback? onNavigateDown;
final VoidCallback? onNavigateLeft;
final VoidCallback? onNavigateRight;
@override
@@ -37,13 +31,11 @@ class ProfileNameField extends StatelessWidget {
final field = FocusableTextField(
controller: controller,
focusNode: focusNode,
autofocus: autofocus,
textInputAction: TextInputAction.done,
decoration: InputDecoration(hintText: hintText),
onChanged: (_) => onChanged?.call(),
onNavigateUp: onNavigateUp ?? () => FocusScope.of(context).previousFocus(),
onNavigateUp: () => FocusScope.of(context).previousFocus(),
onNavigateDown: onNavigateDown ?? () => FocusScope.of(context).nextFocus(),
onNavigateLeft: onNavigateLeft,
onNavigateRight: onNavigateRight,
);
if (trailing == null) return field;
@@ -178,11 +178,6 @@ class _AddJellyfinScreenState extends State<AddJellyfinScreen> with AsyncFormSta
}
void _syncDiscoveredServerFocusNodes(List<DiscoveredJellyfinServer> servers) {
final ids = servers.map((server) => server.id).toSet();
final removed = _discoveredServerFocusNodes.keys.where((id) => !ids.contains(id)).toList(growable: false);
for (final id in removed) {
_discoveredServerFocusNodes.remove(id)?.dispose();
}
for (final server in servers) {
_discoveredServerFocusNodes.putIfAbsent(
server.id,
-1
View File
@@ -146,7 +146,6 @@ class _EpisodeCardState extends State<EpisodeCard> with ContextMenuTapMixin<Epis
item: episode,
onRefresh: widget.onRefresh,
onListRefresh: widget.onListRefresh,
onTap: widget.onTap,
child: InkWell(
key: Key(episode.id),
mouseCursor: SystemMouseCursors.click,
+3 -4
View File
@@ -465,7 +465,6 @@ class MediaCardState extends State<MediaCard> with ContextMenuTapMixin<MediaCard
onRefresh: widget.onRefresh,
onRemoveFromContinueWatching: widget.onRemoveFromContinueWatching,
onListRefresh: widget.onListRefresh,
onTap: () => _handleTap(context, item),
isInContinueWatching: widget.isInContinueWatching,
collectionId: widget.collectionId,
child: cardWidget,
@@ -1249,7 +1248,7 @@ Color? _parseCatalogAccent(String? value) {
String? _catalogRankBadge(CatalogItem item) {
String? allTimeLabel;
for (final rank in item.ranks ?? const <CatalogRank>[]) {
final contextual = !rank.allTime || rank.scope == CatalogRankScope.seasonal;
final contextual = !rank.allTime;
if (contextual) {
final season = rank.season;
final year = rank.year;
@@ -1262,8 +1261,8 @@ String? _catalogRankBadge(CatalogItem item) {
return t.explore.badge.rankSeasonal(n: rank.rank, season: window);
}
// Not contextual means an all-time rank with a non-seasonal scope, so
// rankLabel takes its all-time scope-switch path.
// Not contextual means an all-time rank, so rankLabel takes its
// scope-switch path.
allTimeLabel ??= rankLabel(rank);
}
return allTimeLabel;
-3
View File
@@ -154,7 +154,6 @@ class MediaContextMenu extends StatefulWidget {
final void Function(MediaItem source)? onRefresh;
final VoidCallback? onRemoveFromContinueWatching;
final VoidCallback? onListRefresh;
final VoidCallback? onTap;
/// Plays the item's trailer. When non-null a "Play trailer" item is added to
/// the menu. Only the detail screen passes this (it resolves the trailer from
@@ -175,7 +174,6 @@ class MediaContextMenu extends StatefulWidget {
this.onRefresh,
this.onRemoveFromContinueWatching,
this.onListRefresh,
this.onTap,
this.onPlayTrailer,
required this.child,
this.isInContinueWatching = false,
@@ -1254,7 +1252,6 @@ class MediaContextMenuState extends State<MediaContextMenu> {
context,
fetch: () => _musicTracksForItem(item),
playContext: MusicPlayContext(
id: item.id,
title: item.displayTitle,
kind: item.kind == MediaKind.artist ? MusicPlayContextKind.artist : MusicPlayContextKind.album,
),
+1 -3
View File
@@ -16,7 +16,6 @@ class MusicDetailHeader extends StatelessWidget {
required this.compactArtworkSize,
required this.compactArtworkSpacing,
this.compactBottomSpacing = 0,
this.wideArtworkSize = 180,
this.wideAlignment = CrossAxisAlignment.center,
});
@@ -26,7 +25,6 @@ class MusicDetailHeader extends StatelessWidget {
final double compactArtworkSize;
final double compactArtworkSpacing;
final double compactBottomSpacing;
final double wideArtworkSize;
final CrossAxisAlignment wideAlignment;
@override
@@ -51,7 +49,7 @@ class MusicDetailHeader extends StatelessWidget {
return Row(
crossAxisAlignment: wideAlignment,
children: [
artworkBuilder(wideArtworkSize),
artworkBuilder(180),
const SizedBox(width: 24),
Expanded(
child: Column(
+1 -7
View File
@@ -291,13 +291,7 @@ class _TrackRowState extends State<TrackRow> with ContextMenuTapMixin<TrackRow>,
);
if (!withContextMenu) return row;
return MediaContextMenu(
key: contextMenuKey,
item: widget.item,
onRefresh: widget.onRefresh,
onTap: widget.onTap,
child: row,
);
return MediaContextMenu(key: contextMenuKey, item: widget.item, onRefresh: widget.onRefresh, child: row);
}
}