feat(sheets): position desktop sheets near cursor
This commit is contained in:
@@ -594,7 +594,6 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
final isNumeric = mediaClient?.capabilities.numericUserRating ?? true;
|
||||
final hasRating = metadata.userRating != null && metadata.userRating! > 0;
|
||||
final starValue = hasRating ? metadata.userRating! / 2.0 : 0.0;
|
||||
final activate = isNumeric ? () => _showRatingDialog(metadata, starValue) : () => _toggleLike(metadata);
|
||||
|
||||
final iconData = isNumeric ? Symbols.star_rounded : Symbols.thumb_up_rounded;
|
||||
final activeIconColor = isNumeric ? Colors.amber : Colors.teal;
|
||||
@@ -606,6 +605,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
return ListenableBuilder(
|
||||
listenable: _ratingChipFocusNode,
|
||||
builder: (context, _) {
|
||||
final activate = isNumeric
|
||||
? () => _showRatingDialog(context, metadata, starValue)
|
||||
: () => _toggleLike(metadata);
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isKeyboardMode = InputModeTracker.isKeyboardMode(context);
|
||||
final showFocus = _ratingChipFocusNode.hasFocus && isKeyboardMode;
|
||||
@@ -680,9 +682,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
}
|
||||
}
|
||||
|
||||
void _showRatingDialog(MediaItem metadata, double currentStarValue) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
void _showRatingDialog(BuildContext sheetContext, MediaItem metadata, double currentStarValue) {
|
||||
OverlaySheetController.showAdaptive(
|
||||
sheetContext,
|
||||
builder: (context) => RatingBottomSheet(
|
||||
currentRating: currentStarValue,
|
||||
onRate: (stars) async {
|
||||
|
||||
@@ -19,6 +19,7 @@ import '../../utils/snackbar_helper.dart';
|
||||
import '../../widgets/dialog_action_button.dart';
|
||||
import '../../utils/video_player_navigation.dart';
|
||||
import '../../widgets/focused_scroll_scaffold.dart';
|
||||
import '../../widgets/overlay_sheet.dart';
|
||||
import '../models/watch_session.dart';
|
||||
import '../providers/watch_together_provider.dart';
|
||||
import '../services/recent_rooms_service.dart';
|
||||
@@ -402,8 +403,8 @@ class _RecentRoomTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _showActions(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
OverlaySheetController.showAdaptive(
|
||||
context,
|
||||
builder: (context) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -412,7 +413,7 @@ class _RecentRoomTile extends StatelessWidget {
|
||||
leading: const Icon(Symbols.edit_rounded),
|
||||
title: Text(t.watchTogether.renameRoom),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
OverlaySheetController.closeAdaptive(context);
|
||||
onRename();
|
||||
},
|
||||
),
|
||||
@@ -420,7 +421,7 @@ class _RecentRoomTile extends StatelessWidget {
|
||||
leading: Icon(Symbols.delete_rounded, color: Theme.of(context).colorScheme.error),
|
||||
title: Text(t.watchTogether.removeRoom, style: TextStyle(color: Theme.of(context).colorScheme.error)),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
OverlaySheetController.closeAdaptive(context);
|
||||
onRemove();
|
||||
},
|
||||
),
|
||||
|
||||
@@ -214,6 +214,8 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
BoxConstraints? _constraints;
|
||||
Color? _explicitBackgroundColor;
|
||||
Alignment _alignment = Alignment.bottomCenter;
|
||||
Offset? _lastPointerPosition;
|
||||
double? _sheetHorizontalAnchor;
|
||||
|
||||
// Drag-to-dismiss state
|
||||
double _dragOffset = 0;
|
||||
@@ -274,6 +276,7 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
|
||||
final completer = Completer<T?>();
|
||||
final entry = _OverlaySheetEntry(builder: builder, completer: completer, initialFocusNode: initialFocusNode);
|
||||
final horizontalAnchor = _resolveSheetHorizontalAnchor(alignment);
|
||||
|
||||
setState(() {
|
||||
_pageStack.add(entry);
|
||||
@@ -284,6 +287,7 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
_constraints = constraints;
|
||||
_explicitBackgroundColor = backgroundColor;
|
||||
_alignment = alignment;
|
||||
_sheetHorizontalAnchor = horizontalAnchor;
|
||||
_dragOffset = 0;
|
||||
_isDragging = false;
|
||||
});
|
||||
@@ -345,6 +349,7 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
_isClosing = false;
|
||||
_dragOffset = 0;
|
||||
_isDragging = false;
|
||||
_sheetHorizontalAnchor = null;
|
||||
});
|
||||
// Clear stale back-key flags. handleBackKeyAction sets
|
||||
// markClosedViaBackKey() expecting a route pop, but the overlay
|
||||
@@ -355,6 +360,18 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
});
|
||||
}
|
||||
|
||||
void _rememberPointerPosition(PointerEvent event) {
|
||||
if (event.kind != PointerDeviceKind.mouse) return;
|
||||
_lastPointerPosition = event.localPosition;
|
||||
}
|
||||
|
||||
double? _resolveSheetHorizontalAnchor(Alignment alignment) {
|
||||
if (!PlatformDetector.isDesktopOS() || PlatformDetector.isTV()) return null;
|
||||
if (InputModeTracker.isKeyboardMode(context)) return null;
|
||||
if (alignment.x != 0 || alignment.y <= 0) return null;
|
||||
return _lastPointerPosition?.dx;
|
||||
}
|
||||
|
||||
void _autoFocus() {
|
||||
if (!InputModeTracker.isKeyboardMode(context)) return;
|
||||
|
||||
@@ -458,6 +475,10 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
|
||||
return _OverlaySheetScope(
|
||||
controller: _controller,
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerDown: _rememberPointerPosition,
|
||||
onPointerHover: _rememberPointerPosition,
|
||||
child: Stack(
|
||||
children: [
|
||||
widget.child,
|
||||
@@ -478,6 +499,7 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -586,8 +608,12 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
canRequestFocus: false,
|
||||
skipTraversal: true,
|
||||
onKeyEvent: _handleKeyEvent,
|
||||
child: Align(
|
||||
child: CustomSingleChildLayout(
|
||||
delegate: _OverlaySheetLayoutDelegate(
|
||||
alignment: _alignment,
|
||||
horizontalAnchor: _sheetHorizontalAnchor,
|
||||
edgePadding: isDesktop ? _OverlaySheetLayoutDelegate.desktopEdgePadding : 0,
|
||||
),
|
||||
child: AnimatedBuilder(
|
||||
animation: _slideCurve,
|
||||
builder: (context, child) {
|
||||
@@ -654,3 +680,46 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
|
||||
class _OverlaySheetLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
static const desktopEdgePadding = 16.0;
|
||||
|
||||
final Alignment alignment;
|
||||
final double? horizontalAnchor;
|
||||
final double edgePadding;
|
||||
|
||||
const _OverlaySheetLayoutDelegate({
|
||||
required this.alignment,
|
||||
required this.horizontalAnchor,
|
||||
required this.edgePadding,
|
||||
});
|
||||
|
||||
@override
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
final size = constraints.biggest;
|
||||
final maxWidth = size.width > edgePadding * 2 ? size.width - edgePadding * 2 : size.width;
|
||||
return BoxConstraints.loose(Size(maxWidth, size.height));
|
||||
}
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
final hasHorizontalPadding = edgePadding > 0 && size.width > childSize.width + edgePadding * 2;
|
||||
final minLeft = hasHorizontalPadding ? edgePadding : 0.0;
|
||||
final maxLeft = hasHorizontalPadding ? size.width - childSize.width - edgePadding : minLeft;
|
||||
final left = horizontalAnchor == null
|
||||
? minLeft + (maxLeft - minLeft) * (alignment.x + 1) / 2
|
||||
: (horizontalAnchor! - childSize.width / 2).clamp(minLeft, maxLeft).toDouble();
|
||||
|
||||
final maxTop = size.height > childSize.height ? size.height - childSize.height : 0.0;
|
||||
final top = maxTop * (alignment.y + 1) / 2;
|
||||
|
||||
return Offset(left, top);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(_OverlaySheetLayoutDelegate oldDelegate) {
|
||||
return alignment != oldDelegate.alignment ||
|
||||
horizontalAnchor != oldDelegate.horizontalAnchor ||
|
||||
edgePadding != oldDelegate.edgePadding;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user