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>
|
class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
||||||
with AiScribeSuggestionStateMixin {
|
with AiScribeSuggestionStateMixin {
|
||||||
|
static const double _defaultPadding = 32.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AIAction get aiAction => widget.aiAction;
|
AIAction get aiAction => widget.aiAction;
|
||||||
|
|
||||||
@@ -48,11 +50,23 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
|||||||
OnSelectAiScribeSuggestionAction get onSelectAction =>
|
OnSelectAiScribeSuggestionAction get onSelectAction =>
|
||||||
widget.onSelectAiScribeSuggestionAction;
|
widget.onSelectAiScribeSuggestionAction;
|
||||||
|
|
||||||
|
bool get _hasAnchor =>
|
||||||
|
widget.buttonPosition != null && widget.buttonSize != null;
|
||||||
|
|
||||||
|
bool get _isMobileView =>
|
||||||
|
PlatformInfo.isMobile || PlatformInfo.isWebTouchDevice;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
|
// Cache MediaQuery data to avoid multiple lookups
|
||||||
final keyboardHeightWithSpacing = keyboardHeight > 0 ? keyboardHeight + AIScribeSizes.keyboardSpacing : 0;
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final screenSize = MediaQuery.of(context).size;
|
final screenSize = mediaQuery.size;
|
||||||
|
final viewInsetsBottom = mediaQuery.viewInsets.bottom;
|
||||||
|
|
||||||
|
final keyboardHeightWithSpacing = viewInsetsBottom > 0
|
||||||
|
? viewInsetsBottom + AIScribeSizes.keyboardSpacing
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
final availableHeight = screenSize.height - keyboardHeightWithSpacing;
|
final availableHeight = screenSize.height - keyboardHeightWithSpacing;
|
||||||
|
|
||||||
final modalWidth = min(
|
final modalWidth = min(
|
||||||
@@ -65,33 +79,93 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
|||||||
AIScribeSizes.suggestionModalMaxHeight,
|
AIScribeSizes.suggestionModalMaxHeight,
|
||||||
);
|
);
|
||||||
|
|
||||||
final hasContent = widget.content?.trim().isNotEmpty == true;
|
final dialogContent = _buildDialogContent(context);
|
||||||
|
|
||||||
final dialogContent = _buildDialogContent(context, hasContent);
|
|
||||||
|
|
||||||
|
// No Anchor - Center the modal
|
||||||
if (!_hasAnchor) {
|
if (!_hasAnchor) {
|
||||||
return Center(
|
return _buildCenteredLayout(
|
||||||
child: _buildModalContainer(
|
modalWidth: modalWidth,
|
||||||
width: modalWidth,
|
modalMaxHeight: modalMaxHeight,
|
||||||
maxHeight: modalMaxHeight,
|
child: dialogContent,
|
||||||
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(
|
final layout = AnchoredModalLayoutCalculator.calculateAnchoredSuggestLayout(
|
||||||
screenSize: Size(screenSize.width, availableHeight),
|
screenSize: Size(screenSize.width, availableHeight),
|
||||||
anchorPosition: widget.buttonPosition!,
|
anchorPosition: anchorPos,
|
||||||
anchorSize: widget.buttonSize!,
|
anchorSize: anchorSize,
|
||||||
menuSize: Size(modalWidth, modalMaxHeight),
|
menuSize: Size(modalWidth, modalMaxHeight),
|
||||||
preferredPlacement: widget.preferredPlacement,
|
preferredPlacement: widget.preferredPlacement,
|
||||||
padding: AIScribeSizes.screenEdgePadding,
|
padding: AIScribeSizes.screenEdgePadding,
|
||||||
);
|
);
|
||||||
|
|
||||||
final modalStartOffset = layout.left;
|
// Calculate specific dimensions based on platform logic
|
||||||
final modalBottomOffset =
|
final double? top;
|
||||||
_isMobileView ? null : layout.bottom + keyboardHeightWithSpacing;
|
final double? bottom;
|
||||||
final modalTopOffset = _isMobileView ? widget.buttonPosition!.dy : null;
|
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(
|
return PointerInterceptor(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@@ -102,17 +176,15 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
|||||||
onTap: _handleClickOutside,
|
onTap: _handleClickOutside,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// The Modal
|
||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
start: modalStartOffset,
|
start: layout.left,
|
||||||
// layout.bottom is calculated by taking keyboard into account
|
top: top,
|
||||||
// but positioned without taking keyboard into account
|
bottom: bottom,
|
||||||
// that's why we need to add keyboard height here
|
|
||||||
top: modalTopOffset,
|
|
||||||
bottom: modalBottomOffset,
|
|
||||||
child: _buildModalContainer(
|
child: _buildModalContainer(
|
||||||
width: modalWidth,
|
width: width,
|
||||||
maxHeight: layout.availableHeight,
|
maxHeight: height,
|
||||||
child: dialogContent,
|
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);
|
final localizations = ScribeLocalizations.of(context);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -154,8 +226,8 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget>
|
|||||||
padding: AIScribeSizes.suggestionContentPadding,
|
padding: AIScribeSizes.suggestionContentPadding,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AIScribeColors.background,
|
color: AIScribeColors.background,
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: const BorderRadius.all(
|
||||||
AIScribeSizes.menuRadius,
|
Radius.circular(AIScribeSizes.menuRadius),
|
||||||
),
|
),
|
||||||
boxShadow: AIScribeShadows.modal,
|
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() {
|
void _handleClickOutside() {
|
||||||
final shouldDismiss = suggestionState.value.fold(
|
final state = suggestionState.value;
|
||||||
|
final shouldDismiss = state.fold(
|
||||||
(failure) => failure is GenerateAITextFailure,
|
(failure) => failure is GenerateAITextFailure,
|
||||||
(success) => success is GenerateAITextSuccess,
|
(success) => success is GenerateAITextSuccess,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldDismiss) {
|
if (shouldDismiss) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user