| // 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 'debug.dart'; |
| import 'localizations.dart'; |
| import 'text_selection_toolbar.dart'; |
| import 'text_selection_toolbar_button.dart'; |
| |
| /// iOS only shows 3 spell check suggestions in the toolbar. |
| const int _kMaxSuggestions = 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. |
| /// |
| /// See also: |
| /// * [SpellCheckSuggestionsToolbar], which is similar but for both the |
| /// Material and Cupertino libraries. |
| class CupertinoSpellCheckSuggestionsToolbar extends StatelessWidget { |
| /// Constructs a [CupertinoSpellCheckSuggestionsToolbar]. |
| /// |
| /// [buttonItems] must not contain more than three items. |
| const CupertinoSpellCheckSuggestionsToolbar({ |
| super.key, |
| required this.anchors, |
| required this.buttonItems, |
| }) : assert(buttonItems.length <= _kMaxSuggestions); |
| |
| /// Constructs a [CupertinoSpellCheckSuggestionsToolbar] with the default |
| /// children for an [EditableText]. |
| /// |
| /// See also: |
| /// * [SpellCheckSuggestionsToolbar.editableText], which is similar but |
| /// builds an Android-style toolbar. |
| CupertinoSpellCheckSuggestionsToolbar.editableText({ |
| super.key, |
| required EditableTextState editableTextState, |
| }) : buttonItems = buildButtonItems(editableTextState) ?? <ContextMenuButtonItem>[], |
| anchors = editableTextState.contextMenuAnchors; |
| |
| /// 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. |
| /// |
| /// Must not contain more than three items. |
| /// |
| /// 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( |
| 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) { |
| assert(debugCheckHasCupertinoLocalizations(editableTextState.context)); |
| final CupertinoLocalizations localizations = |
| CupertinoLocalizations.of(editableTextState.context); |
| return <ContextMenuButtonItem>[ |
| ContextMenuButtonItem( |
| onPressed: null, |
| label: localizations.noSpellCheckReplacementsLabel, |
| ) |
| ]; |
| } |
| |
| final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[]; |
| |
| // Build suggestion buttons. |
| for (final String suggestion in spanAtCursorIndex.suggestions.take(_kMaxSuggestions)) { |
| buttonItems.add(ContextMenuButtonItem( |
| onPressed: () { |
| if (!editableTextState.mounted) { |
| return; |
| } |
| _replaceText( |
| editableTextState, |
| suggestion, |
| spanAtCursorIndex.range, |
| ); |
| }, |
| label: suggestion, |
| )); |
| } |
| 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, |
| ) |
| .copyWith( |
| selection: TextSelection.collapsed( |
| offset: replacementRange.start + text.length, |
| ), |
| ); |
| 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(); |
| } |
| |
| /// 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) { |
| if (buttonItems.isEmpty) { |
| return const SizedBox.shrink(); |
| } |
| |
| final List<Widget> children = _buildToolbarButtons(context); |
| return CupertinoTextSelectionToolbar( |
| anchorAbove: anchors.primaryAnchor, |
| anchorBelow: anchors.secondaryAnchor == null ? anchors.primaryAnchor : anchors.secondaryAnchor!, |
| children: children, |
| ); |
| } |
| } |