| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/scheduler.dart'; |
| import 'package:flutter/services.dart' show SelectionChangedCause, SuggestionSpan; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'text_selection_toolbar.dart'; |
| import 'text_selection_toolbar_button.dart'; |
| |
| /// iOS only shows 3 spell check suggestions in the toolbar. |
| const int _maxSuggestions = 3; |
| |
| /// The default spell check suggestions toolbar for iOS. |
| /// |
| /// Tries to position itself below the [anchors], but if it doesn't fit, then it |
| /// readjusts to fit above bottom view insets. |
| class CupertinoSpellCheckSuggestionsToolbar extends StatelessWidget { |
| /// Constructs a [CupertinoSpellCheckSuggestionsToolbar]. |
| const CupertinoSpellCheckSuggestionsToolbar({ |
| super.key, |
| required this.anchors, |
| required this.buttonItems, |
| }); |
| |
| /// The location on which to anchor the menu. |
| final TextSelectionToolbarAnchors anchors; |
| |
| /// The [ContextMenuButtonItem]s that will be turned into the correct button |
| /// widgets and displayed in the spell check suggestions toolbar. |
| /// |
| /// See also: |
| /// |
| /// * [AdaptiveTextSelectionToolbar.buttonItems], the list of |
| /// [ContextMenuButtonItem]s that are used to build the buttons of the |
| /// text selection toolbar. |
| /// * [SpellCheckSuggestionsToolbar.buttonItems], the list of |
| /// [ContextMenuButtonItem]s used to build the Material style spell check |
| /// suggestions toolbar. |
| final List<ContextMenuButtonItem> buttonItems; |
| |
| /// Builds the button items for the toolbar based on the available |
| /// spell check suggestions. |
| static List<ContextMenuButtonItem>? buildButtonItems( |
| BuildContext context, |
| EditableTextState editableTextState, |
| ) { |
| // Determine if composing region is misspelled. |
| final SuggestionSpan? spanAtCursorIndex = |
| editableTextState.findSuggestionSpanAtCursorIndex( |
| editableTextState.currentTextEditingValue.selection.baseOffset, |
| ); |
| |
| if (spanAtCursorIndex == null) { |
| return null; |
| } |
| if (spanAtCursorIndex.suggestions.isEmpty) { |
| return <ContextMenuButtonItem>[ |
| ContextMenuButtonItem( |
| onPressed: () {}, |
| label: 'No Replacements Found', |
| ) |
| ]; |
| } |
| |
| final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[]; |
| |
| // Build suggestion buttons. |
| int suggestionCount = 0; |
| for (final String suggestion in spanAtCursorIndex.suggestions) { |
| if (suggestionCount >= _maxSuggestions) { |
| break; |
| } |
| buttonItems.add(ContextMenuButtonItem( |
| onPressed: () { |
| if (!editableTextState.mounted) { |
| return; |
| } |
| _replaceText( |
| editableTextState, |
| suggestion, |
| spanAtCursorIndex.range, |
| ); |
| }, |
| label: suggestion, |
| )); |
| suggestionCount += 1; |
| } |
| return buttonItems; |
| } |
| |
| static void _replaceText(EditableTextState editableTextState, String text, TextRange replacementRange) { |
| // Replacement cannot be performed if the text is read only or obscured. |
| assert(!editableTextState.widget.readOnly && !editableTextState.widget.obscureText); |
| |
| final TextEditingValue newValue = editableTextState.textEditingValue.replaced( |
| replacementRange, |
| text, |
| ); |
| editableTextState.userUpdateTextEditingValue(newValue,SelectionChangedCause.toolbar); |
| |
| // Schedule a call to bringIntoView() after renderEditable updates. |
| SchedulerBinding.instance.addPostFrameCallback((Duration duration) { |
| if (editableTextState.mounted) { |
| editableTextState.bringIntoView(editableTextState.textEditingValue.selection.extent); |
| } |
| }); |
| editableTextState.hideToolbar(); |
| editableTextState.renderEditable.selectWordEdge(cause: SelectionChangedCause.toolbar); |
| } |
| |
| /// Builds the toolbar buttons based on the [buttonItems]. |
| List<Widget> _buildToolbarButtons(BuildContext context) { |
| return buttonItems.map((ContextMenuButtonItem buttonItem) { |
| return CupertinoTextSelectionToolbarButton.buttonItem( |
| buttonItem: buttonItem, |
| ); |
| }).toList(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final List<Widget> children = _buildToolbarButtons(context); |
| return CupertinoTextSelectionToolbar( |
| anchorAbove: anchors.primaryAnchor, |
| anchorBelow: anchors.secondaryAnchor == null ? anchors.primaryAnchor : anchors.secondaryAnchor!, |
| children: children, |
| ); |
| } |
| } |