From b64aa4e4c731972ad0df3fb934c95c6f80fb9cab Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 13 Feb 2026 17:24:35 +0700 Subject: [PATCH] feat(scribe-mobile):Fix sometimes the top part is cut off --- .../modal/ai_scribe_suggestion_widget.dart | 142 +++++++++++++----- 1 file changed, 105 insertions(+), 37 deletions(-) diff --git a/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart b/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart index ed12cf4a0..56f5f7284 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart @@ -35,6 +35,8 @@ class AiScribeSuggestionWidget extends StatefulWidget { class _AiScribeSuggestionWidgetState extends State with AiScribeSuggestionStateMixin { + static const double _defaultPadding = 32.0; + @override AIAction get aiAction => widget.aiAction; @@ -48,11 +50,23 @@ class _AiScribeSuggestionWidgetState extends State 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 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 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 ); } - 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 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 ); } - 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(); }