mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
Improve scroll bar of favorites page.
This commit is contained in:
@@ -99,11 +99,13 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
|||||||
);
|
);
|
||||||
if (_futurePosition == old) return;
|
if (_futurePosition == old) return;
|
||||||
var target = _futurePosition!;
|
var target = _futurePosition!;
|
||||||
_controller.animateTo(
|
_controller
|
||||||
|
.animateTo(
|
||||||
_futurePosition!,
|
_futurePosition!,
|
||||||
duration: _fastAnimationDuration,
|
duration: _fastAnimationDuration,
|
||||||
curve: Curves.linear,
|
curve: Curves.linear,
|
||||||
).then((_) {
|
)
|
||||||
|
.then((_) {
|
||||||
var current = _controller.position.pixels;
|
var current = _controller.position.pixels;
|
||||||
if (current == target && current == _futurePosition) {
|
if (current == target && current == _futurePosition) {
|
||||||
_futurePosition = null;
|
_futurePosition = null;
|
||||||
@@ -144,3 +146,169 @@ class ScrollControllerProvider extends InheritedWidget {
|
|||||||
return oldWidget.controller != controller;
|
return oldWidget.controller != controller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppScrollBar extends StatefulWidget {
|
||||||
|
const AppScrollBar({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.child,
|
||||||
|
this.topPadding = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ScrollController controller;
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
final double topPadding;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppScrollBar> createState() => _AppScrollBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppScrollBarState extends State<AppScrollBar> {
|
||||||
|
late final ScrollController _scrollController;
|
||||||
|
|
||||||
|
double minExtent = 0;
|
||||||
|
double maxExtent = 0;
|
||||||
|
double position = 0;
|
||||||
|
|
||||||
|
double viewHeight = 0;
|
||||||
|
|
||||||
|
final _scrollIndicatorSize = App.isDesktop ? 42.0 : 48.0;
|
||||||
|
|
||||||
|
late final VerticalDragGestureRecognizer _dragGestureRecognizer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_scrollController = widget.controller;
|
||||||
|
_scrollController.addListener(onChanged);
|
||||||
|
Future.microtask(onChanged);
|
||||||
|
_dragGestureRecognizer = VerticalDragGestureRecognizer()
|
||||||
|
..onUpdate = onUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdate(DragUpdateDetails details) {
|
||||||
|
if (maxExtent - minExtent <= 0 ||
|
||||||
|
viewHeight == 0 ||
|
||||||
|
details.primaryDelta == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var offset = details.primaryDelta!;
|
||||||
|
var positionOffset =
|
||||||
|
offset / (viewHeight - _scrollIndicatorSize) * (maxExtent - minExtent);
|
||||||
|
_scrollController.jumpTo((position + positionOffset).clamp(
|
||||||
|
minExtent,
|
||||||
|
maxExtent,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChanged() {
|
||||||
|
if (_scrollController.positions.isEmpty) return;
|
||||||
|
var position = _scrollController.position;
|
||||||
|
if (position.minScrollExtent != minExtent ||
|
||||||
|
position.maxScrollExtent != maxExtent ||
|
||||||
|
position.pixels != this.position) {
|
||||||
|
setState(() {
|
||||||
|
minExtent = position.minScrollExtent;
|
||||||
|
maxExtent = position.maxScrollExtent;
|
||||||
|
this.position = position.pixels;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constrains) {
|
||||||
|
var scrollHeight = (maxExtent - minExtent);
|
||||||
|
var height = constrains.maxHeight - widget.topPadding;
|
||||||
|
viewHeight = height;
|
||||||
|
var top = scrollHeight == 0
|
||||||
|
? 0.0
|
||||||
|
: (position - minExtent) /
|
||||||
|
scrollHeight *
|
||||||
|
(height - _scrollIndicatorSize);
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: top + widget.topPadding,
|
||||||
|
right: 0,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: Listener(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onPointerDown: (event) {
|
||||||
|
_dragGestureRecognizer.addPointer(event);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: _scrollIndicatorSize/2,
|
||||||
|
height: _scrollIndicatorSize,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: _ScrollIndicatorPainter(
|
||||||
|
backgroundColor: context.colorScheme.surface,
|
||||||
|
shadowColor: context.colorScheme.shadow,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
Icon(Icons.arrow_drop_up, size: 18),
|
||||||
|
Icon(Icons.arrow_drop_down, size: 18),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
).paddingLeft(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScrollIndicatorPainter extends CustomPainter {
|
||||||
|
final Color backgroundColor;
|
||||||
|
|
||||||
|
final Color shadowColor;
|
||||||
|
|
||||||
|
const _ScrollIndicatorPainter({
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.shadowColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
var path = Path()
|
||||||
|
..moveTo(size.width, 0)
|
||||||
|
..lineTo(size.width, size.height)
|
||||||
|
..arcToPoint(
|
||||||
|
Offset(size.width, 0),
|
||||||
|
radius: Radius.circular(size.width),
|
||||||
|
);
|
||||||
|
canvas.drawShadow(path, shadowColor, 4, true);
|
||||||
|
var backgroundPaint = Paint()
|
||||||
|
..color = backgroundColor
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
path = Path()
|
||||||
|
..moveTo(size.width, 0)
|
||||||
|
..lineTo(size.width, size.height)
|
||||||
|
..arcToPoint(
|
||||||
|
Offset(size.width, 0),
|
||||||
|
radius: Radius.circular(size.width),
|
||||||
|
);
|
||||||
|
canvas.drawPath(path, backgroundPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||||
|
return oldDelegate is! _ScrollIndicatorPainter ||
|
||||||
|
oldDelegate.backgroundColor != backgroundColor ||
|
||||||
|
oldDelegate.shadowColor != shadowColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -518,11 +518,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
body = Scrollbar(
|
body = AppScrollBar(
|
||||||
|
topPadding: 48,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
thickness: App.isDesktop ? 8 : 12,
|
|
||||||
radius: const Radius.circular(8),
|
|
||||||
interactive: true,
|
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||||
child: body,
|
child: body,
|
||||||
|
Reference in New Issue
Block a user