fix: use theme colors in video control sheets for light mode support

This commit is contained in:
edde746
2026-02-24 23:40:08 +01:00
parent af41d937c3
commit e0489b68d5
11 changed files with 81 additions and 75 deletions
@@ -21,7 +21,7 @@ class TrackSelectionHelper {
/// Build a centered empty state widget
static Widget buildEmptyState<T>() {
return Center(
child: Text(getEmptyMessage<T>(), style: const TextStyle(color: Colors.white70)),
child: Text(getEmptyMessage<T>()),
);
}
@@ -63,7 +63,7 @@ class TrackSelectionHelper {
}) {
return FocusableListTile(
focusNode: focusNode,
title: Text(label, style: TextStyle(color: isSelected ? Colors.blue : Colors.white)),
title: Text(label, style: TextStyle(color: isSelected ? Colors.blue : null)),
trailing: isSelected ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null,
onTap: onTap,
);
@@ -26,7 +26,7 @@ class BaseVideoControlSheet extends StatelessWidget {
Widget content = Column(
children: [
VideoSheetHeader(title: title, icon: icon, iconColor: iconColor, onBack: onBack),
const Divider(color: Colors.white24, height: 1),
Divider(color: Theme.of(context).dividerColor, height: 1),
Expanded(child: child),
],
);
@@ -7,6 +7,7 @@ import '../../../mpv/mpv.dart';
import '../../../services/plex_client.dart';
import '../../../services/download_storage_service.dart';
import '../../../models/plex_media_info.dart';
import '../../../theme/mono_tokens.dart';
import '../../../utils/formatters.dart';
import '../../../utils/provider_extensions.dart';
import '../../../widgets/focusable_list_tile.dart';
@@ -76,7 +77,7 @@ class _ChapterSheetState extends State<ChapterSheet> {
content = const Center(child: CircularProgressIndicator());
} else if (widget.chapters.isEmpty) {
content = Center(
child: Text(t.videoControls.noChaptersAvailable, style: const TextStyle(color: Colors.white70)),
child: Text(t.videoControls.noChaptersAvailable, style: TextStyle(color: tokens(context).textMuted)),
);
} else {
content = ListView.builder(
@@ -92,8 +93,11 @@ class _ChapterSheetState extends State<ChapterSheet> {
return FocusableListTile(
leading: chapter.thumb != null
? Stack(
children: [
? SizedBox(
width: 60,
height: 34,
child: Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: PlexOptimizedImage.thumb(
@@ -116,20 +120,21 @@ class _ChapterSheetState extends State<ChapterSheet> {
),
),
),
],
],
),
)
: null,
title: Text(
chapter.label,
style: TextStyle(
color: isCurrentChapter ? Colors.blue : Colors.white,
color: isCurrentChapter ? Colors.blue : null,
fontWeight: isCurrentChapter ? FontWeight.bold : FontWeight.normal,
),
),
subtitle: Text(
formatDurationTimestamp(chapter.startTime),
style: TextStyle(
color: isCurrentChapter ? Colors.blue.withValues(alpha: 0.7) : Colors.white70,
color: isCurrentChapter ? Colors.blue.withValues(alpha: 0.7) : tokens(context).textMuted,
fontSize: 12,
),
),
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
import '../../../i18n/strings.g.dart';
import '../../../models/plex_metadata.dart';
import '../../../providers/playback_state_provider.dart';
import '../../../theme/mono_tokens.dart';
import '../../../utils/provider_extensions.dart';
import '../../../widgets/focusable_list_tile.dart';
import '../../../widgets/overlay_sheet.dart';
@@ -28,7 +29,7 @@ class QueueSheet extends StatelessWidget {
Widget content;
if (items.isEmpty) {
content = Center(
child: Text(t.videoControls.noQueueItems, style: const TextStyle(color: Colors.white70)),
child: Text(t.videoControls.noQueueItems, style: TextStyle(color: tokens(context).textMuted)),
);
} else {
// Find current index for initial scroll
@@ -46,7 +47,7 @@ class QueueSheet extends StatelessWidget {
title: Text(
item.title,
style: TextStyle(
color: isCurrent ? Colors.blue : Colors.white,
color: isCurrent ? Colors.blue : null,
fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal,
),
maxLines: 1,
@@ -55,7 +56,7 @@ class QueueSheet extends StatelessWidget {
subtitle: Text(
_buildSubtitle(item),
style: TextStyle(
color: isCurrent ? Colors.blue.withValues(alpha: 0.7) : Colors.white70,
color: isCurrent ? Colors.blue.withValues(alpha: 0.7) : tokens(context).textMuted,
fontSize: 12,
),
maxLines: 1,
@@ -88,30 +89,34 @@ class QueueSheet extends StatelessWidget {
// Try to get client for thumbnails, may fail in offline mode
final client = _tryGetClient(context, item);
return Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: PlexOptimizedImage.thumb(
client: client,
imagePath: item.thumb,
width: 60,
height: 34,
fit: BoxFit.cover,
errorWidget: (context, url, error) =>
const AppIcon(Symbols.image_rounded, fill: 1, color: Colors.white54, size: 34),
),
),
if (isCurrent)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
border: const Border.fromBorderSide(BorderSide(color: Colors.blue, width: 2)),
),
return SizedBox(
width: 60,
height: 34,
child: Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: PlexOptimizedImage.thumb(
client: client,
imagePath: item.thumb,
width: 60,
height: 34,
fit: BoxFit.cover,
errorWidget: (context, url, error) =>
const AppIcon(Symbols.image_rounded, fill: 1, color: Colors.white54, size: 34),
),
),
],
if (isCurrent)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
border: const Border.fromBorderSide(BorderSide(color: Colors.blue, width: 2)),
),
),
),
],
),
);
}
@@ -36,7 +36,7 @@ class _VersionSheetState extends State<VersionSheet> {
final isSelected = index == widget.selectedMediaIndex;
return FocusableListTile(
title: Text(version.displayLabel, style: TextStyle(color: isSelected ? Colors.blue : Colors.white)),
title: Text(version.displayLabel, style: TextStyle(color: isSelected ? Colors.blue : null)),
trailing: isSelected ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null,
onTap: () {
OverlaySheetController.of(context).close();
@@ -14,6 +14,7 @@ import '../../../services/shader_service.dart';
import '../../../services/sleep_timer_service.dart';
import '../../../utils/formatters.dart';
import '../../../utils/platform_detector.dart';
import '../../../theme/mono_tokens.dart';
import '../../../widgets/focusable_list_tile.dart';
import '../../../widgets/overlay_sheet.dart';
import '../widgets/sync_offset_control.dart';
@@ -43,21 +44,22 @@ class _SettingsMenuItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final t = tokens(context);
final valueWidget = Text(
valueText,
style: TextStyle(color: isHighlighted ? Colors.amber : Colors.white70, fontSize: 14),
style: TextStyle(color: isHighlighted ? Colors.amber : t.textMuted, fontSize: 14),
overflow: allowValueOverflow ? TextOverflow.ellipsis : null,
);
return FocusableListTile(
leading: AppIcon(icon, fill: 1, color: isHighlighted ? Colors.amber : Colors.white70),
title: Text(title, style: const TextStyle(color: Colors.white)),
leading: AppIcon(icon, fill: 1, color: isHighlighted ? Colors.amber : t.textMuted),
title: Text(title),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (allowValueOverflow) Flexible(child: valueWidget) else valueWidget,
const SizedBox(width: 8),
const AppIcon(Symbols.chevron_right_rounded, fill: 1, color: Colors.white70),
AppIcon(Symbols.chevron_right_rounded, fill: 1, color: t.textMuted),
],
),
onTap: onTap,
@@ -286,8 +288,8 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
// HDR Toggle (iOS, macOS, and Windows)
if (Platform.isIOS || Platform.isMacOS || Platform.isWindows)
ListTile(
leading: AppIcon(Symbols.hdr_strong_rounded, fill: 1, color: _enableHDR ? Colors.amber : Colors.white70),
title: Text(t.videoSettings.hdr, style: const TextStyle(color: Colors.white)),
leading: AppIcon(Symbols.hdr_strong_rounded, fill: 1, color: _enableHDR ? Colors.amber : tokens(context).textMuted),
title: Text(t.videoSettings.hdr),
trailing: Switch(value: _enableHDR, onChanged: (_) => _toggleHDR(), activeThumbColor: Colors.amber),
onTap: _toggleHDR,
),
@@ -297,9 +299,9 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
leading: AppIcon(
Symbols.skip_next_rounded,
fill: 1,
color: _autoPlayNextEpisode ? Colors.amber : Colors.white70,
color: _autoPlayNextEpisode ? Colors.amber : tokens(context).textMuted,
),
title: Text(t.videoControls.autoPlayNext, style: const TextStyle(color: Colors.white)),
title: Text(t.videoControls.autoPlayNext),
trailing: Switch(
value: _autoPlayNextEpisode,
onChanged: (_) => _toggleAutoPlayNextEpisode(),
@@ -345,9 +347,9 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
leading: AppIcon(
Symbols.blur_on,
fill: 1,
color: widget.isAmbientLightingEnabled ? Colors.amber : Colors.white70,
color: widget.isAmbientLightingEnabled ? Colors.amber : tokens(context).textMuted,
),
title: Text(t.videoControls.ambientLighting, style: const TextStyle(color: Colors.white)),
title: Text(t.videoControls.ambientLighting),
trailing: Switch(
value: widget.isAmbientLightingEnabled,
onChanged: (_) {
@@ -367,9 +369,9 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
leading: AppIcon(
Symbols.analytics_rounded,
fill: 1,
color: _showPerformanceOverlay ? Colors.amber : Colors.white70,
color: _showPerformanceOverlay ? Colors.amber : tokens(context).textMuted,
),
title: Text(t.videoSettings.performanceOverlay, style: const TextStyle(color: Colors.white)),
title: Text(t.videoSettings.performanceOverlay),
trailing: Switch(
value: _showPerformanceOverlay,
onChanged: (_) => _togglePerformanceOverlay(),
@@ -397,7 +399,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
final label = speed == 1.0 ? 'Normal' : '${speed.toStringAsFixed(2)}x';
return ListTile(
title: Text(label, style: TextStyle(color: isSelected ? Colors.blue : Colors.white)),
title: Text(label, style: TextStyle(color: isSelected ? Colors.blue : null)),
trailing: isSelected ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null,
onTap: () async {
widget.player.setRate(speed);
@@ -521,7 +523,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Text(
_formatBackend(entry.key),
style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12, fontWeight: FontWeight.w600),
style: TextStyle(color: tokens(context).textMuted, fontSize: 12, fontWeight: FontWeight.w600),
),
),
for (final d in entry.value) _buildDeviceTile(d, currentDevice),
@@ -546,7 +548,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
final label = device.description.isEmpty ? device.name : device.description;
return ListTile(
title: Text(label, style: TextStyle(color: isSelected ? Colors.blue : Colors.white)),
title: Text(label, style: TextStyle(color: isSelected ? Colors.blue : null)),
trailing: isSelected ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null,
onTap: () {
widget.player.setAudioDevice(device);
@@ -570,9 +572,9 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
final isSelected = preset.id == currentPreset.id;
return FocusableListTile(
title: Text(preset.name, style: TextStyle(color: isSelected ? Colors.amber : Colors.white)),
title: Text(preset.name, style: TextStyle(color: isSelected ? Colors.amber : null)),
subtitle: _getShaderSubtitle(preset) != null
? Text(_getShaderSubtitle(preset)!, style: const TextStyle(color: Colors.white54, fontSize: 12))
? Text(_getShaderSubtitle(preset)!, style: TextStyle(color: tokens(context).textMuted, fontSize: 12))
: null,
trailing: isSelected ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.amber) : null,
onTap: () async {
@@ -624,7 +626,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
iconColor: () {
if (isIconActive) return Colors.amber;
if (_currentView == _SettingsView.shader && isShaderActive) return Colors.amber;
return Colors.white;
return null;
}(),
onBack: _currentView != _SettingsView.menu ? _navigateBack : null,
child: () {
@@ -21,10 +21,10 @@ class VideoSheetHeader extends StatelessWidget {
return BottomSheetHeader(
title: title,
icon: icon,
iconColor: iconColor ?? Colors.white,
iconColor: iconColor,
onBack: onBack,
onClose: onClose,
titleStyle: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
titleStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
showBorder: false,
);
}
@@ -27,7 +27,7 @@ class SleepTimerActiveStatus extends StatelessWidget {
const SizedBox(height: 8),
Text(
t.videoControls.playbackWillPauseIn(duration: formatDurationWithSeconds(remainingTime)),
style: const TextStyle(color: Colors.white70, fontSize: 14),
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 14),
),
const SizedBox(height: 16),
Row(
@@ -37,8 +37,8 @@ class SleepTimerActiveStatus extends StatelessWidget {
icon: const AppIcon(Symbols.add_rounded, fill: 1),
label: Text(t.videoControls.addTime(amount: "15", unit: " min")),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: const BorderSide(color: Colors.white54),
foregroundColor: Theme.of(context).colorScheme.onSurface,
side: BorderSide(color: Theme.of(context).colorScheme.outline),
),
onPressed: () {
sleepTimer.extendTimer(const Duration(minutes: 15));
@@ -31,7 +31,7 @@ class SleepTimerContent extends StatelessWidget {
children: [
if (sleepTimer.isActive && remainingTime != null) ...[
SleepTimerActiveStatus(sleepTimer: sleepTimer, remainingTime: remainingTime, onCancel: onCancel),
const Divider(color: Colors.white24, height: 1),
Divider(color: Theme.of(context).dividerColor, height: 1),
],
Expanded(
child: SleepTimerDurationList(player: player, sleepTimer: sleepTimer, defaultDuration: defaultDuration),
@@ -36,11 +36,8 @@ class SleepTimerDurationList extends StatelessWidget {
);
return ListTile(
leading: const AppIcon(Symbols.timer_rounded, fill: 1, color: Colors.white70),
title: Text(
label,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.normal),
),
leading: AppIcon(Symbols.timer_rounded, fill: 1, color: Theme.of(context).colorScheme.onSurfaceVariant),
title: Text(label),
onTap: () {
sleepTimer.startTimer(Duration(minutes: minutes), () {
// Pause playback when timer completes
@@ -6,6 +6,7 @@ import 'package:material_symbols_icons/symbols.dart';
import '../../../mpv/mpv.dart';
import '../../../i18n/strings.g.dart';
import '../../../theme/mono_tokens.dart';
import '../../../utils/formatters.dart';
/// Reusable widget for adjusting sync offsets (audio or subtitle)
@@ -146,8 +147,8 @@ class _SyncOffsetControlState extends State<SyncOffsetControl> {
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(color: Colors.grey[800], borderRadius: const BorderRadius.all(Radius.circular(8))),
child: Icon(icon, color: Colors.white, size: 28),
decoration: BoxDecoration(color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: const BorderRadius.all(Radius.circular(8))),
child: Icon(icon, color: tokens(context).text, size: 28),
),
);
}
@@ -165,10 +166,10 @@ class _SyncOffsetControlState extends State<SyncOffsetControl> {
// Current offset display
Text(
formatSyncOffset(_currentOffset),
style: const TextStyle(color: Colors.white, fontSize: 48, fontWeight: FontWeight.bold),
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(_getDescriptionText(), style: const TextStyle(color: Colors.white70, fontSize: 16)),
Text(_getDescriptionText(), style: TextStyle(color: tokens(context).textMuted, fontSize: 16)),
const SizedBox(height: 48),
// Slider with +/- buttons
Row(
@@ -183,7 +184,7 @@ class _SyncOffsetControlState extends State<SyncOffsetControl> {
// Slider section
Text(
t.videoControls.minusTime(amount: "5", unit: "s"),
style: const TextStyle(color: Colors.white70),
style: TextStyle(color: tokens(context).textMuted),
),
Expanded(
child: Slider(
@@ -192,7 +193,7 @@ class _SyncOffsetControlState extends State<SyncOffsetControl> {
max: _sliderMax,
divisions: _sliderDivisions,
activeColor: Colors.blue,
inactiveColor: Colors.white24,
inactiveColor: Theme.of(context).colorScheme.outlineVariant,
onChanged: (value) {
setState(() {
_currentOffset = value;
@@ -205,7 +206,7 @@ class _SyncOffsetControlState extends State<SyncOffsetControl> {
),
Text(
t.videoControls.addTime(amount: "5", unit: "s"),
style: const TextStyle(color: Colors.white70),
style: TextStyle(color: tokens(context).textMuted),
),
const SizedBox(width: 12),
// Increment button
@@ -223,10 +224,6 @@ class _SyncOffsetControlState extends State<SyncOffsetControl> {
icon: const AppIcon(Symbols.restart_alt_rounded, fill: 1),
label: Text(t.videoControls.resetToZero),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[800],
foregroundColor: Colors.white,
disabledBackgroundColor: Colors.grey[850],
disabledForegroundColor: Colors.white38,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),