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 { class AnchoredSuggestionLayoutResult {
final Offset offset;
final double availableHeight; final double availableHeight;
final double? top; final double left;
final double? bottom; final double bottom;
const AnchoredSuggestionLayoutResult({ const AnchoredSuggestionLayoutResult({
required this.offset, required this.left,
required this.bottom,
required this.availableHeight, required this.availableHeight,
this.top,
this.bottom,
}); });
} }
@@ -1,5 +1,3 @@
import 'dart:math';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:scribe/scribe.dart'; import 'package:scribe/scribe.dart';
@@ -145,10 +143,11 @@ class AnchoredModalLayoutCalculator {
required Size screenSize, required Size screenSize,
required Offset anchorPosition, required Offset anchorPosition,
required Size anchorSize, required Size anchorSize,
required double modalMaxHeight, required Size menuSize,
ModalPlacement? preferredPlacement, ModalPlacement? preferredPlacement,
double gap = 8, double gap = 8,
double padding = AIScribeSizes.screenEdgePadding, double verticalOffset = 6,
double padding = 8,
}) { }) {
final isTop = preferredPlacement == ModalPlacement.top; final isTop = preferredPlacement == ModalPlacement.top;
@@ -157,25 +156,79 @@ class AnchoredModalLayoutCalculator {
final positionBottom = screenSize.height - anchorPosition.dy + gap; final positionBottom = screenSize.height - anchorPosition.dy + gap;
return AnchoredSuggestionLayoutResult( return AnchoredSuggestionLayoutResult(
offset: Offset(
anchorPosition.dx,
positionBottom,
),
availableHeight: availableHeight, availableHeight: availableHeight,
left: anchorPosition.dx,
bottom: positionBottom, bottom: positionBottom,
); );
} }
final spaceBelow = screenSize.height - anchorPosition.dy - padding - gap; late ModalPlacement placement;
final positionTop = anchorPosition.dx + anchorSize.width + gap;
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( return AnchoredSuggestionLayoutResult(
offset: Offset( availableHeight: menuSize.height,
positionTop, left: left,
anchorPosition.dy, bottom: bottom,
),
availableHeight: min(spaceBelow, modalMaxHeight),
top: anchorPosition.dy,
); );
} }
} }
@@ -104,8 +104,9 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget> {
screenSize: screenSize, screenSize: screenSize,
anchorPosition: widget.buttonPosition!, anchorPosition: widget.buttonPosition!,
anchorSize: widget.buttonSize!, anchorSize: widget.buttonSize!,
modalMaxHeight: modalMaxHeight, menuSize: Size(modalWidth, modalMaxHeight),
preferredPlacement: widget.preferredPlacement, preferredPlacement: widget.preferredPlacement,
padding: AIScribeSizes.screenEdgePadding,
); );
return PointerInterceptor( return PointerInterceptor(
@@ -118,8 +119,7 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget> {
), ),
), ),
PositionedDirectional( PositionedDirectional(
start: layout.offset.dx, start: layout.left,
top: layout.top,
bottom: layout.bottom, bottom: layout.bottom,
child: _buildModalContainer( child: _buildModalContainer(
width: modalWidth, width: modalWidth,