chore: clean up code comments

This commit is contained in:
edde746
2026-08-10 20:28:41 +02:00
parent 5611c6785a
commit 69fadc220d
170 changed files with 324 additions and 1765 deletions
-1
View File
@@ -68,7 +68,6 @@ class BottomSheetHeader extends StatelessWidget {
Widget build(BuildContext context) {
final usesBackButton = leading == null && onBack != null;
// Determine the leading widget based on priority: leading > onBack > icon
Widget? resolvedLeading;
if (leading != null) {
resolvedLeading = leading;
-33
View File
@@ -92,7 +92,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
@override
void didUpdateWidget(DownloadTreeView oldWidget) {
super.didUpdateWidget(oldWidget);
// When suppressAutoFocus changes from true to false, focus the first item
if (oldWidget.suppressAutoFocus && !widget.suppressAutoFocus) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _firstItemFocusNode.canRequestFocus) {
@@ -121,13 +120,11 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
);
}
/// Build the download tree from flat download list
List<DownloadTreeNode> _buildTree() {
final Map<String, List<MapEntry<String, DownloadProgress>>> showGroups = {};
final Map<String, List<MapEntry<String, DownloadProgress>>> albumGroups = {};
final List<DownloadTreeNode> movies = [];
// Group downloads
for (final entry in widget.downloads.entries) {
final globalKey = entry.key;
final download = entry.value;
@@ -136,17 +133,14 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
if (meta == null) continue;
if (meta.isEpisode) {
// Group episodes by show
final showKey = meta.grandparentId ?? 'unknown';
showGroups.putIfAbsent(showKey, () => []);
showGroups[showKey]!.add(entry);
} else if (meta.kind == MediaKind.track) {
// Group tracks by album (single level — no per-disc tier)
final albumKey = meta.parentId ?? 'unknown';
albumGroups.putIfAbsent(albumKey, () => []);
albumGroups[albumKey]!.add(entry);
} else if (meta.isMovie) {
// Movies go at top level
movies.add(
DownloadTreeNode(
key: globalKey,
@@ -161,7 +155,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
}
}
// Build show nodes
final List<DownloadTreeNode> shows = [];
for (final showEntry in showGroups.entries) {
final showKey = showEntry.key;
@@ -169,11 +162,9 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
if (episodes.isEmpty) continue;
// Get show metadata from first episode
final firstEpisode = widget.metadata[episodes.first.key];
final showTitle = firstEpisode?.grandparentTitle ?? t.downloads.unknownShow;
// Group episodes by season
final Map<String, List<MapEntry<String, DownloadProgress>>> seasonGroups = {};
for (final episode in episodes) {
final meta = widget.metadata[episode.key];
@@ -184,7 +175,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
seasonGroups[seasonKey]!.add(episode);
}
// Build season nodes
final List<DownloadTreeNode> seasons = [];
for (final seasonEntry in seasonGroups.entries) {
final seasonKey = seasonEntry.key;
@@ -192,7 +182,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
if (seasonEpisodes.isEmpty) continue;
// Get season metadata from first episode
final firstEpisode = widget.metadata[seasonEpisodes.first.key];
final seasonNumber = firstEpisode?.parentIndex;
final seasonTitle = firstEpisode?.parentTitle?.isNotEmpty == true
@@ -201,7 +190,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
? t.common.seasonNumber(number: seasonNumber)
: t.downloads.unknownSeason;
// Build episode nodes
final List<DownloadTreeNode> episodeNodes = [];
for (final episodeEntry in seasonEpisodes) {
final globalKey = episodeEntry.key;
@@ -228,14 +216,12 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
);
}
// Sort episodes by episode number only (not by status)
episodeNodes.sort((a, b) {
final aIndex = a.metadata?.index ?? 0;
final bIndex = b.metadata?.index ?? 0;
return aIndex.compareTo(bIndex);
});
// Calculate aggregate season progress
final seasonProgress = episodeNodes.isEmpty
? 0.0
: episodeNodes.map((e) => e.progress).reduce((a, b) => a + b) / episodeNodes.length;
@@ -255,14 +241,12 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
seasons.removeWhere((s) => s.children.isEmpty);
// Sort seasons by season number
seasons.sort((a, b) {
final aSeasonNum = widget.metadata[a.children.first.key]?.parentIndex ?? 0;
final bSeasonNum = widget.metadata[b.children.first.key]?.parentIndex ?? 0;
return aSeasonNum.compareTo(bSeasonNum);
});
// Calculate aggregate show progress
final showProgress = seasons.isEmpty
? 0.0
: seasons.map((s) => s.progress).reduce((a, b) => a + b) / seasons.length;
@@ -280,14 +264,12 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
);
}
// Build album nodes (album -> tracks)
final List<DownloadTreeNode> albums = [];
for (final albumEntry in albumGroups.entries) {
final albumKey = albumEntry.key;
final tracks = albumEntry.value;
if (tracks.isEmpty) continue;
// Album/artist names from any track's parent fields
final firstTrack = widget.metadata[tracks.first.key];
final albumTitle = firstTrack?.albumTitle ?? t.downloads.unknownAlbum;
final artistTitle = firstTrack?.albumArtistTitle;
@@ -314,7 +296,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
}
if (trackNodes.isEmpty) continue;
// Sort tracks by disc then track number
trackNodes.sort((a, b) {
final byDisc = (a.metadata?.discNumber ?? 1).compareTo(b.metadata?.discNumber ?? 1);
if (byDisc != 0) return byDisc;
@@ -336,17 +317,13 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
);
}
// Sort shows, albums, and movies by status and title
_sortNodesByStatusAndTitle(shows);
_sortNodesByStatusAndTitle(albums);
_sortNodesByStatusAndTitle(movies);
// Combine movies, shows, and albums
return [...movies, ...shows, ...albums];
}
/// Determine aggregate status from child statuses
/// Priority: downloading > queued > paused > completed > failed
DownloadStatus _determineAggregateStatus(List<DownloadStatus> statuses) {
if (statuses.isEmpty) return DownloadStatus.queued;
@@ -365,7 +342,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
return DownloadStatus.completed;
}
/// Compare statuses for sorting (downloading first, then queued, etc.)
int _compareByStatus(DownloadStatus a, DownloadStatus b) {
const statusOrder = {
DownloadStatus.downloading: 0,
@@ -378,7 +354,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
return (statusOrder[a] ?? 99).compareTo(statusOrder[b] ?? 99);
}
/// Sort nodes by status (downloading first) then by title
void _sortNodesByStatusAndTitle(List<DownloadTreeNode> nodes) {
nodes.sort((a, b) {
final statusCompare = _compareByStatus(a.status, b.status);
@@ -414,7 +389,6 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
});
}
/// Build a tree item widget
Widget _buildTreeItem(DownloadTreeNode node, int depth, {bool isFirst = false}) {
return _DownloadTreeItem(
node: node,
@@ -590,9 +564,7 @@ class _DownloadTreeItemState extends State<_DownloadTreeItem> {
return widget.node.status;
}
// Focus node for row content (only created if not provided externally)
FocusNode? _ownedRowFocusNode;
// Focus nodes for action buttons (up to 3 buttons max)
final List<FocusNode> _buttonFocusNodes = [];
FocusNode get _rowFocusNode => widget.rowFocusNode ?? _ownedRowFocusNode!;
@@ -676,10 +648,8 @@ class _DownloadTreeItemState extends State<_DownloadTreeItem> {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
// Row content
Expanded(child: _buildRowContent(theme, canExpand)),
// Action buttons
if (actions.isNotEmpty)
Row(
mainAxisSize: .min,
@@ -696,7 +666,6 @@ class _DownloadTreeItemState extends State<_DownloadTreeItem> {
Widget _buildRowContent(ThemeData theme, bool canExpand) {
return Row(
children: [
// Expand/collapse icon
if (canExpand)
AppIcon(widget.isExpanded ? Symbols.expand_more_rounded : Symbols.chevron_right_rounded, fill: 1, size: 20)
else
@@ -704,12 +673,10 @@ class _DownloadTreeItemState extends State<_DownloadTreeItem> {
const SizedBox(width: 8),
// Status icon
DownloadStatusIcon(status: _effectiveStatus, size: 20),
const SizedBox(width: 12),
// Title and info
Expanded(
child: Column(
crossAxisAlignment: .start,
-1
View File
@@ -275,7 +275,6 @@ class HubSectionState extends State<HubSection> with MountedSetStateMixin, Skele
}
}
// Handle key down and repeat events
if (!event.isActionable) {
return KeyEventResult.ignored;
}
@@ -289,7 +289,6 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet>
final ScrollController _dialogScrollController = ScrollController();
final ScrollController _sheetScrollController = ScrollController();
// Keyboard navigation: column 0 = row, 1 = visibility button, 2 = options button.
@override
List<MediaLibrary> get reorderItems => _tempLibraries;
@@ -468,16 +467,13 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet>
final isHidden = hiddenLibraryKeys.contains(library.globalKey);
final colorScheme = Theme.of(context).colorScheme;
// Determine background color based on state
Color? tileColor;
if (isMoving) {
tileColor = colorScheme.primaryContainer;
} else if (isFocused && focusedColumn == 0) {
// Only highlight row when row itself is focused (column 0)
tileColor = colorScheme.surfaceContainerHighest;
}
// Button focus states
final isVisibilityButtonFocused = isFocused && focusedColumn == 1;
final isOptionsButtonFocused = isFocused && focusedColumn == 2;
-8
View File
@@ -615,13 +615,11 @@ class MediaCardState extends State<MediaCard> with ContextMenuTapMixin<MediaCard
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
// Poster with overlay
if (posterHeight != null)
SizedBox(width: double.infinity, height: posterHeight, child: poster)
else
Expanded(child: poster),
const SizedBox(height: 2),
// Title (flattened — no inner Column)
if (widget.onTap == null && item is MediaItem && _hasClickableTitle(item))
_ClickableText(
text: item.displayTitle,
@@ -637,7 +635,6 @@ class MediaCardState extends State<MediaCard> with ContextMenuTapMixin<MediaCard
style: const TextStyle(fontWeight: .w600, fontSize: 13, height: 1.1),
),
),
// Subtitle
if (item is MediaPlaylist)
_MediaCardHelpers.buildPlaylistMeta(context, item)
else if (item is MediaItem)
@@ -1131,7 +1128,6 @@ class _MediaCardHelpers {
}
}
// For collections, show item count
if (mi.kind == MediaKind.collection) {
final count = mi.childCount ?? mi.leafCount;
if (count != null && count > 0) {
@@ -1146,14 +1142,12 @@ class _MediaCardHelpers {
}
}
// For albums, show the album artist
if (mi.kind == MediaKind.album && mi.albumArtistTitle != null) {
return ExcludeSemantics(
child: Text(mi.albumArtistTitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle),
);
}
// For tracks, show "Artist • duration"
if (mi.kind == MediaKind.track) {
final parts = [?mi.trackArtistTitle, if (mi.durationMs case final durationMs?) formatDurationTextual(durationMs)];
if (parts.isNotEmpty) {
@@ -1163,7 +1157,6 @@ class _MediaCardHelpers {
}
}
// For episodes, show "S# · Episode Title" with clickable season link
if (mi.isEpisode && mi.parentIndex != null) {
if (enableDetailLinks && mi.parentId != null) {
return _buildEpisodeSubtitleRow(
@@ -1185,7 +1178,6 @@ class _MediaCardHelpers {
);
}
// For other media types, show subtitle/parent/year
if (mi.displaySubtitle != null) {
return ExcludeSemantics(
child: Text(mi.displaySubtitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle),
+1 -1
View File
@@ -130,7 +130,7 @@ class MediaContextMenu extends StatefulWidget {
final Object item;
final void Function(MediaItem source)? onRefresh;
final VoidCallback? onRemoveFromContinueWatching;
final VoidCallback? onListRefresh; // For refreshing list after deletion
final VoidCallback? onListRefresh;
final VoidCallback? onTap;
/// Plays the item's trailer. When non-null a "Play trailer" item is added to
@@ -173,7 +173,6 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
bool get _canControl => _trackControlsState.canControl;
bool get _isLive => _trackControlsState.isLive;
// Focus nodes for playback control buttons
late final FocusNode _prevItemFocusNode;
late final FocusNode _prevChapterFocusNode;
late final FocusNode _skipBackFocusNode;
@@ -184,30 +183,23 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
late final FocusNode _goToLiveFocusNode;
late final FocusNode _timelineFocusNode;
// Focus node for volume control
late final FocusNode _volumeFocusNode;
// Focus nodes for track/chapter controls (max 8 buttons possible)
late final List<FocusNode> _trackControlFocusNodes;
// List of button focus nodes for horizontal navigation
late final List<FocusNode> _buttonFocusNodes;
// Progressive seek acceleration state
LogicalKeyboardKey? _seekDirection; // Current direction being held
int _seekRepeatCount = 0; // Consecutive key repeats for acceleration
// Preview thumbnail during sustained dpad/keyboard seeking
bool _showKeyRepeatThumbnail = false;
Timer? _keyRepeatThumbnailTimer;
late final DebouncedSeekAccumulator _timelineSeek;
static const _keyRepeatThumbnailTimeout = Duration(milliseconds: 400);
// Content strip state
bool _contentStripVisible = false;
final GlobalKey<ContentStripState> _contentStripKey = GlobalKey<ContentStripState>();
// Track which button was last focused (for returning from content strip)
FocusNode? _lastFocusedButtonNode;
/// Whether the content strip has any content to show
@@ -714,7 +706,6 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
children: [
// Row 1: Timeline (LiveTimelineBar for time-shifted live, VideoTimelineBar for VOD)
if (_isLive && widget.captureBuffer != null) ...[
LiveTimelineBar(
player: widget.player,
@@ -748,14 +739,12 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
previewPosition: _timelineSeek.pendingPosition,
),
],
// Row 2: Playback controls and options
Focus(
onFocusChange: _onButtonRowFocusChange,
skipTraversal: true,
child: Row(
children: [
if (!_isLive) ...[
// Previous item
Opacity(
opacity: _canControl ? 1.0 : 0.5,
child: _buildFocusableButton(
@@ -354,7 +354,6 @@ class _MobileVideoControlsState extends State<MobileVideoControls> with SingleTi
mainAxisAlignment: .center,
children: [
if (!widget.isLive) ...[
// Previous episode button (greyed out when unavailable)
CircularControlButton(
semanticLabel: t.videoControls.previousButton,
icon: Symbols.skip_previous_rounded,
@@ -378,7 +377,6 @@ class _MobileVideoControlsState extends State<MobileVideoControls> with SingleTi
),
if (!widget.isLive) ...[
const SizedBox(width: 24),
// Next episode button (greyed out when unavailable)
CircularControlButton(
semanticLabel: t.videoControls.nextButton,
icon: Symbols.skip_next_rounded,
@@ -24,7 +24,6 @@ extension _PlexVideoControlsTrackMethods on _PlexVideoControlsState {
}
final currentTrack = widget.player.state.track.subtitle;
// Nothing to hide when no subtitle track is selected.
if (currentTrack == null || currentTrack.id == SubtitleTrack.off.id) return;
_setSubtitleVisibility(false);
@@ -51,7 +50,6 @@ extension _PlexVideoControlsTrackMethods on _PlexVideoControlsState {
}
void _onSubtitleTrackChanged(SubtitleTrack track) {
// Reset visibility when user explicitly picks a new subtitle track
if (track.id != 'no' && !_subtitlesVisible) {
_setSubtitleVisibility(true);
}
@@ -770,7 +770,6 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
},
),
// Audio Sync
_SettingsMenuItem(
icon: Symbols.sync_rounded,
title: t.videoSettings.audioSync,
@@ -779,7 +778,6 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
onTap: () => _navigateTo(_SettingsView.audioSync),
),
// Subtitle Sync
_SettingsMenuItem(
icon: Symbols.subtitles_rounded,
title: t.videoSettings.subtitleSync,
@@ -788,7 +786,6 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
onTap: () => _navigateTo(_SettingsView.subtitleSync),
),
// HDR Toggle
if (_supportsHdrControl)
_SettingsToggleItem(
pref: SettingsService.enableHDR,
@@ -808,14 +805,12 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
onTap: () => _navigateTo(_SettingsView.hdrToneMapping),
),
// Auto-Play Next Episode Toggle
_SettingsToggleItem(
pref: SettingsService.autoPlayNextEpisode,
icon: Symbols.skip_next_rounded,
title: t.videoControls.autoPlayNext,
),
// Audio Output Device (Desktop only)
if (isDesktop)
StreamBuilder<AudioDevice>(
stream: widget.player.streams.audioDevice,
@@ -845,7 +840,6 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
// "not Dolby" when the system reports notApplicable.
if (PlatformDetector.isAppleTV()) _AudioRenderingModeItem(player: widget.player),
// Audio Normalization
_SettingsToggleItem(
pref: SettingsService.audioNormalization,
icon: Symbols.graphic_eq_rounded,
@@ -853,7 +847,6 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
onAfterWrite: widget.player.setAudioNormalization,
),
// Stereo Downmix
_SettingsToggleItem(
pref: SettingsService.audioDownmix,
icon: Symbols.headphones_rounded,
@@ -62,7 +62,6 @@ class VideoControlButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Determine the effective color: explicit color > active amber > default white
final effectiveColor = color ?? (isActive ? Colors.amber : Colors.white);
final button = IconButton(
@@ -85,9 +84,9 @@ class VideoControlButton extends StatelessWidget {
semanticLabel: effectiveSemanticLabel,
semanticValue: semanticValue,
checked: checked,
borderRadius: 20, // Circular for icon buttons
autoScroll: false, // Video controls don't scroll
useBackgroundFocus: true, // Use background highlight for video controls
borderRadius: 20,
autoScroll: false,
useBackgroundFocus: true,
child: result,
);
} else if (effectiveSemanticLabel != null) {
@@ -5,10 +5,8 @@ import '../../../../i18n/strings.g.dart';
/// Contains metrics queried from the video player (MPV or ExoPlayer)
/// including video/audio codec info, playback performance, and buffer state.
class PerformanceStats {
// Player info
final String playerType; // 'mpv' or 'exoplayer'
final String playerType;
// Video metrics
final String? videoCodec;
final int? videoWidth;
final int? videoHeight;
@@ -19,38 +17,32 @@ class PerformanceStats {
final int? rotate;
final String? videoDecoderName;
// Color/Format metrics
final String? pixelformat;
final String? hwPixelformat;
final String? colormatrix;
final String? primaries;
final String? gamma;
// HDR metadata
final double? maxLuma;
final double? minLuma;
final double? maxCll;
final double? maxFall;
// Audio metrics
final String? audioCodec;
final int? audioSamplerate;
final String? audioChannels;
final int? audioBitrate;
final String? audioDecoderName;
// Tunneling
final bool tunneledPlayback;
final String? tunnelingStatus;
// Performance metrics
final double? actualFps;
final double? avsyncChange;
final double? displayFps;
final int? frameDropCount;
final int? decoderFrameDropCount;
// Buffer metrics
final int? cacheUsed;
final int? cacheLimit;
final double? cacheSpeed;
@@ -58,9 +50,8 @@ class PerformanceStats {
final int? bufferTargetBytes;
final int? bufferMaxMs;
// DV conversion
final bool dvConversionActive;
final String dvConversionMode; // "DV81", "HEVC_STRIP", "DISABLED"
final String dvConversionMode;
final int? dvConvertedRpus;
final int? dvRpuConversionFailures;
final int? dvRpuOutputTooSmall;
@@ -70,7 +61,6 @@ class PerformanceStats {
final String? dvPlaybackPath;
final String? dvPlaybackReason;
// App metrics
final int? appMemoryBytes;
final double? uiFps;
@@ -69,7 +69,6 @@ class TrackChapterControls extends StatelessWidget {
final key = event.logicalKey;
// LEFT arrow - move to previous button or exit to volume
if (key == LogicalKeyboardKey.arrowLeft) {
if (index > 0 && focusNodes != null && focusNodes!.length > index - 1) {
focusNodes![index - 1].requestFocus();
@@ -81,23 +80,19 @@ class TrackChapterControls extends StatelessWidget {
return KeyEventResult.handled;
}
// RIGHT arrow - move to next button
if (key == LogicalKeyboardKey.arrowRight) {
if (index < totalButtons - 1 && focusNodes != null && focusNodes!.length > index + 1) {
focusNodes![index + 1].requestFocus();
return KeyEventResult.handled;
}
// At end, consume to prevent bubbling
return KeyEventResult.handled;
}
// UP arrow - navigate up (e.g., to timeline)
if (key == LogicalKeyboardKey.arrowUp) {
onNavigateUp?.call();
return KeyEventResult.handled;
}
// DOWN arrow - navigate down (e.g., to content strip)
if (key == LogicalKeyboardKey.arrowDown) {
onNavigateDown?.call();
return KeyEventResult.handled;
@@ -146,11 +141,9 @@ class TrackChapterControls extends StatelessWidget {
final isMobile = PlatformDetector.isMobile(context);
final isDesktop = PlatformDetector.isDesktopOS();
// Build list of buttons dynamically to track indices
final buttons = <Widget>[];
int buttonIndex = 0;
// Settings button (always shown)
buttons.add(
ListenableBuilder(
listenable: SleepTimerService(),
@@ -193,7 +186,6 @@ class TrackChapterControls extends StatelessWidget {
);
buttonIndex++;
// Combined audio & subtitles button
{
final currentIndex = buttonIndex;
buttons.add(
@@ -232,7 +224,6 @@ class TrackChapterControls extends StatelessWidget {
buttonIndex++;
}
// Chapters button (hidden on mobile when content strip is available)
if (chapters.isNotEmpty && !hideChaptersAndQueue) {
final currentIndex = buttonIndex;
buttons.add(
@@ -264,7 +255,6 @@ class TrackChapterControls extends StatelessWidget {
buttonIndex++;
}
// Queue button (hidden on mobile when content strip is available)
if (state.showQueueButton && state.onQueueItemSelected != null && !hideChaptersAndQueue) {
final currentIndex = buttonIndex;
buttons.add(
@@ -286,7 +276,6 @@ class TrackChapterControls extends StatelessWidget {
buttonIndex++;
}
// Picture-in-Picture mode
if (state.onTogglePIPMode != null) {
final currentIndex = buttonIndex;
buttons.add(