diff --git a/lib/widgets/video_controls/widgets/sync_offset_control.dart b/lib/widgets/video_controls/widgets/sync_offset_control.dart index 4dce3304..3e017315 100644 --- a/lib/widgets/video_controls/widgets/sync_offset_control.dart +++ b/lib/widgets/video_controls/widgets/sync_offset_control.dart @@ -61,7 +61,7 @@ class _SyncOffsetControlState extends State { static const double _absoluteMax = 60000; static const double _tapStep = 100; // 100ms per tap static const double _longPressStep = 1000; // 1s per long-press tick - static const int _sliderDivisions = 240; // 500ms steps for ±60s range + static const int _sliderDivisions = 1200; // 100ms steps for ±60s range late double _currentOffset; Timer? _longPressTimer; @@ -243,22 +243,25 @@ class _SyncOffsetControlState extends State { return KeyEventResult.ignored; }, canRequestFocus: false, - child: Slider( - focusNode: widget.sliderFocusNode, - value: sliderValue, - min: _sliderMin, - max: _sliderMax, - divisions: _sliderDivisions, - activeColor: Theme.of(context).colorScheme.primary, - inactiveColor: Theme.of(context).colorScheme.outlineVariant, - onChanged: (value) { - setState(() { - _currentOffset = value; - }); - }, - onChangeEnd: (value) { - _applyOffset(value); - }, + child: SliderTheme( + data: SliderTheme.of(context).copyWith(tickMarkShape: SliderTickMarkShape.noTickMark), + child: Slider( + focusNode: widget.sliderFocusNode, + value: sliderValue, + min: _sliderMin, + max: _sliderMax, + divisions: _sliderDivisions, + activeColor: Theme.of(context).colorScheme.primary, + inactiveColor: Theme.of(context).colorScheme.outlineVariant, + onChanged: (value) { + setState(() { + _currentOffset = value; + }); + }, + onChangeEnd: (value) { + _applyOffset(value); + }, + ), ), ), ), @@ -333,21 +336,24 @@ class _SyncOffsetControlState extends State { style: TextStyle(color: tokens(context).textMuted), ), Expanded( - child: Slider( - value: sliderValue, - min: _sliderMin, - max: _sliderMax, - divisions: _sliderDivisions, - activeColor: Theme.of(context).colorScheme.primary, - inactiveColor: Theme.of(context).colorScheme.outlineVariant, - onChanged: (value) { - setState(() { - _currentOffset = value; - }); - }, - onChangeEnd: (value) { - _applyOffset(value); - }, + child: SliderTheme( + data: SliderTheme.of(context).copyWith(tickMarkShape: SliderTickMarkShape.noTickMark), + child: Slider( + value: sliderValue, + min: _sliderMin, + max: _sliderMax, + divisions: _sliderDivisions, + activeColor: Theme.of(context).colorScheme.primary, + inactiveColor: Theme.of(context).colorScheme.outlineVariant, + onChanged: (value) { + setState(() { + _currentOffset = value; + }); + }, + onChangeEnd: (value) { + _applyOffset(value); + }, + ), ), ), Text( diff --git a/test/widgets/video_controls_test.dart b/test/widgets/video_controls_test.dart index fcc10bad..65cf45f2 100644 --- a/test/widgets/video_controls_test.dart +++ b/test/widgets/video_controls_test.dart @@ -1,15 +1,36 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/i18n/strings.g.dart'; import 'package:plezy/media/media_source_info.dart'; import 'package:plezy/media/media_version.dart'; import 'package:plezy/models/shader_preset.dart'; +import 'package:plezy/mpv/mpv.dart'; +import 'package:plezy/theme/mono_tokens.dart'; import 'package:plezy/widgets/video_controls/video_controls.dart'; import 'package:plezy/widgets/video_controls/painters/buffer_range_painter.dart'; import 'package:plezy/widgets/video_controls/widgets/mobile_skip_zones.dart'; +import 'package:plezy/widgets/video_controls/widgets/sync_offset_control.dart'; import 'package:plezy/widgets/video_controls/widgets/timeline_slider.dart'; +const _testTokens = MonoTokens( + radiusSm: 8, + radiusMd: 12, + space: 8, + fast: Duration(milliseconds: 1), + normal: Duration(milliseconds: 1), + slow: Duration(milliseconds: 1), + bg: Colors.black, + surface: Colors.black, + outline: Colors.white24, + text: Colors.white, + textMuted: Colors.white70, + splashFactory: NoSplash.splashFactory, +); + void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + group('resolveShaderTogglePreset', () { test('turns shaders off when a shader is currently active', () { final result = resolveShaderTogglePreset( @@ -234,4 +255,51 @@ void main() { expect(slider.max, 0.0); }); }); + + group('SyncOffsetControl', () { + testWidgets('uses 100ms slider steps without rendering tick marks', (tester) async { + LocaleSettings.setLocaleSync(AppLocale.en); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: const [_testTokens]), + home: Scaffold( + body: SizedBox( + width: 700, + child: SyncOffsetControl( + player: _FakeSyncPlayer(), + propertyName: 'sub-delay', + initialOffset: 0, + labelText: 'Subtitles', + onOffsetChanged: (_) async {}, + compact: true, + ), + ), + ), + ), + ); + + final slider = tester.widget(find.byType(Slider)); + final sliderTheme = tester.widget( + find.ancestor(of: find.byType(Slider), matching: find.byType(SliderTheme)).first, + ); + + expect(slider.min, -60000); + expect(slider.max, 60000); + expect(slider.divisions, 1200); + expect((slider.max - slider.min) / slider.divisions!, 100); + expect(sliderTheme.data.tickMarkShape, same(SliderTickMarkShape.noTickMark)); + }); + }); +} + +class _FakeSyncPlayer implements Player { + @override + PlayerState get state => PlayerState(); + + @override + Future setProperty(String name, String value) async {} + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); }