From 3366ed02f57819a4bdf22717e33e23e85073f590 Mon Sep 17 00:00:00 2001 From: Doezer Date: Thu, 13 Nov 2025 22:53:31 +0100 Subject: [PATCH] Ran dart format using this pwsh command $FILES = Get-ChildItem -Path lib, test -Recurse -Filter "*.dart" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -notmatch '\.(g|freezed)\.dart$' } | Select-Object -ExpandProperty FullName; if (-not $FILES) { Write-Host "No Dart files found to format"; exit 0 } else { dart format --output=write --set-exit-if-changed $FILES } --- lib/models/plex_file_info.dart | 3 +- lib/models/plex_video_playback_data.dart | 2 +- lib/providers/playback_state_provider.dart | 5 +- lib/screens/about_screen.dart | 4 +- lib/screens/auth_screen.dart | 36 +++++--- lib/screens/discover_screen.dart | 23 ++--- lib/screens/hub_detail_screen.dart | 6 +- lib/screens/libraries_screen.dart | 56 ++++++++---- lib/screens/licenses_screen.dart | 8 +- lib/screens/logs_screen.dart | 85 ++++++++---------- lib/screens/profile_switch_screen.dart | 4 +- lib/screens/server_selection_screen.dart | 18 ++-- lib/screens/subtitle_styling_screen.dart | 71 +++++++++++---- lib/screens/video_player_screen.dart | 6 +- lib/services/keyboard_shortcuts_service.dart | 4 +- lib/services/plex_auth_service.dart | 37 ++++++-- lib/services/settings_service.dart | 6 +- lib/services/track_selection_service.dart | 28 ++++-- lib/services/update_service.dart | 16 ++-- lib/utils/app_logger.dart | 33 ++++--- lib/utils/shuffle_play_helper.dart | 17 ++-- lib/utils/user_switching_utils.dart | 4 +- lib/utils/video_player_navigation.dart | 4 +- lib/widgets/context_menu_wrapper.dart | 4 +- lib/widgets/file_info_bottom_sheet.dart | 89 ++++++++++++++----- .../horizontal_scroll_with_arrows.dart | 23 ++--- lib/widgets/media_card.dart | 61 +++++++------ lib/widgets/media_context_menu.dart | 25 ++++-- lib/widgets/pin_entry_dialog.dart | 4 +- lib/widgets/sort_bottom_sheet.dart | 20 ++--- .../sheets/audio_track_sheet.dart | 15 ++-- .../video_controls/sheets/chapter_sheet.dart | 68 +++++++------- .../sheets/playback_speed_sheet.dart | 5 +- .../sheets/sleep_timer_sheet.dart | 61 +++++++------ .../sheets/subtitle_track_sheet.dart | 19 ++-- .../sheets/video_settings_sheet.dart | 15 +++- .../video_controls/video_controls.dart | 6 +- .../widgets/sync_offset_control.dart | 10 ++- 38 files changed, 530 insertions(+), 371 deletions(-) diff --git a/lib/models/plex_file_info.dart b/lib/models/plex_file_info.dart index 84972061..3ebf85cb 100644 --- a/lib/models/plex_file_info.dart +++ b/lib/models/plex_file_info.dart @@ -140,7 +140,8 @@ class PlexFileInfo { /// Format audio channels (e.g., "2 channels (stereo)") String get audioChannelsFormatted { if (audioChannels != null) { - String channelText = '$audioChannels channel${audioChannels! > 1 ? 's' : ''}'; + String channelText = + '$audioChannels channel${audioChannels! > 1 ? 's' : ''}'; if (audioChannelLayout != null) { channelText += ' ($audioChannelLayout)'; } diff --git a/lib/models/plex_video_playback_data.dart b/lib/models/plex_video_playback_data.dart index 90851cde..8f5cd912 100644 --- a/lib/models/plex_video_playback_data.dart +++ b/lib/models/plex_video_playback_data.dart @@ -27,4 +27,4 @@ class PlexVideoPlaybackData { /// Returns true if there are multiple media versions available bool get hasMultipleVersions => availableVersions.length > 1; -} \ No newline at end of file +} diff --git a/lib/providers/playback_state_provider.dart b/lib/providers/playback_state_provider.dart index b61ce5df..721345af 100644 --- a/lib/providers/playback_state_provider.dart +++ b/lib/providers/playback_state_provider.dart @@ -26,7 +26,10 @@ class PlaybackStateProvider with ChangeNotifier { /// Gets the next episode in the shuffle queue. /// Returns null if queue is exhausted or current episode is not in queue. /// [loopQueue] - If true, restart from beginning when queue is exhausted - PlexMetadata? getNextEpisode(String currentEpisodeKey, {bool loopQueue = false}) { + PlexMetadata? getNextEpisode( + String currentEpisodeKey, { + bool loopQueue = false, + }) { if (_shuffleQueue.isEmpty) return null; // Find current episode in queue diff --git a/lib/screens/about_screen.dart b/lib/screens/about_screen.dart index 702b97f0..640e3531 100644 --- a/lib/screens/about_screen.dart +++ b/lib/screens/about_screen.dart @@ -78,9 +78,7 @@ class _AboutScreenState extends State { child: ListTile( leading: const Icon(Icons.description), title: Text(t.about.openSourceLicenses), - subtitle: Text( - t.about.viewLicensesDescription, - ), + subtitle: Text(t.about.viewLicensesDescription), trailing: const Icon(Icons.chevron_right), onTap: () { Navigator.push( diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index 2f41bf84..93c67716 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -245,13 +245,16 @@ class _AuthScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Image.asset('assets/plezy.png', width: 120, height: 120), + Image.asset( + 'assets/plezy.png', + width: 120, + height: 120, + ), const SizedBox(height: 24), Text( t.app.title, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - ), + style: Theme.of(context).textTheme.headlineMedium + ?.copyWith(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), ], @@ -321,7 +324,9 @@ class _AuthScreenState extends State { ElevatedButton( onPressed: _startAuthentication, style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + padding: const EdgeInsets.symmetric( + vertical: 16, + ), ), child: Text(t.auth.signInWithPlex), ), @@ -334,7 +339,9 @@ class _AuthScreenState extends State { _startAuthentication(); }, style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + padding: const EdgeInsets.symmetric( + vertical: 16, + ), ), child: Text(t.auth.showQRCode), ), @@ -343,11 +350,12 @@ class _AuthScreenState extends State { OutlinedButton( onPressed: _handleDebugTap, style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric( + vertical: 12, + ), side: BorderSide( - color: Theme.of( - context, - ).colorScheme.outline.withValues(alpha: 0.5), + color: Theme.of(context).colorScheme.outline + .withValues(alpha: 0.5), ), ), child: Text( @@ -380,9 +388,8 @@ class _AuthScreenState extends State { const SizedBox(height: 24), Text( t.app.title, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - ), + style: Theme.of(context).textTheme.headlineMedium + ?.copyWith(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), const SizedBox(height: 48), @@ -405,7 +412,8 @@ class _AuthScreenState extends State { ), child: Text(t.auth.retry), ), - ] else ...[ // add QR button here + ] else ...[ + // add QR button here ElevatedButton( onPressed: _startAuthentication, style: ElevatedButton.styleFrom( diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index a133ca17..051e1d79 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -240,7 +240,7 @@ class _DiscoverScreenState extends State /// This is called when returning to the home screen to avoid blocking UI Future _refreshContinueWatching() async { appLogger.d('Refreshing Continue Watching in background'); - + try { final clientProvider = context.plexClient; final client = clientProvider.client; @@ -250,7 +250,7 @@ class _DiscoverScreenState extends State } final onDeck = await client.getOnDeck(); - + if (mounted) { setState(() { _onDeck = onDeck; @@ -412,11 +412,9 @@ class _DiscoverScreenState extends State if (plexToken == null) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(t.messages.noPlexToken), - ), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.messages.noPlexToken))); } return; } @@ -627,10 +625,13 @@ class _DiscoverScreenState extends State ), ), ), - _buildHorizontalList(_onDeck, isLarge: false, isInContinueWatching: true), + _buildHorizontalList( + _onDeck, + isLarge: false, + isInContinueWatching: true, + ), ], - // Recommendation Hubs (Trending, Top in Genre, etc.) for (final hub in _hubs) ...[ SliverToBoxAdapter( @@ -1279,8 +1280,8 @@ class _DiscoverScreenState extends State width: cardWidth, height: posterHeight, onRefresh: updateItem, - onRemoveFromContinueWatching: isInContinueWatching - ? _refreshContinueWatching + onRemoveFromContinueWatching: isInContinueWatching + ? _refreshContinueWatching : null, forceGridMode: true, isInContinueWatching: isInContinueWatching, diff --git a/lib/screens/hub_detail_screen.dart b/lib/screens/hub_detail_screen.dart index 427ce57d..5e3fd5a4 100644 --- a/lib/screens/hub_detail_screen.dart +++ b/lib/screens/hub_detail_screen.dart @@ -100,7 +100,11 @@ class _HubDetailScreenState extends State with Refreshable { List _getDefaultSortOptions() { return [ - PlexSort(key: 'titleSort', title: t.hubDetail.title, defaultDirection: 'asc'), + PlexSort( + key: 'titleSort', + title: t.hubDetail.title, + defaultDirection: 'asc', + ), PlexSort( key: 'year', descKey: 'year:desc', diff --git a/lib/screens/libraries_screen.dart b/lib/screens/libraries_screen.dart index 2c4cea24..b7a65fd2 100644 --- a/lib/screens/libraries_screen.dart +++ b/lib/screens/libraries_screen.dart @@ -72,7 +72,10 @@ class _LibrariesScreenState extends State return t.errors.connectionFailed; default: appLogger.e('Error loading $context', error: error); - return t.errors.failedToLoad(context: context, error: error.message ?? 'Unknown error'); + return t.errors.failedToLoad( + context: context, + error: error.message ?? 'Unknown error', + ); } } @@ -601,8 +604,9 @@ class _LibrariesScreenState extends State label: t.libraries.scanLibraryFiles, requiresConfirmation: true, confirmationTitle: t.libraries.scanLibrary, - confirmationMessage: - t.libraries.scanLibraryConfirm(title: library.title), + confirmationMessage: t.libraries.scanLibraryConfirm( + title: library.title, + ), ), ContextMenuItem( value: 'analyze', @@ -610,8 +614,9 @@ class _LibrariesScreenState extends State label: t.libraries.analyze, requiresConfirmation: true, confirmationTitle: t.libraries.analyzeLibrary, - confirmationMessage: - t.libraries.analyzeLibraryConfirm(title: library.title), + confirmationMessage: t.libraries.analyzeLibraryConfirm( + title: library.title, + ), ), ContextMenuItem( value: 'refresh', @@ -619,8 +624,9 @@ class _LibrariesScreenState extends State label: t.libraries.refreshMetadata, requiresConfirmation: true, confirmationTitle: t.libraries.refreshMetadata, - confirmationMessage: - t.libraries.refreshMetadataConfirm(title: library.title), + confirmationMessage: t.libraries.refreshMetadataConfirm( + title: library.title, + ), isDestructive: true, ), ContextMenuItem( @@ -629,8 +635,9 @@ class _LibrariesScreenState extends State label: t.libraries.emptyTrash, requiresConfirmation: true, confirmationTitle: t.libraries.emptyTrash, - confirmationMessage: - t.libraries.emptyTrashConfirm(title: library.title), + confirmationMessage: t.libraries.emptyTrashConfirm( + title: library.title, + ), isDestructive: true, ), ]; @@ -743,7 +750,9 @@ class _LibrariesScreenState extends State if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(t.messages.metadataRefreshStarted(title: library.title)), + content: Text( + t.messages.metadataRefreshStarted(title: library.title), + ), duration: const Duration(seconds: 3), ), ); @@ -753,7 +762,9 @@ class _LibrariesScreenState extends State if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(t.messages.metadataRefreshFailed(error: e.toString())), + content: Text( + t.messages.metadataRefreshFailed(error: e.toString()), + ), backgroundColor: Colors.red, duration: const Duration(seconds: 3), ), @@ -1037,7 +1048,11 @@ class _LibrariesScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.folder_open, size: 64, color: Colors.grey), + const Icon( + Icons.folder_open, + size: 64, + color: Colors.grey, + ), const SizedBox(height: 16), Text(t.libraries.thisLibraryIsEmpty), ], @@ -1097,7 +1112,9 @@ class _LibrariesScreenState extends State const CircularProgressIndicator(), const SizedBox(height: 8), Text( - t.libraries.loadingLibraryWithCount(count: _items.length), + t.libraries.loadingLibraryWithCount( + count: _items.length, + ), style: Theme.of(context).textTheme.bodySmall, ), ], @@ -1393,7 +1410,10 @@ class _FiltersBottomSheetState extends State<_FiltersBottomSheet> { const SizedBox(width: 12), Text( t.libraries.filters, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), const Spacer(), if (_tempSelectedFilters.isNotEmpty) @@ -1718,7 +1738,9 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( - title: Text(selectedItem.confirmationTitle ?? t.dialog.confirmAction), + title: Text( + selectedItem.confirmationTitle ?? t.dialog.confirmAction, + ), content: Text( selectedItem.confirmationMessage ?? t.libraries.confirmActionMessage, @@ -1852,7 +1874,9 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { : Icons.visibility, ), onPressed: () => widget.onToggleVisibility(library), - tooltip: isHidden ? t.libraries.showLibrary : t.libraries.hideLibrary, + tooltip: isHidden + ? t.libraries.showLibrary + : t.libraries.hideLibrary, ), IconButton( icon: const Icon(Icons.more_vert), diff --git a/lib/screens/licenses_screen.dart b/lib/screens/licenses_screen.dart index 87e2bb26..042123f6 100644 --- a/lib/screens/licenses_screen.dart +++ b/lib/screens/licenses_screen.dart @@ -93,7 +93,9 @@ class _LicensesScreenState extends State { ), subtitle: mergedLicense.licenseEntries.length > 1 ? Text( - t.licenses.licensesCount(count: mergedLicense.licenseEntries.length), + t.licenses.licensesCount( + count: mergedLicense.licenseEntries.length, + ), ) : null, trailing: const Icon(Icons.chevron_right), @@ -178,7 +180,9 @@ class _LicenseDetailScreen extends StatelessWidget { children: [ Text( isMultipleLicenses - ? t.licenses.licenseNumber(number: index + 1) + ? t.licenses.licenseNumber( + number: index + 1, + ) : t.licenses.license, style: Theme.of(context).textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.bold), diff --git a/lib/screens/logs_screen.dart b/lib/screens/logs_screen.dart index e6368395..6b91e738 100644 --- a/lib/screens/logs_screen.dart +++ b/lib/screens/logs_screen.dart @@ -40,9 +40,9 @@ class _LogsScreenState extends State { MemoryLogOutput.clearLogs(); _logs = []; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(t.messages.logsCleared)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.messages.logsCleared))); } void _copyAllLogs() { @@ -55,7 +55,8 @@ class _LogsScreenState extends State { isFirst = false; buffer.write( - '[${_formatTime(log.timestamp)}] [${log.level.name.toUpperCase()}] ${log.message}'); + '[${_formatTime(log.timestamp)}] [${log.level.name.toUpperCase()}] ${log.message}', + ); if (log.error != null) { buffer.write('\nError: ${log.error}'); } @@ -64,9 +65,9 @@ class _LogsScreenState extends State { } } Clipboard.setData(ClipboardData(text: buffer.toString())); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(t.messages.logsCopied)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.messages.logsCopied))); } Color _getLevelColor(Level level) { @@ -131,26 +132,21 @@ class _LogsScreenState extends State { ), if (_logs.isEmpty) SliverFillRemaining( - child: Center( - child: Text(t.messages.noLogsAvailable), - ), + child: Center(child: Text(t.messages.noLogsAvailable)), ) else SliverPadding( padding: const EdgeInsets.all(8), sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final log = _logs[index]; - return _LogEntryCard( - log: log, - formatTime: _formatTime, - levelColor: _getLevelColor(log.level), - levelIcon: _getLevelIcon(log.level), - ); - }, - childCount: _logs.length, - ), + delegate: SliverChildBuilderDelegate((context, index) { + final log = _logs[index]; + return _LogEntryCard( + log: log, + formatTime: _formatTime, + levelColor: _getLevelColor(log.level), + levelIcon: _getLevelIcon(log.level), + ); + }, childCount: _logs.length), ), ), ], @@ -198,11 +194,7 @@ class _LogEntryCardState extends State<_LogEntryCard> { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( - widget.levelIcon, - color: widget.levelColor, - size: 20, - ), + Icon(widget.levelIcon, color: widget.levelColor, size: 20), const SizedBox(width: 8), Expanded( child: Column( @@ -221,9 +213,7 @@ class _LogEntryCardState extends State<_LogEntryCard> { const SizedBox(width: 8), Text( widget.formatTime(widget.log.timestamp), - style: Theme.of(context) - .textTheme - .bodySmall + style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: Theme.of(context) .textTheme @@ -244,13 +234,10 @@ class _LogEntryCardState extends State<_LogEntryCard> { ), if (hasErrorOrStackTrace) Icon( - _isExpanded - ? Icons.expand_less - : Icons.expand_more, - color: Theme.of(context) - .iconTheme - .color - ?.withValues(alpha: 0.6), + _isExpanded ? Icons.expand_less : Icons.expand_more, + color: Theme.of( + context, + ).iconTheme.color?.withValues(alpha: 0.6), ), ], ), @@ -262,9 +249,9 @@ class _LogEntryCardState extends State<_LogEntryCard> { Text( t.logs.error, style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: widget.levelColor, - fontWeight: FontWeight.bold, - ), + color: widget.levelColor, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 4), Container( @@ -277,9 +264,9 @@ class _LogEntryCardState extends State<_LogEntryCard> { ), child: SelectableText( widget.log.error.toString(), - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontFamily: 'monospace', - ), + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(fontFamily: 'monospace'), ), ), ], @@ -288,9 +275,9 @@ class _LogEntryCardState extends State<_LogEntryCard> { Text( t.logs.stackTrace, style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: widget.levelColor, - fontWeight: FontWeight.bold, - ), + color: widget.levelColor, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 4), Container( @@ -303,9 +290,9 @@ class _LogEntryCardState extends State<_LogEntryCard> { ), child: SelectableText( widget.log.stackTrace.toString(), - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontFamily: 'monospace', - ), + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(fontFamily: 'monospace'), ), ), ], diff --git a/lib/screens/profile_switch_screen.dart b/lib/screens/profile_switch_screen.dart index a2a2062c..42f84707 100644 --- a/lib/screens/profile_switch_screen.dart +++ b/lib/screens/profile_switch_screen.dart @@ -116,7 +116,9 @@ class ProfileSwitchScreen extends StatelessWidget { } else if (!success && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(t.errors.failedToSwitchProfile(displayName: user.displayName)), + content: Text( + t.errors.failedToSwitchProfile(displayName: user.displayName), + ), backgroundColor: Theme.of(context).colorScheme.error, ), ); diff --git a/lib/screens/server_selection_screen.dart b/lib/screens/server_selection_screen.dart index 4f1fc749..8f142ca2 100644 --- a/lib/screens/server_selection_screen.dart +++ b/lib/screens/server_selection_screen.dart @@ -75,7 +75,9 @@ class _ServerSelectionScreenState extends State { String _getErrorMessage(dynamic error) { if (error is ServerParsingException) { - return t.serverSelection.malformedServerData(count: error.invalidServerData.length); + return t.serverSelection.malformedServerData( + count: error.invalidServerData.length, + ); } else if (error is FormatException) { // Handle JSON parsing errors with more user-friendly messages if (error.message.contains('Invalid server data')) { @@ -85,13 +87,13 @@ class _ServerSelectionScreenState extends State { } return t.serverSelection.malformedServerInfo(message: error.message); } else if (error.toString().contains('SocketException') || - error.toString().contains('TimeoutException')) { + error.toString().contains('TimeoutException')) { return t.serverSelection.networkConnectionFailed; } else if (error.toString().contains('401') || - error.toString().contains('Unauthorized')) { + error.toString().contains('Unauthorized')) { return t.serverSelection.authenticationFailed; } else if (error.toString().contains('404') || - error.toString().contains('Not Found')) { + error.toString().contains('Not Found')) { return t.serverSelection.plexServiceUnavailable; } @@ -101,7 +103,9 @@ class _ServerSelectionScreenState extends State { Future _copyDebugDataToClipboard() async { if (_debugServerData == null) return; - final jsonString = const JsonEncoder.withIndent(' ').convert(_debugServerData); + final jsonString = const JsonEncoder.withIndent( + ' ', + ).convert(_debugServerData); await Clipboard.setData(ClipboardData(text: jsonString)); if (mounted) { @@ -207,7 +211,9 @@ class _ServerSelectionScreenState extends State { // Show error if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(result.error ?? t.errors.connectionFailedGeneric)), + SnackBar( + content: Text(result.error ?? t.errors.connectionFailedGeneric), + ), ); } } diff --git a/lib/screens/subtitle_styling_screen.dart b/lib/screens/subtitle_styling_screen.dart index 10b9a2ae..4c10c7a1 100644 --- a/lib/screens/subtitle_styling_screen.dart +++ b/lib/screens/subtitle_styling_screen.dart @@ -52,10 +52,15 @@ class _SubtitleStylingScreenState extends State { } String _colorToHex(Color color) { - return '#${((color.r * 255.0).round() & 0xff).toRadixString(16).padLeft(2, '0')}${((color.g * 255.0).round() & 0xff).toRadixString(16).padLeft(2, '0')}${((color.b * 255.0).round() & 0xff).toRadixString(16).padLeft(2, '0')}'.toUpperCase(); + return '#${((color.r * 255.0).round() & 0xff).toRadixString(16).padLeft(2, '0')}${((color.g * 255.0).round() & 0xff).toRadixString(16).padLeft(2, '0')}${((color.b * 255.0).round() & 0xff).toRadixString(16).padLeft(2, '0')}' + .toUpperCase(); } - Future _showColorPicker(String title, String currentColor, Function(String) onColorSelected) async { + Future _showColorPicker( + String title, + String currentColor, + Function(String) onColorSelected, + ) async { Color initialColor = _hexToColor(currentColor); final Color selectedColor = await showColorPickerDialog( @@ -123,7 +128,9 @@ class _SubtitleStylingScreenState extends State { padding: const EdgeInsets.all(16), child: Text( t.subtitlingStyling.stylingOptions, - style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), + style: Theme.of( + context, + ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), ), // Font Size Slider @@ -142,7 +149,10 @@ class _SubtitleStylingScreenState extends State { const SizedBox(height: 8), Row( children: [ - const Text('30', style: TextStyle(fontSize: 12, color: Colors.grey)), + const Text( + '30', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), Expanded( child: Slider( value: _fontSize.toDouble(), @@ -160,7 +170,10 @@ class _SubtitleStylingScreenState extends State { }, ), ), - const Text('80', style: TextStyle(fontSize: 12, color: Colors.grey)), + const Text( + '80', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), ], ), ], @@ -182,7 +195,9 @@ class _SubtitleStylingScreenState extends State { subtitle: Text(_textColor), trailing: const Icon(Icons.chevron_right), onTap: () { - _showColorPicker(t.subtitlingStyling.textColor, _textColor, (color) { + _showColorPicker(t.subtitlingStyling.textColor, _textColor, ( + color, + ) { setState(() { _textColor = color; }); @@ -207,7 +222,10 @@ class _SubtitleStylingScreenState extends State { const SizedBox(height: 8), Row( children: [ - const Text('0', style: TextStyle(fontSize: 12, color: Colors.grey)), + const Text( + '0', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), Expanded( child: Slider( value: _borderSize.toDouble(), @@ -225,7 +243,10 @@ class _SubtitleStylingScreenState extends State { }, ), ), - const Text('5', style: TextStyle(fontSize: 12, color: Colors.grey)), + const Text( + '5', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), ], ), ], @@ -247,7 +268,9 @@ class _SubtitleStylingScreenState extends State { subtitle: Text(_borderColor), trailing: const Icon(Icons.chevron_right), onTap: () { - _showColorPicker(t.subtitlingStyling.borderColor, _borderColor, (color) { + _showColorPicker(t.subtitlingStyling.borderColor, _borderColor, ( + color, + ) { setState(() { _borderColor = color; }); @@ -272,7 +295,10 @@ class _SubtitleStylingScreenState extends State { const SizedBox(height: 8), Row( children: [ - const Text('0%', style: TextStyle(fontSize: 12, color: Colors.grey)), + const Text( + '0%', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), Expanded( child: Slider( value: _backgroundOpacity.toDouble(), @@ -286,11 +312,16 @@ class _SubtitleStylingScreenState extends State { }); }, onChangeEnd: (value) { - _settingsService.setSubtitleBackgroundOpacity(_backgroundOpacity); + _settingsService.setSubtitleBackgroundOpacity( + _backgroundOpacity, + ); }, ), ), - const Text('100%', style: TextStyle(fontSize: 12, color: Colors.grey)), + const Text( + '100%', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), ], ), ], @@ -312,12 +343,16 @@ class _SubtitleStylingScreenState extends State { subtitle: Text(_backgroundColor), trailing: const Icon(Icons.chevron_right), onTap: () { - _showColorPicker(t.subtitlingStyling.backgroundColor, _backgroundColor, (color) { - setState(() { - _backgroundColor = color; - }); - _settingsService.setSubtitleBackgroundColor(color); - }); + _showColorPicker( + t.subtitlingStyling.backgroundColor, + _backgroundColor, + (color) { + setState(() { + _backgroundColor = color; + }); + _settingsService.setSubtitleBackgroundColor(color); + }, + ); }, ), ], diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 13443a48..7d5a5826 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -428,9 +428,9 @@ class VideoPlayerScreenState extends State { } } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(t.messages.errorLoading(error: e.toString())))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))), + ); } } } diff --git a/lib/services/keyboard_shortcuts_service.dart b/lib/services/keyboard_shortcuts_service.dart index abfd4a6c..86361db3 100644 --- a/lib/services/keyboard_shortcuts_service.dart +++ b/lib/services/keyboard_shortcuts_service.dart @@ -239,8 +239,8 @@ class KeyboardShortcutsService { // Clamp between 0 and video duration final clampedPosition = newPosition.isNegative - ? Duration.zero - : (newPosition > duration ? duration : newPosition); + ? Duration.zero + : (newPosition > duration ? duration : newPosition); player.seek(clampedPosition); } diff --git a/lib/services/plex_auth_service.dart b/lib/services/plex_auth_service.dart index 54b5ec4a..7c6ec57c 100644 --- a/lib/services/plex_auth_service.dart +++ b/lib/services/plex_auth_service.dart @@ -271,7 +271,9 @@ class PlexServer { factory PlexServer.fromJson(Map json) { // Validate required fields first if (!_isValidServerJson(json)) { - throw FormatException('Invalid server data: missing required fields (name, clientIdentifier, accessToken, or connections)'); + throw FormatException( + 'Invalid server data: missing required fields (name, clientIdentifier, accessToken, or connections)', + ); } final List connectionsJson = json['connections'] as List; @@ -309,8 +311,10 @@ class PlexServer { return PlexServer( name: json['name'] as String, // Safe because validated above - clientIdentifier: json['clientIdentifier'] as String, // Safe because validated above - accessToken: json['accessToken'] as String, // Safe because validated above + clientIdentifier: + json['clientIdentifier'] as String, // Safe because validated above + accessToken: + json['accessToken'] as String, // Safe because validated above connections: connections, owned: json['owned'] as bool? ?? false, product: json['product'] as String?, @@ -326,10 +330,12 @@ class PlexServer { if (json['name'] is! String || (json['name'] as String).isEmpty) { return false; } - if (json['clientIdentifier'] is! String || (json['clientIdentifier'] as String).isEmpty) { + if (json['clientIdentifier'] is! String || + (json['clientIdentifier'] as String).isEmpty) { return false; } - if (json['accessToken'] is! String || (json['accessToken'] as String).isEmpty) { + if (json['accessToken'] is! String || + (json['accessToken'] as String).isEmpty) { return false; } @@ -394,8 +400,16 @@ class PlexServer { final httpCandidates = <_ConnectionCandidate>[]; for (final connection in connections) { - final uriCandidate = _ConnectionCandidate(connection, connection.uri, true); - final directCandidate = _ConnectionCandidate(connection, connection.directUrl, false); + final uriCandidate = _ConnectionCandidate( + connection, + connection.uri, + true, + ); + final directCandidate = _ConnectionCandidate( + connection, + connection.directUrl, + false, + ); if (connection.protocol == 'https') { httpsCandidates.add(uriCandidate); @@ -574,7 +588,9 @@ class PlexConnection { factory PlexConnection.fromJson(Map json) { // Validate required fields if (!_isValidConnectionJson(json)) { - throw FormatException('Invalid connection data: missing required fields (protocol, address, port, or uri)'); + throw FormatException( + 'Invalid connection data: missing required fields (protocol, address, port, or uri)', + ); } return PlexConnection( @@ -634,7 +650,10 @@ class PlexConnection { /// Create an HTTP fallback version of this HTTPS connection /// This allows testing HTTP when HTTPS is unavailable (e.g., certificate issues) PlexConnection toHttpFallback() { - assert(protocol == 'https', 'Can only create HTTP fallback for HTTPS connections'); + assert( + protocol == 'https', + 'Can only create HTTP fallback for HTTPS connections', + ); return PlexConnection( protocol: 'http', diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 6034e0be..8c745318 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -37,7 +37,8 @@ class SettingsService { static const String _keySubtitleBorderSize = 'subtitle_border_size'; static const String _keySubtitleBorderColor = 'subtitle_border_color'; static const String _keySubtitleBackgroundColor = 'subtitle_background_color'; - static const String _keySubtitleBackgroundOpacity = 'subtitle_background_opacity'; + static const String _keySubtitleBackgroundOpacity = + 'subtitle_background_opacity'; static const String _keyShuffleUnwatchedOnly = 'shuffle_unwatched_only'; static const String _keyShuffleOrderNavigation = 'shuffle_order_navigation'; static const String _keyShuffleLoopQueue = 'shuffle_loop_queue'; @@ -228,7 +229,8 @@ class SettingsService { } bool getRotationLocked() { - return _prefs.getBool(_keyRotationLocked) ?? true; // Default: locked (landscape only) + return _prefs.getBool(_keyRotationLocked) ?? + true; // Default: locked (landscape only) } // Subtitle Styling Settings diff --git a/lib/services/track_selection_service.dart b/lib/services/track_selection_service.dart index 11f6df7b..d852abef 100644 --- a/lib/services/track_selection_service.dart +++ b/lib/services/track_selection_service.dart @@ -68,7 +68,8 @@ class TrackSelectionService { // Mode 1: Shown with foreign audio if (profile.autoSelectSubtitle == 1) { // Check if audio language matches user's preferred subtitle language - if (selectedAudioTrack != null && profile.defaultSubtitleLanguage != null) { + if (selectedAudioTrack != null && + profile.defaultSubtitleLanguage != null) { final audioLang = selectedAudioTrack.languageCode; final prefLang = profile.defaultSubtitleLanguage; @@ -108,10 +109,16 @@ class TrackSelectionService { var candidateTracks = tracks; // Apply SDH (hearing impaired) filtering - candidateTracks = _filterBySDH(candidateTracks, profile.defaultSubtitleAccessibility); + candidateTracks = _filterBySDH( + candidateTracks, + profile.defaultSubtitleAccessibility, + ); // Apply forced subtitle filtering - candidateTracks = _filterByForced(candidateTracks, profile.defaultSubtitleForced); + candidateTracks = _filterByForced( + candidateTracks, + profile.defaultSubtitleForced, + ); // If no candidates after filtering, relax filters if (candidateTracks.isEmpty) { @@ -200,17 +207,20 @@ class TrackSelectionService { // Look for common SDH indicators return title.contains('sdh') || - displayTitle.contains('sdh') || - title.contains('cc') || - displayTitle.contains('cc') || - title.contains('hearing impaired') || - displayTitle.contains('hearing impaired'); + displayTitle.contains('sdh') || + title.contains('cc') || + displayTitle.contains('cc') || + title.contains('hearing impaired') || + displayTitle.contains('hearing impaired'); } /// Checks if a language code matches a preferred language /// /// Handles both 2-letter (ISO 639-1) and 3-letter (ISO 639-2) codes - static bool _matchesLanguage(String? trackLanguage, String? preferredLanguage) { + static bool _matchesLanguage( + String? trackLanguage, + String? preferredLanguage, + ) { if (trackLanguage == null || preferredLanguage == null) { return false; } diff --git a/lib/services/update_service.dart b/lib/services/update_service.dart index a470f7a0..b2e2b93b 100644 --- a/lib/services/update_service.dart +++ b/lib/services/update_service.dart @@ -18,7 +18,10 @@ class UpdateService { /// Check if update checking is enabled via build flag static bool get isUpdateCheckEnabled { - const enabled = bool.fromEnvironment('ENABLE_UPDATE_CHECK', defaultValue: false); + const enabled = bool.fromEnvironment( + 'ENABLE_UPDATE_CHECK', + defaultValue: false, + ); return enabled; } @@ -62,24 +65,21 @@ class UpdateService { /// Check for updates on GitHub (manual check, ignores cooldown) /// Returns a map with update info, or null if no update or error - static Future?> checkForUpdates({bool silent = false}) async { + static Future?> checkForUpdates({ + bool silent = false, + }) async { if (!isUpdateCheckEnabled) { return null; } try { - final packageInfo = await PackageInfo.fromPlatform(); final currentVersion = packageInfo.version; final dio = Dio(); final response = await dio.get( 'https://api.github.com/repos/$_githubRepo/releases/latest', - options: Options( - headers: { - 'Accept': 'application/vnd.github+json', - }, - ), + options: Options(headers: {'Accept': 'application/vnd.github+json'}), ); if (response.statusCode == 200) { diff --git a/lib/utils/app_logger.dart b/lib/utils/app_logger.dart index 43be6817..07c39cb6 100644 --- a/lib/utils/app_logger.dart +++ b/lib/utils/app_logger.dart @@ -13,7 +13,10 @@ String _redactSensitiveData(String message) { // Redact authorization headers redacted = redacted.replaceAllMapped( - RegExp(r'([Aa]uthorization[=:]\s*)([A-Za-z0-9_\-\.]+)', caseSensitive: false), + RegExp( + r'([Aa]uthorization[=:]\s*)([A-Za-z0-9_\-\.]+)', + caseSensitive: false, + ), (match) => '${match.group(1)}[REDACTED]', ); @@ -31,7 +34,9 @@ String _redactSensitiveData(String message) { // Redact full URLs with tokens in query parameters redacted = redacted.replaceAllMapped( - RegExp(r'(https?://[^\s]*[?&])([Xx]-[Pp]lex-[Tt]oken|token)=([A-Za-z0-9_-]+)'), + RegExp( + r'(https?://[^\s]*[?&])([Xx]-[Pp]lex-[Tt]oken|token)=([A-Za-z0-9_-]+)', + ), (match) => '${match.group(1)}${match.group(2)}=[REDACTED]', ); @@ -49,18 +54,18 @@ String _redactSensitiveData(String message) { // Redact standalone token-like strings (20+ alphanumeric characters) // Only if they appear in common token contexts - redacted = redacted.replaceAllMapped( - RegExp(r'\b([A-Za-z0-9_-]{20,})\b'), - (match) { - final token = match.group(1)!; - // Only redact if it looks like a token (mixed case or contains hyphens/underscores) - if (token.contains(RegExp(r'[A-Z]')) && token.contains(RegExp(r'[a-z]')) || - token.contains('_') || token.contains('-')) { - return '[REDACTED_TOKEN]'; - } - return token; - }, - ); + redacted = redacted.replaceAllMapped(RegExp(r'\b([A-Za-z0-9_-]{20,})\b'), ( + match, + ) { + final token = match.group(1)!; + // Only redact if it looks like a token (mixed case or contains hyphens/underscores) + if (token.contains(RegExp(r'[A-Z]')) && token.contains(RegExp(r'[a-z]')) || + token.contains('_') || + token.contains('-')) { + return '[REDACTED_TOKEN]'; + } + return token; + }); return redacted; } diff --git a/lib/utils/shuffle_play_helper.dart b/lib/utils/shuffle_play_helper.dart index a39b4b3f..58abed1c 100644 --- a/lib/utils/shuffle_play_helper.dart +++ b/lib/utils/shuffle_play_helper.dart @@ -32,8 +32,7 @@ Future handleShufflePlay( showDialog( context: context, barrierDismissible: false, - builder: (context) => - const Center(child: CircularProgressIndicator()), + builder: (context) => const Center(child: CircularProgressIndicator()), ); } @@ -42,9 +41,7 @@ Future handleShufflePlay( if (itemType == 'show') { if (unwatchedOnly) { // Get only unwatched episodes - episodes = await client.getAllUnwatchedEpisodes( - metadata.ratingKey, - ); + episodes = await client.getAllUnwatchedEpisodes(metadata.ratingKey); } else { // Get all episodes from all seasons final allEpisodes = []; @@ -71,9 +68,7 @@ Future handleShufflePlay( } else { // Get all episodes in season final seasonEpisodes = await client.getChildren(metadata.ratingKey); - episodes = seasonEpisodes - .where((ep) => ep.type == 'episode') - .toList(); + episodes = seasonEpisodes.where((ep) => ep.type == 'episode').toList(); } } @@ -84,9 +79,9 @@ Future handleShufflePlay( if (episodes.isEmpty) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(t.messages.noEpisodesFound)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.messages.noEpisodesFound))); } return; } diff --git a/lib/utils/user_switching_utils.dart b/lib/utils/user_switching_utils.dart index 50f5a125..38827aa0 100644 --- a/lib/utils/user_switching_utils.dart +++ b/lib/utils/user_switching_utils.dart @@ -18,7 +18,9 @@ class UserSwitchingUtils { } else if (!success && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(t.messages.failedToSwitchProfile(displayName: user.displayName)), + content: Text( + t.messages.failedToSwitchProfile(displayName: user.displayName), + ), backgroundColor: Theme.of(context).colorScheme.error, ), ); diff --git a/lib/utils/video_player_navigation.dart b/lib/utils/video_player_navigation.dart index 22f57523..d0996f21 100644 --- a/lib/utils/video_player_navigation.dart +++ b/lib/utils/video_player_navigation.dart @@ -41,7 +41,9 @@ Future navigateToVideoPlayer( try { final settingsService = await SettingsService.getInstance(); final seriesKey = metadata.grandparentRatingKey ?? metadata.ratingKey; - final savedPreference = settingsService.getMediaVersionPreference(seriesKey); + final savedPreference = settingsService.getMediaVersionPreference( + seriesKey, + ); if (savedPreference != null) { mediaIndex = savedPreference; } diff --git a/lib/widgets/context_menu_wrapper.dart b/lib/widgets/context_menu_wrapper.dart index 7f05d288..474daf2f 100644 --- a/lib/widgets/context_menu_wrapper.dart +++ b/lib/widgets/context_menu_wrapper.dart @@ -167,9 +167,7 @@ class _ContextMenuWrapperState extends State { if (selectedItem.requiresConfirmation) { final confirmed = await _showConfirmationDialog( title: selectedItem.confirmationTitle ?? t.dialog.confirmAction, - message: - selectedItem.confirmationMessage ?? - t.dialog.areYouSure, + message: selectedItem.confirmationMessage ?? t.dialog.areYouSure, isDestructive: selectedItem.isDestructive, ); diff --git a/lib/widgets/file_info_bottom_sheet.dart b/lib/widgets/file_info_bottom_sheet.dart index 1e2d21d1..58b92d48 100644 --- a/lib/widgets/file_info_bottom_sheet.dart +++ b/lib/widgets/file_info_bottom_sheet.dart @@ -77,30 +77,66 @@ class FileInfoBottomSheet extends StatelessWidget { // Video Section _buildSectionHeader(t.fileInfo.video), const SizedBox(height: 8), - _buildInfoRow(t.fileInfo.codec, fileInfo.videoCodec ?? t.common.unknown), - _buildInfoRow(t.fileInfo.resolution, fileInfo.resolutionFormatted), - _buildInfoRow(t.fileInfo.bitrate, fileInfo.bitrateFormatted), - _buildInfoRow(t.fileInfo.frameRate, fileInfo.frameRateFormatted), - _buildInfoRow(t.fileInfo.aspectRatio, fileInfo.aspectRatioFormatted), + _buildInfoRow( + t.fileInfo.codec, + fileInfo.videoCodec ?? t.common.unknown, + ), + _buildInfoRow( + t.fileInfo.resolution, + fileInfo.resolutionFormatted, + ), + _buildInfoRow( + t.fileInfo.bitrate, + fileInfo.bitrateFormatted, + ), + _buildInfoRow( + t.fileInfo.frameRate, + fileInfo.frameRateFormatted, + ), + _buildInfoRow( + t.fileInfo.aspectRatio, + fileInfo.aspectRatioFormatted, + ), if (fileInfo.videoProfile != null) _buildInfoRow(t.fileInfo.profile, fileInfo.videoProfile!), if (fileInfo.bitDepth != null) - _buildInfoRow(t.fileInfo.bitDepth, '${fileInfo.bitDepth} bit'), + _buildInfoRow( + t.fileInfo.bitDepth, + '${fileInfo.bitDepth} bit', + ), if (fileInfo.colorSpace != null) - _buildInfoRow(t.fileInfo.colorSpace, fileInfo.colorSpace!), + _buildInfoRow( + t.fileInfo.colorSpace, + fileInfo.colorSpace!, + ), if (fileInfo.colorRange != null) - _buildInfoRow(t.fileInfo.colorRange, fileInfo.colorRange!), + _buildInfoRow( + t.fileInfo.colorRange, + fileInfo.colorRange!, + ), if (fileInfo.colorPrimaries != null) - _buildInfoRow(t.fileInfo.colorPrimaries, fileInfo.colorPrimaries!), + _buildInfoRow( + t.fileInfo.colorPrimaries, + fileInfo.colorPrimaries!, + ), if (fileInfo.chromaSubsampling != null) - _buildInfoRow(t.fileInfo.chromaSubsampling, fileInfo.chromaSubsampling!), + _buildInfoRow( + t.fileInfo.chromaSubsampling, + fileInfo.chromaSubsampling!, + ), const SizedBox(height: 20), // Audio Section _buildSectionHeader(t.fileInfo.audio), const SizedBox(height: 8), - _buildInfoRow(t.fileInfo.codec, fileInfo.audioCodec ?? t.common.unknown), - _buildInfoRow(t.fileInfo.channels, fileInfo.audioChannelsFormatted), + _buildInfoRow( + t.fileInfo.codec, + fileInfo.audioCodec ?? t.common.unknown, + ), + _buildInfoRow( + t.fileInfo.channels, + fileInfo.audioChannelsFormatted, + ), if (fileInfo.audioProfile != null) _buildInfoRow(t.fileInfo.profile, fileInfo.audioProfile!), const SizedBox(height: 20), @@ -109,10 +145,20 @@ class FileInfoBottomSheet extends StatelessWidget { _buildSectionHeader(t.fileInfo.file), const SizedBox(height: 8), if (fileInfo.filePath != null) - _buildInfoRow(t.fileInfo.path, fileInfo.filePath!, isMonospace: true), + _buildInfoRow( + t.fileInfo.path, + fileInfo.filePath!, + isMonospace: true, + ), _buildInfoRow(t.fileInfo.size, fileInfo.fileSizeFormatted), - _buildInfoRow(t.fileInfo.container, fileInfo.container ?? t.common.unknown), - _buildInfoRow(t.fileInfo.duration, fileInfo.durationFormatted), + _buildInfoRow( + t.fileInfo.container, + fileInfo.container ?? t.common.unknown, + ), + _buildInfoRow( + t.fileInfo.duration, + fileInfo.durationFormatted, + ), const SizedBox(height: 20), // Advanced Section @@ -120,11 +166,15 @@ class FileInfoBottomSheet extends StatelessWidget { const SizedBox(height: 8), _buildInfoRow( t.fileInfo.optimizedForStreaming, - fileInfo.optimizedForStreaming == true ? t.common.yes : t.common.no, + fileInfo.optimizedForStreaming == true + ? t.common.yes + : t.common.no, ), _buildInfoRow( t.fileInfo.has64bitOffsets, - fileInfo.has64bitOffsets == true ? t.common.yes : t.common.no, + fileInfo.has64bitOffsets == true + ? t.common.yes + : t.common.no, ), ], ), @@ -157,10 +207,7 @@ class FileInfoBottomSheet extends StatelessWidget { width: 140, child: Text( label, - style: TextStyle( - color: Colors.grey[400], - fontSize: 14, - ), + style: TextStyle(color: Colors.grey[400], fontSize: 14), ), ), Expanded( diff --git a/lib/widgets/horizontal_scroll_with_arrows.dart b/lib/widgets/horizontal_scroll_with_arrows.dart index 8a01d067..1e1a0475 100644 --- a/lib/widgets/horizontal_scroll_with_arrows.dart +++ b/lib/widgets/horizontal_scroll_with_arrows.dart @@ -56,9 +56,9 @@ class _HorizontalScrollWithArrowsState void _scrollLeft() { final position = _scrollController.position; - final targetScroll = (position.pixels - - (position.viewportDimension * widget.scrollAmount)) - .clamp(0.0, position.maxScrollExtent); + final targetScroll = + (position.pixels - (position.viewportDimension * widget.scrollAmount)) + .clamp(0.0, position.maxScrollExtent); _scrollController.animateTo( targetScroll, @@ -69,9 +69,9 @@ class _HorizontalScrollWithArrowsState void _scrollRight() { final position = _scrollController.position; - final targetScroll = (position.pixels + - (position.viewportDimension * widget.scrollAmount)) - .clamp(0.0, position.maxScrollExtent); + final targetScroll = + (position.pixels + (position.viewportDimension * widget.scrollAmount)) + .clamp(0.0, position.maxScrollExtent); _scrollController.animateTo( targetScroll, @@ -145,10 +145,7 @@ class _NavigationArrow extends StatefulWidget { final IconData icon; final VoidCallback onPressed; - const _NavigationArrow({ - required this.icon, - required this.onPressed, - }); + const _NavigationArrow({required this.icon, required this.onPressed}); @override State<_NavigationArrow> createState() => _NavigationArrowState(); @@ -182,11 +179,7 @@ class _NavigationArrowState extends State<_NavigationArrow> { ), ], ), - child: Icon( - widget.icon, - color: Colors.white, - size: 32, - ), + child: Icon(widget.icon, color: Colors.white, size: 32), ), ), ); diff --git a/lib/widgets/media_card.dart b/lib/widgets/media_card.dart index fe03780a..0801f86e 100644 --- a/lib/widgets/media_card.dart +++ b/lib/widgets/media_card.dart @@ -181,34 +181,31 @@ class _MediaCardGrid extends StatelessWidget { item.displaySubtitle!, maxLines: 1, overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall - ?.copyWith( - color: tokens(context).textMuted, - fontSize: 11, - height: 1.1, - ), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: tokens(context).textMuted, + fontSize: 11, + height: 1.1, + ), ) else if (item.parentTitle != null) Text( item.parentTitle!, maxLines: 1, overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall - ?.copyWith( - color: tokens(context).textMuted, - fontSize: 11, - height: 1.1, - ), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: tokens(context).textMuted, + fontSize: 11, + height: 1.1, + ), ) else if (item.year != null) Text( '${item.year}', - style: Theme.of(context).textTheme.bodySmall - ?.copyWith( - color: tokens(context).textMuted, - fontSize: 11, - height: 1.1, - ), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: tokens(context).textMuted, + fontSize: 11, + height: 1.1, + ), ), ], ), @@ -473,10 +470,12 @@ class _MediaCardList extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: tokens(context).textMuted.withValues(alpha: 0.9), - fontSize: _metadataFontSize, - fontWeight: FontWeight.w500, - ), + color: tokens( + context, + ).textMuted.withValues(alpha: 0.9), + fontSize: _metadataFontSize, + fontWeight: FontWeight.w500, + ), ), const SizedBox(height: 2), ], @@ -487,9 +486,11 @@ class _MediaCardList extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: tokens(context).textMuted.withValues(alpha: 0.85), - fontSize: _subtitleFontSize, - ), + color: tokens( + context, + ).textMuted.withValues(alpha: 0.85), + fontSize: _subtitleFontSize, + ), ), const SizedBox(height: 4), ], @@ -500,10 +501,12 @@ class _MediaCardList extends StatelessWidget { maxLines: _summaryMaxLines, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: tokens(context).textMuted.withValues(alpha: 0.7), - fontSize: _summaryFontSize, - height: 1.3, - ), + color: tokens( + context, + ).textMuted.withValues(alpha: 0.7), + fontSize: _summaryFontSize, + height: 1.3, + ), ), ], ], diff --git a/lib/widgets/media_context_menu.dart b/lib/widgets/media_context_menu.dart index f3dcddde..a186469d 100644 --- a/lib/widgets/media_context_menu.dart +++ b/lib/widgets/media_context_menu.dart @@ -102,7 +102,11 @@ class _MediaContextMenuState extends State { if ((itemType == 'episode' || itemType == 'season') && widget.metadata.grandparentTitle != null) { menuActions.add( - _MenuAction(value: 'series', icon: Icons.tv, label: t.mediaMenu.goToSeries), + _MenuAction( + value: 'series', + icon: Icons.tv, + label: t.mediaMenu.goToSeries, + ), ); } @@ -262,7 +266,9 @@ class _MediaContextMenuState extends State { } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))), + SnackBar( + content: Text(t.messages.errorLoading(error: e.toString())), + ), ); } } @@ -312,9 +318,9 @@ class _MediaContextMenuState extends State { } } catch (e) { if (context.mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(t.messages.errorLoading(error: e.toString())))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))), + ); } } } @@ -397,14 +403,15 @@ class _MediaContextMenuState extends State { } if (context.mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(t.messages.errorLoadingFileInfo(error: e.toString())))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(t.messages.errorLoadingFileInfo(error: e.toString())), + ), + ); } } } - @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/widgets/pin_entry_dialog.dart b/lib/widgets/pin_entry_dialog.dart index aa4af9e5..c66e9167 100644 --- a/lib/widgets/pin_entry_dialog.dart +++ b/lib/widgets/pin_entry_dialog.dart @@ -125,7 +125,9 @@ class _PinEntryDialogState extends State _obscureText = !_obscureText; }); }, - tooltip: _obscureText ? t.pinEntry.showPin : t.pinEntry.hidePin, + tooltip: _obscureText + ? t.pinEntry.showPin + : t.pinEntry.hidePin, ), ), onSubmitted: (_) => _submit(), diff --git a/lib/widgets/sort_bottom_sheet.dart b/lib/widgets/sort_bottom_sheet.dart index 61f0c964..6d5908fd 100644 --- a/lib/widgets/sort_bottom_sheet.dart +++ b/lib/widgets/sort_bottom_sheet.dart @@ -95,10 +95,7 @@ class _SortBottomSheetState extends State { groupValue: _currentSort, onChanged: (PlexSort? value) { if (value != null) { - _handleSortChange( - value, - value.defaultDirection == 'desc', - ); + _handleSortChange(value, value.defaultDirection == 'desc'); } }, child: ListView.builder( @@ -124,7 +121,10 @@ class _SortBottomSheetState extends State { ), ButtonSegment( value: true, - icon: Icon(Icons.arrow_downward, size: 16), + icon: Icon( + Icons.arrow_downward, + size: 16, + ), ), ], selected: {_currentDescending}, @@ -135,12 +135,12 @@ class _SortBottomSheetState extends State { ], ) : null, - leading: Radio( - value: sort, - toggleable: false, - ), + leading: Radio(value: sort, toggleable: false), onTap: () { - _handleSortChange(sort, sort.defaultDirection == 'desc'); + _handleSortChange( + sort, + sort.defaultDirection == 'desc', + ); }, ); }, diff --git a/lib/widgets/video_controls/sheets/audio_track_sheet.dart b/lib/widgets/video_controls/sheets/audio_track_sheet.dart index ef372e37..ba7cd991 100644 --- a/lib/widgets/video_controls/sheets/audio_track_sheet.dart +++ b/lib/widgets/video_controls/sheets/audio_track_sheet.dart @@ -6,10 +6,7 @@ import '../../../i18n/strings.g.dart'; class AudioTrackSheet extends StatelessWidget { final Player player; - const AudioTrackSheet({ - super.key, - required this.player, - }); + const AudioTrackSheet({super.key, required this.player}); static BoxConstraints getBottomSheetConstraints(BuildContext context) { final size = MediaQuery.of(context).size; @@ -123,15 +120,13 @@ class AudioTrackSheet extends StatelessWidget { title: Text( label, style: TextStyle( - color: - isSelected ? Colors.blue : Colors.white, + color: isSelected + ? Colors.blue + : Colors.white, ), ), trailing: isSelected - ? const Icon( - Icons.check, - color: Colors.blue, - ) + ? const Icon(Icons.check, color: Colors.blue) : null, onTap: () { player.setAudioTrack(audioTrack); diff --git a/lib/widgets/video_controls/sheets/chapter_sheet.dart b/lib/widgets/video_controls/sheets/chapter_sheet.dart index 706ab4ba..f77ea04a 100644 --- a/lib/widgets/video_controls/sheets/chapter_sheet.dart +++ b/lib/widgets/video_controls/sheets/chapter_sheet.dart @@ -73,7 +73,8 @@ class ChapterSheet extends StatelessWidget { for (int i = 0; i < chapters.length; i++) { final chapter = chapters[i]; final startMs = chapter.startTimeOffset ?? 0; - final endMs = chapter.endTimeOffset ?? + final endMs = + chapter.endTimeOffset ?? (i < chapters.length - 1 ? chapters[i + 1].startTimeOffset ?? 0 : double.maxFinite.toInt()); @@ -142,41 +143,43 @@ class ChapterSheet extends StatelessWidget { child: Consumer( builder: (context, clientProvider, child) { - final client = clientProvider.client; - if (client == null) { - return const Icon( - Icons.image, - color: Colors.white54, - size: 34, - ); - } - return Image.network( - client.getThumbnailUrl( - chapter.thumb, - ), - width: 60, - height: 34, - fit: BoxFit.cover, - errorBuilder: ( - context, - error, - stackTrace, - ) => - const Icon( - Icons.image, - color: Colors.white54, - size: 34, - ), - ); - }, + final client = + clientProvider.client; + if (client == null) { + return const Icon( + Icons.image, + color: Colors.white54, + size: 34, + ); + } + return Image.network( + client.getThumbnailUrl( + chapter.thumb, + ), + width: 60, + height: 34, + fit: BoxFit.cover, + errorBuilder: + ( + context, + error, + stackTrace, + ) => const Icon( + Icons.image, + color: Colors.white54, + size: 34, + ), + ); + }, ), ), if (isCurrentChapter) Positioned.fill( child: Container( decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(4), + borderRadius: BorderRadius.circular( + 4, + ), border: Border.all( color: Colors.blue, width: 2, @@ -190,8 +193,9 @@ class ChapterSheet extends StatelessWidget { title: Text( chapter.label, style: TextStyle( - color: - isCurrentChapter ? Colors.blue : Colors.white, + color: isCurrentChapter + ? Colors.blue + : Colors.white, fontWeight: isCurrentChapter ? FontWeight.bold : FontWeight.normal, diff --git a/lib/widgets/video_controls/sheets/playback_speed_sheet.dart b/lib/widgets/video_controls/sheets/playback_speed_sheet.dart index c82204aa..ea96e225 100644 --- a/lib/widgets/video_controls/sheets/playback_speed_sheet.dart +++ b/lib/widgets/video_controls/sheets/playback_speed_sheet.dart @@ -5,10 +5,7 @@ import 'package:media_kit/media_kit.dart'; class PlaybackSpeedSheet extends StatelessWidget { final Player player; - const PlaybackSpeedSheet({ - super.key, - required this.player, - }); + const PlaybackSpeedSheet({super.key, required this.player}); static BoxConstraints getBottomSheetConstraints(BuildContext context) { final size = MediaQuery.of(context).size; diff --git a/lib/widgets/video_controls/sheets/sleep_timer_sheet.dart b/lib/widgets/video_controls/sheets/sleep_timer_sheet.dart index d8564c26..b2786ba3 100644 --- a/lib/widgets/video_controls/sheets/sleep_timer_sheet.dart +++ b/lib/widgets/video_controls/sheets/sleep_timer_sheet.dart @@ -37,10 +37,8 @@ class SleepTimerSheet extends StatelessWidget { backgroundColor: Colors.grey[900], isScrollControlled: true, constraints: getBottomSheetConstraints(context), - builder: (context) => SleepTimerSheet( - player: player, - defaultDuration: defaultDuration, - ), + builder: (context) => + SleepTimerSheet(player: player, defaultDuration: defaultDuration), ); } @@ -86,7 +84,9 @@ class SleepTimerSheet extends StatelessWidget { sleepTimer.isActive ? Icons.bedtime : Icons.bedtime_outlined, - color: sleepTimer.isActive ? Colors.amber : Colors.white, + color: sleepTimer.isActive + ? Colors.amber + : Colors.white, ), const SizedBox(width: 12), const Text( @@ -136,12 +136,15 @@ class SleepTimerSheet extends StatelessWidget { children: [ OutlinedButton.icon( icon: const Icon(Icons.add), - label: Text(t.videoControls.addTime(amount: "15", unit: " min")), + label: Text( + t.videoControls.addTime( + amount: "15", + unit: " min", + ), + ), style: OutlinedButton.styleFrom( foregroundColor: Colors.white, - side: const BorderSide( - color: Colors.white54, - ), + side: const BorderSide(color: Colors.white54), ), onPressed: () { sleepTimer.extendTimer( @@ -180,10 +183,7 @@ class SleepTimerSheet extends StatelessWidget { : '${(minutes / 60).toStringAsFixed(minutes % 60 == 0 ? 0 : 1)} ${minutes == 60 ? 'hour' : 'hours'}'; return ListTile( - leading: const Icon( - Icons.timer, - color: Colors.white70, - ), + leading: const Icon(Icons.timer, color: Colors.white70), title: Text( label, style: const TextStyle( @@ -192,31 +192,30 @@ class SleepTimerSheet extends StatelessWidget { ), ), onTap: () { - sleepTimer.startTimer( - Duration(minutes: minutes), - () { - // Pause playback when timer completes - player.pause(); + sleepTimer.startTimer(Duration(minutes: minutes), () { + // Pause playback when timer completes + player.pause(); - // Show a snackbar notification - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Sleep timer completed - playback paused', - ), - duration: Duration(seconds: 3), + // Show a snackbar notification + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Sleep timer completed - playback paused', ), - ); - } - }, - ); + duration: Duration(seconds: 3), + ), + ); + } + }); Navigator.pop(context); // Show confirmation snackbar ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(t.messages.sleepTimerSet(label: label)), + content: Text( + t.messages.sleepTimerSet(label: label), + ), duration: const Duration(seconds: 2), ), ); diff --git a/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart b/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart index e03e9fa0..ab3858b7 100644 --- a/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart +++ b/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart @@ -6,10 +6,7 @@ import '../../../i18n/strings.g.dart'; class SubtitleTrackSheet extends StatelessWidget { final Player player; - const SubtitleTrackSheet({ - super.key, - required this.player, - }); + const SubtitleTrackSheet({super.key, required this.player}); static BoxConstraints getBottomSheetConstraints(BuildContext context) { final size = MediaQuery.of(context).size; @@ -115,9 +112,7 @@ class SubtitleTrackSheet extends StatelessWidget { ) : null, onTap: () { - player.setSubtitleTrack( - SubtitleTrack.no(), - ); + player.setSubtitleTrack(SubtitleTrack.no()); Navigator.pop(context); }, ); @@ -162,15 +157,13 @@ class SubtitleTrackSheet extends StatelessWidget { title: Text( label, style: TextStyle( - color: - isSelected ? Colors.blue : Colors.white, + color: isSelected + ? Colors.blue + : Colors.white, ), ), trailing: isSelected - ? const Icon( - Icons.check, - color: Colors.blue, - ) + ? const Icon(Icons.check, color: Colors.blue) : null, onTap: () { player.setSubtitleTrack(subtitle); diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index 67d322ca..fb78e421 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -321,7 +321,8 @@ class _VideoSettingsSheetState extends State { stream: widget.player.stream.audioDevice, initialData: widget.player.state.audioDevice, builder: (context, snapshot) { - final currentDevice = snapshot.data ?? widget.player.state.audioDevice; + final currentDevice = + snapshot.data ?? widget.player.state.audioDevice; final deviceLabel = currentDevice.description.isEmpty ? currentDevice.name : currentDevice.description; @@ -338,7 +339,10 @@ class _VideoSettingsSheetState extends State { Flexible( child: Text( deviceLabel, - style: const TextStyle(color: Colors.white70, fontSize: 14), + style: const TextStyle( + color: Colors.white70, + fontSize: 14, + ), overflow: TextOverflow.ellipsis, ), ), @@ -432,7 +436,9 @@ class _VideoSettingsSheetState extends State { children: [ OutlinedButton.icon( icon: const Icon(Icons.add), - label: Text(t.videoControls.addTime(amount: "15", unit: " min")), + label: Text( + t.videoControls.addTime(amount: "15", unit: " min"), + ), style: OutlinedButton.styleFrom( foregroundColor: Colors.white, side: const BorderSide(color: Colors.white54), @@ -552,7 +558,8 @@ class _VideoSettingsSheetState extends State { stream: widget.player.stream.audioDevice, initialData: widget.player.state.audioDevice, builder: (context, selectedSnapshot) { - final currentDevice = selectedSnapshot.data ?? widget.player.state.audioDevice; + final currentDevice = + selectedSnapshot.data ?? widget.player.state.audioDevice; return ListView.builder( itemCount: devices.length, diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index 279055b9..c3bc93b4 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -1602,9 +1602,9 @@ class _PlexVideoControlsState extends State } } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(t.messages.errorLoading(error: e.toString())))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))), + ); } } } diff --git a/lib/widgets/video_controls/widgets/sync_offset_control.dart b/lib/widgets/video_controls/widgets/sync_offset_control.dart index 58d68f74..16a8a7ea 100644 --- a/lib/widgets/video_controls/widgets/sync_offset_control.dart +++ b/lib/widgets/video_controls/widgets/sync_offset_control.dart @@ -103,7 +103,10 @@ class _SyncOffsetControlState extends State { // Slider Row( children: [ - Text(t.videoControls.minusTime(amount: "2", unit: "s"), style: const TextStyle(color: Colors.white70)), + Text( + t.videoControls.minusTime(amount: "2", unit: "s"), + style: const TextStyle(color: Colors.white70), + ), Expanded( child: Slider( value: _currentOffset, @@ -122,7 +125,10 @@ class _SyncOffsetControlState extends State { }, ), ), - Text(t.videoControls.addTime(amount: "2", unit: "s"), style: const TextStyle(color: Colors.white70)), + Text( + t.videoControls.addTime(amount: "2", unit: "s"), + style: const TextStyle(color: Colors.white70), + ), ], ), const SizedBox(height: 24),