From 7bc0aeb4affb1956e9fc27b78a7c986e52894b61 Mon Sep 17 00:00:00 2001 From: Pacalini <141402887+Pacalini@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:07:39 +0800 Subject: [PATCH] tool bar: RtL slider & button swap (#50) --- lib/components/custom_slider.dart | 224 ++++++++++++++++++++++++++++++ lib/pages/reader/reader.dart | 1 + lib/pages/reader/scaffold.dart | 42 +++--- 3 files changed, 244 insertions(+), 23 deletions(-) create mode 100644 lib/components/custom_slider.dart diff --git a/lib/components/custom_slider.dart b/lib/components/custom_slider.dart new file mode 100644 index 0000000..ae9f04f --- /dev/null +++ b/lib/components/custom_slider.dart @@ -0,0 +1,224 @@ +import 'package:flutter/material.dart'; + +/// patched slider.dart with RtL support +class _SliderDefaultsM3 extends SliderThemeData { + _SliderDefaultsM3(this.context) + : super(trackHeight: 4.0); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => _colors.primary; + + @override + Color? get inactiveTrackColor => _colors.surfaceContainerHighest; + + @override + Color? get secondaryActiveTrackColor => _colors.primary.withOpacity(0.54); + + @override + Color? get disabledActiveTrackColor => _colors.onSurface.withOpacity(0.38); + + @override + Color? get disabledInactiveTrackColor => _colors.onSurface.withOpacity(0.12); + + @override + Color? get disabledSecondaryActiveTrackColor => _colors.onSurface.withOpacity(0.12); + + @override + Color? get activeTickMarkColor => _colors.onPrimary.withOpacity(0.38); + + @override + Color? get inactiveTickMarkColor => _colors.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledActiveTickMarkColor => _colors.onSurface.withOpacity(0.38); + + @override + Color? get disabledInactiveTickMarkColor => _colors.onSurface.withOpacity(0.38); + + @override + Color? get thumbColor => _colors.primary; + + @override + Color? get disabledThumbColor => Color.alphaBlend(_colors.onSurface.withOpacity(0.38), _colors.surface); + + @override + Color? get overlayColor => WidgetStateColor.resolveWith((Set states) { + if (states.contains(WidgetState.dragged)) { + return _colors.primary.withOpacity(0.1); + } + if (states.contains(WidgetState.hovered)) { + return _colors.primary.withOpacity(0.08); + } + if (states.contains(WidgetState.focused)) { + return _colors.primary.withOpacity(0.1); + } + + return Colors.transparent; + }); + + @override + TextStyle? get valueIndicatorTextStyle => Theme.of(context).textTheme.labelMedium!.copyWith( + color: _colors.onPrimary, + ); + + @override + SliderComponentShape? get valueIndicatorShape => const DropSliderValueIndicatorShape(); +} + +class CustomSlider extends StatefulWidget { + const CustomSlider({required this.min, required this.max, required this.value, required this.divisions, required this.onChanged, required this.focusNode, this.reversed = false, super.key}); + + final double min; + + final double max; + + final double value; + + final int divisions; + + final void Function(double) onChanged; + + final FocusNode? focusNode; + + final bool reversed; + + @override + State createState() => _CustomSliderState(); +} + +class _CustomSliderState extends State { + late double value; + + @override + void initState() { + super.initState(); + value = widget.value; + } + + @override + void didUpdateWidget(CustomSlider oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.value != oldWidget.value) { + setState(() { + value = widget.value; + }); + } + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final theme = _SliderDefaultsM3(context); + return Padding( + padding: const EdgeInsets.fromLTRB(24, 12, 24, 12), + child: widget.max - widget.min > 0 ? LayoutBuilder( + builder: (context, constraints) => MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTapDown: (details){ + var dx = details.localPosition.dx; + if(widget.reversed){ + dx = constraints.maxWidth - dx; + } + var gap = constraints.maxWidth / widget.divisions; + var gapValue = (widget.max - widget.min) / widget.divisions; + widget.onChanged.call((dx / gap).round() * gapValue + widget.min); + }, + onVerticalDragUpdate: (details){ + var dx = details.localPosition.dx; + if(dx > constraints.maxWidth || dx < 0) return; + if(widget.reversed){ + dx = constraints.maxWidth - dx; + } + var gap = constraints.maxWidth / widget.divisions; + var gapValue = (widget.max - widget.min) / widget.divisions; + widget.onChanged.call((dx / gap).round() * gapValue + widget.min); + }, + child: SizedBox( + height: 24, + child: Center( + child: SizedBox( + height: 24, + child: Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + child: Center( + child: Container( + width: double.infinity, + height: 6, + decoration: BoxDecoration( + color: theme.inactiveTrackColor, + borderRadius: const BorderRadius.all(Radius.circular(10)) + ), + ), + ), + ), + if(constraints.maxWidth / widget.divisions > 10) + Positioned.fill( + child: Row( + children: (){ + var res = []; + for(int i = 0; i { bool get isOpen => _isOpen; + bool get isReversed => context.reader.mode == ReaderMode.galleryRightToLeft || + context.reader.mode == ReaderMode.continuousRightToLeft; + int showFloatingButtonValue = 0; var lastValue = 0; @@ -217,34 +220,26 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> { children: [ const SizedBox(width: 8), IconButton.filledTonal( - onPressed: () { - if (!context.reader.toPrevChapter()) { - context.reader.toPage(1); - } else { - if (showFloatingButtonValue != 0) { - setState(() { - showFloatingButtonValue = 0; - }); - } - } - }, + onPressed: () => !isReversed + ? context.reader.chapter > 1 + ? context.reader.toPrevChapter() + : context.reader.toPage(1) + : context.reader.chapter < context.reader.maxChapter + ? context.reader.toNextChapter() + : context.reader.toPage(context.reader.maxPage), icon: const Icon(Icons.first_page), ), Expanded( child: buildSlider(), ), IconButton.filledTonal( - onPressed: () { - if (!context.reader.toNextChapter()) { - context.reader.toPage(context.reader.maxPage); - } else { - if (showFloatingButtonValue != 0) { - setState(() { - showFloatingButtonValue = 0; - }); - } - } - }, + onPressed: () => !isReversed + ? context.reader.chapter < context.reader.maxChapter + ? context.reader.toNextChapter() + : context.reader.toPage(context.reader.maxPage) + : context.reader.chapter > 1 + ? context.reader.toPrevChapter() + : context.reader.toPage(1), icon: const Icon(Icons.last_page)), const SizedBox( width: 8, @@ -379,12 +374,13 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> { var sliderFocus = FocusNode(); Widget buildSlider() { - return Slider( + return CustomSlider( focusNode: sliderFocus, value: context.reader.page.toDouble(), min: 1, max: context.reader.maxPage.clamp(context.reader.page, 1 << 16).toDouble(), + reversed: isReversed, divisions: (context.reader.maxPage - 1).clamp(2, 1 << 16), onChanged: (i) { context.reader.toPage(i.toInt());