Files
IKAR/lib/frontend/widgets/scroll_keyboard_dismiss.dart
T
sevenhill 1ef2b7420e
Tests / test (push) Canceled after 0s
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
Port Komet UI improvements and implement IKAR ghost mode
Adapt video editing, decrypted message actions, custom themes and gestures from PR 166. Track visible server presence per session and preserve delivery for invisible clients.
2026-10-07 22:07:05 +03:00

73 lines
2.2 KiB
Dart

import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
class ScrollKeyboardDismiss extends StatefulWidget {
const ScrollKeyboardDismiss({
super.key,
required this.onDismiss,
required this.child,
});
final VoidCallback onDismiss;
final Widget child;
@override
State<ScrollKeyboardDismiss> createState() => _ScrollKeyboardDismissState();
}
class _ScrollKeyboardDismissState extends State<ScrollKeyboardDismiss> {
static const _minimumSpeed = 300.0;
static const _minimumDistance = 18.0;
VelocityTracker? _tracker;
Offset? _origin;
bool _dismissed = false;
bool _onScroll(ScrollNotification notification) {
if (notification.depth != 0 || notification.metrics.axis != Axis.vertical) {
return false;
}
if (notification is ScrollStartNotification) {
final details = notification.dragDetails;
_tracker = details == null
? null
: VelocityTracker.withKind(details.kind ?? PointerDeviceKind.touch);
_origin = details?.globalPosition;
_dismissed = false;
if (details?.sourceTimeStamp case final time?) {
_tracker?.addPosition(time, details!.globalPosition);
}
} else if (notification is ScrollEndNotification) {
_tracker = null;
_origin = null;
} else if (!_dismissed) {
final details = switch (notification) {
ScrollUpdateNotification() => notification.dragDetails,
OverscrollNotification() => notification.dragDetails,
_ => null,
};
final time = details?.sourceTimeStamp;
final tracker = _tracker;
final origin = _origin;
if (details != null &&
time != null &&
tracker != null &&
origin != null) {
tracker.addPosition(time, details.globalPosition);
if ((details.globalPosition.dy - origin.dy).abs() >= _minimumDistance &&
tracker.getVelocity().pixelsPerSecond.dy.abs() >= _minimumSpeed) {
_dismissed = true;
widget.onDismiss();
}
}
}
return false;
}
@override
Widget build(BuildContext context) =>
NotificationListener<ScrollNotification>(
onNotification: _onScroll,
child: widget.child,
);
}