feat(ai-scribe): Fix alignment of prompt when AI button at very right of composer or response have long result but at the bottom of composer

This commit is contained in:
dab246
2025-12-19 11:37:54 +07:00
committed by Dat H. Pham
parent 89eb1ecc71
commit 088b922b0e
3 changed files with 76 additions and 27 deletions
@@ -1,15 +1,11 @@
import 'package:flutter/cupertino.dart';
class AnchoredSuggestionLayoutResult {
final Offset offset;
final double availableHeight;
final double? top;
final double? bottom;
final double left;
final double bottom;
const AnchoredSuggestionLayoutResult({
required this.offset,
required this.left,
required this.bottom,
required this.availableHeight,
this.top,
this.bottom,
});
}
@@ -1,5 +1,3 @@
import 'dart:math';
import 'package:flutter/animation.dart';
import 'package:scribe/scribe.dart';
@@ -145,10 +143,11 @@ class AnchoredModalLayoutCalculator {
required Size screenSize,
required Offset anchorPosition,
required Size anchorSize,
required double modalMaxHeight,
required Size menuSize,
ModalPlacement? preferredPlacement,
double gap = 8,
double padding = AIScribeSizes.screenEdgePadding,
double verticalOffset = 6,
double padding = 8,
}) {
final isTop = preferredPlacement == ModalPlacement.top;
@@ -157,25 +156,79 @@ class AnchoredModalLayoutCalculator {
final positionBottom = screenSize.height - anchorPosition.dy + gap;
return AnchoredSuggestionLayoutResult(
offset: Offset(
anchorPosition.dx,
positionBottom,
),
availableHeight: availableHeight,
left: anchorPosition.dx,
bottom: positionBottom,
);
}
final spaceBelow = screenSize.height - anchorPosition.dy - padding - gap;
final positionTop = anchorPosition.dx + anchorSize.width + gap;
late ModalPlacement placement;
if (preferredPlacement != null &&
_canPlace(
placement: preferredPlacement,
screen: screenSize,
anchor: anchorPosition,
anchorSize: anchorSize,
menu: menuSize,
gap: gap,
)) {
placement = preferredPlacement;
} else {
final fallbackOrder = [
ModalPlacement.right,
ModalPlacement.bottom,
ModalPlacement.top,
ModalPlacement.left,
];
placement = fallbackOrder.firstWhere(
(p) => _canPlace(
placement: p,
screen: screenSize,
anchor: anchorPosition,
anchorSize: anchorSize,
menu: menuSize,
gap: gap,
),
orElse: () => ModalPlacement.right,
);
}
late double left;
late double bottom;
switch (placement) {
case ModalPlacement.right:
left = anchorPosition.dx + anchorSize.width + gap;
bottom = anchorPosition.dy + verticalOffset;
break;
case ModalPlacement.bottom:
left = anchorPosition.dx;
bottom = anchorPosition.dy + anchorSize.height + gap + verticalOffset;
break;
case ModalPlacement.top:
left = anchorPosition.dx;
bottom = anchorPosition.dy - menuSize.height - gap + verticalOffset;
break;
case ModalPlacement.left:
left = anchorPosition.dx - menuSize.width - gap;
bottom = anchorPosition.dy + verticalOffset;
break;
}
left = left.clamp(padding, screenSize.width - menuSize.width - padding);
bottom = bottom.clamp(
padding,
screenSize.height - menuSize.height - padding,
);
return AnchoredSuggestionLayoutResult(
offset: Offset(
positionTop,
anchorPosition.dy,
),
availableHeight: min(spaceBelow, modalMaxHeight),
top: anchorPosition.dy,
availableHeight: menuSize.height,
left: left,
bottom: bottom,
);
}
}
@@ -104,8 +104,9 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget> {
screenSize: screenSize,
anchorPosition: widget.buttonPosition!,
anchorSize: widget.buttonSize!,
modalMaxHeight: modalMaxHeight,
menuSize: Size(modalWidth, modalMaxHeight),
preferredPlacement: widget.preferredPlacement,
padding: AIScribeSizes.screenEdgePadding,
);
return PointerInterceptor(
@@ -118,8 +119,7 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget> {
),
),
PositionedDirectional(
start: layout.offset.dx,
top: layout.top,
start: layout.left,
bottom: layout.bottom,
child: _buildModalContainer(
width: modalWidth,