feat(scribe-mobile):Fix sometimes the top part is cut off
This commit is contained in:
@@ -35,6 +35,8 @@ class AiScribeSuggestionWidget extends StatefulWidget {
|
||||
|
||||
class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||
with AiScribeSuggestionStateMixin {
|
||||
static const double _defaultPadding = 32.0;
|
||||
|
||||
@override
|
||||
AIAction get aiAction => widget.aiAction;
|
||||
|
||||
@@ -48,11 +50,23 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||
OnSelectAiScribeSuggestionAction get onSelectAction =>
|
||||
widget.onSelectAiScribeSuggestionAction;
|
||||
|
||||
bool get _hasAnchor =>
|
||||
widget.buttonPosition != null && widget.buttonSize != null;
|
||||
|
||||
bool get _isMobileView =>
|
||||
PlatformInfo.isMobile || PlatformInfo.isWebTouchDevice;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||
final keyboardHeightWithSpacing = keyboardHeight > 0 ? keyboardHeight + AIScribeSizes.keyboardSpacing : 0;
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
// Cache MediaQuery data to avoid multiple lookups
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final screenSize = mediaQuery.size;
|
||||
final viewInsetsBottom = mediaQuery.viewInsets.bottom;
|
||||
|
||||
final keyboardHeightWithSpacing = viewInsetsBottom > 0
|
||||
? viewInsetsBottom + AIScribeSizes.keyboardSpacing
|
||||
: 0.0;
|
||||
|
||||
final availableHeight = screenSize.height - keyboardHeightWithSpacing;
|
||||
|
||||
final modalWidth = min(
|
||||
@@ -65,33 +79,93 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||
AIScribeSizes.suggestionModalMaxHeight,
|
||||
);
|
||||
|
||||
final hasContent = widget.content?.trim().isNotEmpty == true;
|
||||
|
||||
final dialogContent = _buildDialogContent(context, hasContent);
|
||||
final dialogContent = _buildDialogContent(context);
|
||||
|
||||
// No Anchor - Center the modal
|
||||
if (!_hasAnchor) {
|
||||
return Center(
|
||||
child: _buildModalContainer(
|
||||
width: modalWidth,
|
||||
maxHeight: modalMaxHeight,
|
||||
child: dialogContent,
|
||||
),
|
||||
return _buildCenteredLayout(
|
||||
modalWidth: modalWidth,
|
||||
modalMaxHeight: modalMaxHeight,
|
||||
child: dialogContent,
|
||||
);
|
||||
}
|
||||
|
||||
// Anchored Modal
|
||||
return _buildAnchoredLayout(
|
||||
screenSize: screenSize,
|
||||
availableHeight: availableHeight,
|
||||
keyboardHeightWithSpacing: keyboardHeightWithSpacing,
|
||||
modalWidth: modalWidth,
|
||||
modalMaxHeight: modalMaxHeight,
|
||||
child: dialogContent,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCenteredLayout({
|
||||
required double modalWidth,
|
||||
required double modalMaxHeight,
|
||||
required Widget child,
|
||||
}) {
|
||||
return Center(
|
||||
child: _buildModalContainer(
|
||||
width: modalWidth,
|
||||
maxHeight: modalMaxHeight,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAnchoredLayout({
|
||||
required Size screenSize,
|
||||
required double availableHeight,
|
||||
required double keyboardHeightWithSpacing,
|
||||
required double modalWidth,
|
||||
required double modalMaxHeight,
|
||||
required Widget child,
|
||||
}) {
|
||||
// Safe unwrap because we checked _hasAnchor before calling this
|
||||
final anchorPos = widget.buttonPosition!;
|
||||
final anchorSize = widget.buttonSize!;
|
||||
|
||||
// Calculate layout using the helper
|
||||
final layout = AnchoredModalLayoutCalculator.calculateAnchoredSuggestLayout(
|
||||
screenSize: Size(screenSize.width, availableHeight),
|
||||
anchorPosition: widget.buttonPosition!,
|
||||
anchorSize: widget.buttonSize!,
|
||||
anchorPosition: anchorPos,
|
||||
anchorSize: anchorSize,
|
||||
menuSize: Size(modalWidth, modalMaxHeight),
|
||||
preferredPlacement: widget.preferredPlacement,
|
||||
padding: AIScribeSizes.screenEdgePadding,
|
||||
);
|
||||
|
||||
final modalStartOffset = layout.left;
|
||||
final modalBottomOffset =
|
||||
_isMobileView ? null : layout.bottom + keyboardHeightWithSpacing;
|
||||
final modalTopOffset = _isMobileView ? widget.buttonPosition!.dy : null;
|
||||
// Calculate specific dimensions based on platform logic
|
||||
final double? top;
|
||||
final double? bottom;
|
||||
final double height;
|
||||
final double width;
|
||||
|
||||
if (_isMobileView) {
|
||||
// Mobile logic: Anchor usually relates to top position
|
||||
top = anchorPos.dy;
|
||||
bottom = null;
|
||||
|
||||
height = min(
|
||||
layout.availableHeight,
|
||||
screenSize.height - anchorPos.dy - anchorSize.height - _defaultPadding,
|
||||
);
|
||||
|
||||
width = min(
|
||||
modalWidth,
|
||||
screenSize.width - anchorPos.dx - anchorSize.width - _defaultPadding,
|
||||
);
|
||||
} else {
|
||||
// Desktop/Web logic: Uses calculated bottom offset
|
||||
top = null;
|
||||
// Layout bottom doesn't account for keyboard in calculation usually, so we add it back
|
||||
bottom = layout.bottom + keyboardHeightWithSpacing;
|
||||
|
||||
height = layout.availableHeight;
|
||||
width = modalWidth;
|
||||
}
|
||||
|
||||
return PointerInterceptor(
|
||||
child: Stack(
|
||||
@@ -102,17 +176,15 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||
onTap: _handleClickOutside,
|
||||
),
|
||||
),
|
||||
// The Modal
|
||||
PositionedDirectional(
|
||||
start: modalStartOffset,
|
||||
// layout.bottom is calculated by taking keyboard into account
|
||||
// but positioned without taking keyboard into account
|
||||
// that's why we need to add keyboard height here
|
||||
top: modalTopOffset,
|
||||
bottom: modalBottomOffset,
|
||||
start: layout.left,
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
child: _buildModalContainer(
|
||||
width: modalWidth,
|
||||
maxHeight: layout.availableHeight,
|
||||
child: dialogContent,
|
||||
width: width,
|
||||
maxHeight: height,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -120,7 +192,7 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDialogContent(BuildContext context, bool hasContent) {
|
||||
Widget _buildDialogContent(BuildContext context) {
|
||||
final localizations = ScribeLocalizations.of(context);
|
||||
|
||||
return Column(
|
||||
@@ -154,8 +226,8 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||
padding: AIScribeSizes.suggestionContentPadding,
|
||||
decoration: BoxDecoration(
|
||||
color: AIScribeColors.background,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AIScribeSizes.menuRadius,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(AIScribeSizes.menuRadius),
|
||||
),
|
||||
boxShadow: AIScribeShadows.modal,
|
||||
),
|
||||
@@ -164,17 +236,13 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||
);
|
||||
}
|
||||
|
||||
bool get _hasAnchor =>
|
||||
widget.buttonPosition != null && widget.buttonSize != null;
|
||||
|
||||
bool get _isMobileView =>
|
||||
PlatformInfo.isMobile || PlatformInfo.isWebTouchDevice;
|
||||
|
||||
void _handleClickOutside() {
|
||||
final shouldDismiss = suggestionState.value.fold(
|
||||
final state = suggestionState.value;
|
||||
final shouldDismiss = state.fold(
|
||||
(failure) => failure is GenerateAITextFailure,
|
||||
(success) => success is GenerateAITextSuccess,
|
||||
);
|
||||
|
||||
if (shouldDismiss) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user