ente/mobile/lib/ui/huge_listview/draggable_scrollbar.dart

216 lines
6 KiB
Dart
Raw Normal View History

2021-04-20 19:39:45 +00:00
import 'dart:async';
2021-04-20 09:22:03 +00:00
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
2021-04-20 19:39:45 +00:00
import 'package:photos/ui/huge_listview/scroll_bar_thumb.dart';
2021-04-20 09:22:03 +00:00
class DraggableScrollbar extends StatefulWidget {
final Widget child;
final Color backgroundColor;
final Color drawColor;
final double heightScrollThumb;
final EdgeInsetsGeometry? padding;
2021-04-20 09:22:03 +00:00
final int totalCount;
final int initialScrollIndex;
final double bottomSafeArea;
2021-04-20 09:22:03 +00:00
final int currentFirstIndex;
final ValueChanged<double>? onChange;
2021-04-20 19:39:45 +00:00
final String Function(int) labelTextBuilder;
final bool isEnabled;
2021-04-20 09:22:03 +00:00
const DraggableScrollbar({
Key? key,
required this.child,
2021-04-20 09:22:03 +00:00
this.backgroundColor = Colors.white,
this.drawColor = Colors.grey,
2021-04-20 20:34:24 +00:00
this.heightScrollThumb = 80.0,
this.bottomSafeArea = 120,
2021-04-20 09:22:03 +00:00
this.padding,
this.totalCount = 1,
this.initialScrollIndex = 0,
this.currentFirstIndex = 0,
required this.labelTextBuilder,
2021-04-20 09:22:03 +00:00
this.onChange,
this.isEnabled = true,
2021-04-20 09:22:03 +00:00
}) : super(key: key);
@override
DraggableScrollbarState createState() => DraggableScrollbarState();
}
class DraggableScrollbarState extends State<DraggableScrollbar>
with TickerProviderStateMixin {
2022-07-04 06:02:17 +00:00
static const thumbAnimationDuration = Duration(milliseconds: 1000);
static const labelAnimationDuration = Duration(milliseconds: 1000);
2021-04-20 09:22:03 +00:00
double thumbOffset = 0.0;
bool isDragging = false;
late int currentFirstIndex;
2021-04-20 09:22:03 +00:00
double get thumbMin => 0.0;
double get thumbMax =>
context.size!.height - widget.heightScrollThumb - widget.bottomSafeArea;
2021-04-20 09:22:03 +00:00
late AnimationController _thumbAnimationController;
Animation<double>? _thumbAnimation;
late AnimationController _labelAnimationController;
Animation<double>? _labelAnimation;
Timer? _fadeoutTimer;
2021-04-20 19:39:45 +00:00
2021-04-20 09:22:03 +00:00
@override
void initState() {
super.initState();
currentFirstIndex = widget.currentFirstIndex;
2021-04-20 09:22:03 +00:00
2023-07-07 10:23:11 +00:00
///Where will this be true on init?
2021-04-20 09:22:03 +00:00
if (widget.initialScrollIndex > 0 && widget.totalCount > 1) {
WidgetsBinding.instance.addPostFrameCallback((_) {
2022-06-11 08:23:52 +00:00
setState(
() => thumbOffset = (widget.initialScrollIndex / widget.totalCount) *
(thumbMax - thumbMin),
);
2021-04-20 09:22:03 +00:00
});
}
2021-04-20 19:39:45 +00:00
_thumbAnimationController = AnimationController(
vsync: this,
duration: thumbAnimationDuration,
);
_thumbAnimation = CurvedAnimation(
parent: _thumbAnimationController,
curve: Curves.fastOutSlowIn,
);
2021-04-20 19:39:45 +00:00
_labelAnimationController = AnimationController(
vsync: this,
duration: labelAnimationDuration,
2021-04-20 19:39:45 +00:00
);
_labelAnimation = CurvedAnimation(
parent: _labelAnimationController,
curve: Curves.fastOutSlowIn,
);
2021-04-20 09:22:03 +00:00
}
@override
2021-04-20 19:39:45 +00:00
void dispose() {
_thumbAnimationController.dispose();
2021-04-20 19:39:45 +00:00
_labelAnimationController.dispose();
_fadeoutTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
RepaintBoundary(child: widget.child),
widget.isEnabled
? RepaintBoundary(child: buildThumb())
: const SizedBox.shrink(),
],
);
2021-04-20 19:39:45 +00:00
}
2021-04-20 09:22:03 +00:00
Widget buildThumb() => Padding(
padding: widget.padding!,
child: Container(
alignment: Alignment.topRight,
margin: EdgeInsets.only(top: thumbOffset),
child: ScrollBarThumb(
widget.backgroundColor,
widget.drawColor,
widget.heightScrollThumb,
widget.labelTextBuilder.call(currentFirstIndex),
_labelAnimation,
_thumbAnimation,
onDragStart,
onDragUpdate,
onDragEnd,
),
2021-04-20 09:22:03 +00:00
),
);
void setPosition(double position, int currentFirstIndex) {
2021-04-20 09:22:03 +00:00
setState(() {
this.currentFirstIndex = currentFirstIndex;
2021-04-20 09:22:03 +00:00
thumbOffset = position * (thumbMax - thumbMin);
if (_thumbAnimationController.status != AnimationStatus.forward) {
_thumbAnimationController.forward();
}
2021-04-20 19:39:45 +00:00
_fadeoutTimer?.cancel();
_fadeoutTimer = Timer(thumbAnimationDuration, () {
_thumbAnimationController.reverse();
2021-04-20 19:39:45 +00:00
_labelAnimationController.reverse();
_fadeoutTimer = null;
});
2021-04-20 09:22:03 +00:00
});
}
void onDragStart(DragStartDetails details) {
2021-04-20 19:39:45 +00:00
setState(() {
isDragging = true;
_labelAnimationController.forward();
_fadeoutTimer?.cancel();
});
2021-04-20 09:22:03 +00:00
}
void onDragUpdate(DragUpdateDetails details) {
setState(() {
if (_thumbAnimationController.status != AnimationStatus.forward) {
_thumbAnimationController.forward();
}
2021-04-20 09:22:03 +00:00
if (isDragging && details.delta.dy != 0) {
thumbOffset += details.delta.dy;
thumbOffset = thumbOffset.clamp(thumbMin, thumbMax);
2022-08-29 14:43:31 +00:00
final double position = thumbOffset / (thumbMax - thumbMin);
2021-04-20 09:22:03 +00:00
widget.onChange?.call(position);
}
});
}
void onDragEnd(DragEndDetails details) {
_fadeoutTimer = Timer(thumbAnimationDuration, () {
_thumbAnimationController.reverse();
2021-04-20 19:39:45 +00:00
_labelAnimationController.reverse();
_fadeoutTimer = null;
});
2021-04-20 09:22:03 +00:00
setState(() => isDragging = false);
}
void keyHandler(RawKeyEvent value) {
if (value.runtimeType == RawKeyDownEvent) {
if (value.logicalKey == LogicalKeyboardKey.arrowDown) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, 2),
2022-06-11 08:23:52 +00:00
),
);
} else if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, -2),
2022-06-11 08:23:52 +00:00
),
);
} else if (value.logicalKey == LogicalKeyboardKey.pageDown) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, 25),
2022-06-11 08:23:52 +00:00
),
);
} else if (value.logicalKey == LogicalKeyboardKey.pageUp) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, -25),
2022-06-11 08:23:52 +00:00
),
);
}
2021-04-20 09:22:03 +00:00
}
}
}