improve bottom sheet logic
This commit is contained in:
@@ -115,44 +115,44 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
bool dismissOnThreshold(
|
final relativeSafeArea = MediaQuery.of(context).relativeSafeArea();
|
||||||
DraggableScrollableNotification notification,
|
|
||||||
) {
|
|
||||||
if (!context.mounted) return false;
|
|
||||||
|
|
||||||
if (notification.extent <= 0.1) {
|
sheetController.value = state.showBottomSheet((context) {
|
||||||
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
bool dismissOnThreshold(
|
||||||
return true;
|
DraggableScrollableNotification notification,
|
||||||
} else {
|
) {
|
||||||
ref
|
if (notification.extent <= 0.1) {
|
||||||
.read(bottomSheetExtendProvider.notifier)
|
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||||
.add(notification.extent);
|
return true;
|
||||||
|
} else {
|
||||||
|
ref
|
||||||
|
.read(bottomSheetExtendProvider.notifier)
|
||||||
|
.add(notification.extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
final sheet = switch (next) {
|
||||||
}
|
ViewTabsSheet() =>
|
||||||
|
NotificationListener<DraggableScrollableNotification>(
|
||||||
final sheet = switch (next) {
|
key: ValueKey(next),
|
||||||
ViewTabsSheet() =>
|
onNotification: dismissOnThreshold,
|
||||||
NotificationListener<DraggableScrollableNotification>(
|
child: _ViewTabsSheet(maxChildSize: relativeSafeArea),
|
||||||
key: ValueKey(next),
|
|
||||||
onNotification: dismissOnThreshold,
|
|
||||||
child: _ViewTabsSheet(
|
|
||||||
maxChildSize: MediaQuery.of(context).relativeSafeArea(),
|
|
||||||
),
|
),
|
||||||
),
|
final EditUrlSheet parameter =>
|
||||||
final EditUrlSheet parameter =>
|
NotificationListener<DraggableScrollableNotification>(
|
||||||
NotificationListener<DraggableScrollableNotification>(
|
key: ValueKey(parameter),
|
||||||
key: ValueKey(parameter),
|
onNotification: dismissOnThreshold,
|
||||||
onNotification: dismissOnThreshold,
|
child: _ViewUrlSheet(
|
||||||
child: _ViewUrlSheet(
|
initialTabState: parameter.tabState,
|
||||||
initialTabState: parameter.tabState,
|
maxChildSize: relativeSafeArea,
|
||||||
maxChildSize: MediaQuery.of(context).relativeSafeArea(),
|
),
|
||||||
),
|
),
|
||||||
),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
sheetController.value = state.showBottomSheet((context) => sheet);
|
return sheet;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+112
-5
@@ -21,26 +21,133 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DraggableScrollableHeader extends StatelessWidget {
|
class DraggableScrollableHeader extends StatefulWidget {
|
||||||
final DraggableScrollableController controller;
|
final DraggableScrollableController controller;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
|
// Velocity control parameters
|
||||||
|
final double velocitySensitivity;
|
||||||
|
final double maxVelocity;
|
||||||
|
final double minVelocity;
|
||||||
|
final Duration animationDuration;
|
||||||
|
final Curve animationCurve;
|
||||||
|
final double dragSensitivity;
|
||||||
|
|
||||||
const DraggableScrollableHeader({
|
const DraggableScrollableHeader({
|
||||||
super.key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
this.velocitySensitivity = 0.35, // Higher = more sensitive to velocity
|
||||||
|
this.maxVelocity = 2500.0, // Maximum velocity to consider
|
||||||
|
this.minVelocity = 150.0, // Minimum velocity to trigger momentum
|
||||||
|
this.animationDuration = const Duration(milliseconds: 300),
|
||||||
|
this.animationCurve = Curves.decelerate,
|
||||||
|
this.dragSensitivity = 1.0, // Controls drag responsiveness
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DraggableScrollableHeader> createState() =>
|
||||||
|
_DraggableScrollableHeaderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DraggableScrollableHeaderState extends State<DraggableScrollableHeader>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double>? _animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: widget.animationDuration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _animateToPosition(
|
||||||
|
double targetSize, {
|
||||||
|
Duration? customDuration,
|
||||||
|
}) async {
|
||||||
|
if (customDuration != null) {
|
||||||
|
_animationController.duration = customDuration;
|
||||||
|
} else {
|
||||||
|
_animationController.duration = widget.animationDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
_animation = Tween<double>(begin: widget.controller.size, end: targetSize)
|
||||||
|
.animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: widget.animationCurve,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_animationController.reset();
|
||||||
|
await _animationController.forward();
|
||||||
|
|
||||||
|
_animation!.addListener(() {
|
||||||
|
widget.controller.jumpTo(_animation!.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onVerticalDragUpdate: (details) {
|
onVerticalDragUpdate: (details) {
|
||||||
// Use the DraggableScrollableSheet's controller
|
_animationController.stop();
|
||||||
controller.jumpTo(
|
|
||||||
min(1, controller.pixelsToSize(controller.pixels - details.delta.dy)),
|
// Apply drag sensitivity
|
||||||
|
final adjustedDelta = details.delta.dy * widget.dragSensitivity;
|
||||||
|
|
||||||
|
widget.controller.jumpTo(
|
||||||
|
min(
|
||||||
|
1,
|
||||||
|
widget.controller.pixelsToSize(
|
||||||
|
widget.controller.pixels - adjustedDelta,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: child,
|
onVerticalDragEnd: (details) async {
|
||||||
|
final velocity = details.primaryVelocity ?? 0;
|
||||||
|
final currentSize = widget.controller.size;
|
||||||
|
|
||||||
|
// Clamp velocity to defined range
|
||||||
|
final clampedVelocity = velocity.clamp(
|
||||||
|
-widget.maxVelocity,
|
||||||
|
widget.maxVelocity,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only apply momentum if velocity exceeds minimum threshold
|
||||||
|
if (clampedVelocity.abs() < widget.minVelocity) {
|
||||||
|
return; // No momentum, stay at current position
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate momentum-based target with sensitivity control
|
||||||
|
final velocityFactor =
|
||||||
|
clampedVelocity / 1000 * widget.velocitySensitivity;
|
||||||
|
double targetSize = currentSize - velocityFactor;
|
||||||
|
|
||||||
|
// Clamp to valid range
|
||||||
|
targetSize = targetSize.clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
// Calculate animation duration based on velocity (faster velocity = longer animation)
|
||||||
|
final velocityRatio = clampedVelocity.abs() / widget.maxVelocity;
|
||||||
|
final dynamicDuration = Duration(
|
||||||
|
milliseconds:
|
||||||
|
(widget.animationDuration.inMilliseconds * (0.5 + velocityRatio))
|
||||||
|
.round(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await _animateToPosition(targetSize, customDuration: dynamicDuration);
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -96,18 +96,17 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
final totalHeight =
|
final totalHeight =
|
||||||
headerBox.size.height + textBox.size.height + kToolbarHeight;
|
headerBox.size.height + textBox.size.height + kToolbarHeight;
|
||||||
|
|
||||||
final relative = totalHeight / MediaQuery.of(context).size.height;
|
final relative = (totalHeight / MediaQuery.of(context).size.height)
|
||||||
|
.clamp(0.0, 1.0);
|
||||||
|
|
||||||
if (relative >= 0 && relative <= 1) {
|
if (draggableScrollableController.size < relative &&
|
||||||
if (draggableScrollableController.size < relative &&
|
relative > scrolledTo.value) {
|
||||||
relative > scrolledTo.value) {
|
await draggableScrollableController.animateTo(
|
||||||
await draggableScrollableController.animateTo(
|
relative,
|
||||||
relative,
|
duration: const Duration(milliseconds: 150),
|
||||||
duration: const Duration(milliseconds: 150),
|
curve: Curves.easeInOut,
|
||||||
curve: Curves.easeInOut,
|
);
|
||||||
);
|
scrolledTo.value = relative;
|
||||||
scrolledTo.value = relative;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,10 +124,13 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
MediaQuery.of(context).size.height) -
|
MediaQuery.of(context).size.height) -
|
||||||
bottomInsets.value;
|
bottomInsets.value;
|
||||||
|
|
||||||
draggableScrollableController.jumpTo(
|
final jumpValue = (draggableScrollableController.size + diff).clamp(
|
||||||
draggableScrollableController.size + diff,
|
0.0,
|
||||||
|
1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
draggableScrollableController.jumpTo(jumpValue);
|
||||||
|
|
||||||
bottomInsets.value += diff;
|
bottomInsets.value += diff;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user