From 2571a6c38b52fd8ebef267f5989de79f9e490428 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:16:12 +0100 Subject: [PATCH] refactor: adjust video controls button sizes --- .../video_controls/video_control_button.dart | 46 ++++++++++++++ .../video_controls/video_controls.dart | 60 ++++++++----------- 2 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 lib/widgets/video_controls/video_control_button.dart diff --git a/lib/widgets/video_controls/video_control_button.dart b/lib/widgets/video_controls/video_control_button.dart new file mode 100644 index 00000000..c87b50be --- /dev/null +++ b/lib/widgets/video_controls/video_control_button.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +/// A standardized button for video player controls with improved tap targets. +/// +/// This widget ensures consistent tap target sizing across all video control +/// buttons without changing their visual appearance. The larger tap area makes +/// buttons easier to interact with, especially on mobile devices. +class VideoControlButton extends StatelessWidget { + /// The icon to display in the button. + final IconData icon; + + /// Called when the button is tapped. + final VoidCallback? onPressed; + + /// The color of the icon. Defaults to white, or amber if [isActive] is true. + final Color? color; + + /// Optional tooltip text shown on hover or long press. + final String? tooltip; + + /// Whether this button represents an active state (e.g., a feature is enabled). + /// When true, the icon color defaults to amber instead of white. + final bool isActive; + + const VideoControlButton({ + super.key, + required this.icon, + required this.onPressed, + this.color, + this.tooltip, + this.isActive = false, + }); + + @override + Widget build(BuildContext context) { + // Determine the effective color: explicit color > active amber > default white + final effectiveColor = color ?? (isActive ? Colors.amber : Colors.white); + + return IconButton( + icon: Icon(icon, color: effectiveColor), + onPressed: onPressed, + tooltip: tooltip, + constraints: const BoxConstraints(minWidth: 40, minHeight: 56), + ); + } +} diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index c1ad99e4..122cfdf8 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -23,6 +23,7 @@ import 'sheets/chapter_sheet.dart'; import 'sheets/subtitle_track_sheet.dart'; import 'sheets/version_sheet.dart'; import 'sheets/video_settings_sheet.dart'; +import 'video_control_button.dart'; /// Custom video controls builder for Plex with chapter, audio, and subtitle support Widget plexVideoControlsBuilder( @@ -374,9 +375,11 @@ class _PlexVideoControlsState extends State initialData: widget.player.state.tracks, builder: (context, snapshot) { final tracks = snapshot.data; - return Row( - mainAxisSize: MainAxisSize.min, - children: [ + return IntrinsicHeight( + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ // Unified settings button (speed, sleep timer, audio sync, subtitle sync) ListenableBuilder( listenable: SleepTimerService(), @@ -386,11 +389,9 @@ class _PlexVideoControlsState extends State sleepTimer.isActive || _audioSyncOffset != 0 || _subtitleSyncOffset != 0; - return IconButton( - icon: Icon( - Icons.tune, - color: isActive ? Colors.amber : Colors.white, - ), + return VideoControlButton( + icon: Icons.tune, + isActive: isActive, onPressed: () async { await VideoSettingsSheet.show( context, @@ -407,19 +408,18 @@ class _PlexVideoControlsState extends State }, ), if (_hasMultipleAudioTracks(tracks)) - IconButton( - icon: const Icon(Icons.audiotrack, color: Colors.white), + VideoControlButton( + icon: Icons.audiotrack, onPressed: () => AudioTrackSheet.show(context, widget.player), ), if (_hasSubtitles(tracks)) - IconButton( - icon: const Icon(Icons.subtitles, color: Colors.white), - onPressed: () => - SubtitleTrackSheet.show(context, widget.player), + VideoControlButton( + icon: Icons.subtitles, + onPressed: () => SubtitleTrackSheet.show(context, widget.player), ), if (_chapters.isNotEmpty) - IconButton( - icon: const Icon(Icons.video_library, color: Colors.white), + VideoControlButton( + icon: Icons.video_library, onPressed: () => ChapterSheet.show( context, widget.player, @@ -428,8 +428,8 @@ class _PlexVideoControlsState extends State ), ), if (widget.availableVersions.length > 1) - IconButton( - icon: const Icon(Icons.video_file, color: Colors.white), + VideoControlButton( + icon: Icons.video_file, onPressed: () => VersionSheet.show( context, widget.availableVersions, @@ -439,34 +439,26 @@ class _PlexVideoControlsState extends State ), // BoxFit mode cycle button if (widget.onCycleBoxFitMode != null) - IconButton( - icon: Icon( - _getBoxFitIcon(widget.boxFitMode), - color: Colors.white, - ), + VideoControlButton( + icon: _getBoxFitIcon(widget.boxFitMode), tooltip: _getBoxFitTooltip(widget.boxFitMode), onPressed: widget.onCycleBoxFitMode, ), // Rotation lock toggle (mobile only) if (PlatformDetector.isMobile(context)) - IconButton( - icon: Icon( - _isRotationLocked ? Icons.screen_lock_rotation : Icons.screen_rotation, - color: Colors.white, - ), + VideoControlButton( + icon: _isRotationLocked ? Icons.screen_lock_rotation : Icons.screen_rotation, tooltip: _isRotationLocked ? 'Unlock rotation' : 'Lock rotation', onPressed: _toggleRotationLock, ), // Fullscreen toggle (desktop only) if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) - IconButton( - icon: Icon( - _isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen, - color: Colors.white, - ), + VideoControlButton( + icon: _isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen, onPressed: _toggleFullscreen, ), - ], + ], + ), ); }, );