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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user