aves/lib/widgets/common/action_controls/quick_choosers/common/button.dart

150 lines
4.6 KiB
Dart

import 'dart:async';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/route_layout.dart';
import 'package:aves/widgets/common/basic/gestures/gesture_detector.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
abstract class ChooserQuickButton<T> extends StatefulWidget {
final bool blurred;
final ValueSetter<T>? onChooserValue;
final FocusNode? focusNode;
final VoidCallback? onPressed;
const ChooserQuickButton({
super.key,
required this.blurred,
this.onChooserValue,
this.focusNode,
required this.onPressed,
});
}
abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> extends State<T> with SingleTickerProviderStateMixin {
AnimationController? _animationController;
CurvedAnimation? _animation;
OverlayEntry? _chooserOverlayEntry;
final ValueNotifier<U?> _chooserValueNotifier = ValueNotifier(null);
final StreamController<LongPressMoveUpdateDetails> _moveUpdateStreamController = StreamController.broadcast();
Widget get icon;
String get tooltip;
U? get defaultValue => null;
Duration get animationDuration => context.read<DurationsData>().quickChooserAnimation;
Curve get animationCurve => Curves.easeOutQuad;
bool get hasChooser => widget.onChooserValue != null;
Widget buildChooser(Animation<double> animation, PopupMenuPosition chooserPosition);
ValueNotifier<U?> get chooserValueNotifier => _chooserValueNotifier;
Stream<Offset> get pointerGlobalPosition => _moveUpdateStreamController.stream.map((event) => event.globalPosition);
@override
void dispose() {
_animation?.dispose();
_animationController?.dispose();
_clearChooserOverlayEntry();
_chooserValueNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final _hasChooser = hasChooser;
Widget child = IconButton(
icon: icon,
onPressed: widget.onPressed,
focusNode: widget.focusNode,
tooltip: _hasChooser ? null : tooltip,
);
if (_hasChooser) {
child = Semantics(
tooltip: tooltip,
child: child,
);
}
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Theme(
data: theme.copyWith(
colorScheme: colorScheme.copyWith(
onSurfaceVariant: colorScheme.onSurface,
),
),
child: AGestureDetector(
behavior: HitTestBehavior.opaque,
onLongPressStart: _hasChooser ? _showChooser : null,
onLongPressMoveUpdate: _hasChooser ? _moveUpdateStreamController.add : null,
onLongPressEnd: _hasChooser
? (details) {
_clearChooserOverlayEntry();
final selectedValue = _chooserValueNotifier.value;
if (selectedValue != null) {
widget.onChooserValue?.call(selectedValue);
}
}
: null,
onLongPressCancel: _clearChooserOverlayEntry,
longPressTimeout: settings.longPressTimeout,
child: child,
),
);
}
void _clearChooserOverlayEntry() {
_chooserOverlayEntry
?..remove()
..dispose();
_chooserOverlayEntry = null;
}
void _showChooser(LongPressStartDetails details) {
final overlay = Overlay.of(context);
final triggerBox = context.findRenderObject() as RenderBox;
final overlayBox = overlay.context.findRenderObject() as RenderBox;
final triggerRect = RelativeRect.fromRect(
triggerBox.localToGlobal(Offset.zero, ancestor: overlayBox) & triggerBox.size,
Offset.zero & overlayBox.size,
);
_chooserValueNotifier.value = defaultValue;
_chooserOverlayEntry = OverlayEntry(
builder: (context) {
final mq = MediaQuery.of(context);
final chooserPosition = (details.globalPosition.dy > mq.size.height / 2) ? PopupMenuPosition.over : PopupMenuPosition.under;
return CustomSingleChildLayout(
delegate: QuickChooserRouteLayout(
triggerRect,
chooserPosition,
mq.padding,
DisplayFeatureSubScreen.avoidBounds(mq).toSet(),
),
child: buildChooser(_animation!, chooserPosition),
);
},
);
if (_animationController == null) {
_animationController = AnimationController(
duration: animationDuration,
vsync: this,
);
_animation = CurvedAnimation(
parent: _animationController!,
curve: animationCurve,
);
}
_animationController!.reset();
overlay.insert(_chooserOverlayEntry!);
_animationController!.forward();
}
}