1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-08 13:58:47 +00:00

Flutter 3.29 beta (#2571)

This commit is contained in:
Eric Windmill
2025-02-12 18:08:01 -05:00
committed by GitHub
parent d62c784789
commit 719fd72c38
685 changed files with 76244 additions and 53721 deletions

View File

@@ -47,36 +47,40 @@ class AppStateWidget extends StatefulWidget {
class AppStateWidgetState extends State<AppStateWidget> {
AppState _data = AppState(
replacementsController: ReplacementTextEditingController(
text: 'The quick brown fox jumps over the lazy dog.'),
text: 'The quick brown fox jumps over the lazy dog.',
),
textEditingDeltaHistory: <TextEditingDelta>[],
toggleButtonsState: <ToggleButtonsState>{},
);
void updateTextEditingDeltaHistory(List<TextEditingDelta> textEditingDeltas) {
_data = _data.copyWith(textEditingDeltaHistory: <TextEditingDelta>[
..._data.textEditingDeltaHistory,
...textEditingDeltas
]);
_data = _data.copyWith(
textEditingDeltaHistory: <TextEditingDelta>[
..._data.textEditingDeltaHistory,
...textEditingDeltas,
],
);
setState(() {});
}
void updateToggleButtonsStateOnSelectionChanged(
TextSelection selection, ReplacementTextEditingController controller) {
TextSelection selection,
ReplacementTextEditingController controller,
) {
// When the selection changes we want to check the replacements at the new
// selection. Enable/disable toggle buttons based on the replacements found
// at the new selection.
final List<TextStyle> replacementStyles =
controller.getReplacementsAtSelection(selection);
final List<TextStyle> replacementStyles = controller
.getReplacementsAtSelection(selection);
final Set<ToggleButtonsState> hasChanged = {};
if (replacementStyles.isEmpty) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..removeAll({
ToggleButtonsState.bold,
ToggleButtonsState.italic,
ToggleButtonsState.underline,
}),
toggleButtonsState: Set.from(_data.toggleButtonsState)..removeAll({
ToggleButtonsState.bold,
ToggleButtonsState.italic,
ToggleButtonsState.underline,
}),
);
}
@@ -185,7 +189,9 @@ class AppStateWidgetState extends State<AppStateWidget> {
} else {
controller.disableExpand(attributeMap[index]!);
controller.removeReplacementsAtRange(
replacementRange, attributeMap[index]);
replacementRange,
attributeMap[index],
);
_data = _data.copyWith(replacementsController: controller);
setState(() {});
}
@@ -193,9 +199,6 @@ class AppStateWidgetState extends State<AppStateWidget> {
@override
Widget build(BuildContext context) {
return AppStateManager(
state: _data,
child: widget.child,
);
return AppStateManager(state: _data, child: widget.child);
}
}

View File

@@ -86,7 +86,9 @@ class _BasicTextFieldState extends State<BasicTextField> {
}
void _handleSelectionChanged(
TextSelection selection, SelectionChangedCause? cause) {
TextSelection selection,
SelectionChangedCause? cause,
) {
final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
if (willShowSelectionHandles != _showSelectionHandles) {
setState(() {
@@ -96,9 +98,16 @@ class _BasicTextFieldState extends State<BasicTextField> {
}
void _onDragUpdate(DragUpdateDetails details) {
final Offset startOffset = _renderEditable.maxLines == 1
? Offset(_renderEditable.offset.pixels - _dragStartViewportOffset, 0.0)
: Offset(0.0, _renderEditable.offset.pixels - _dragStartViewportOffset);
final Offset startOffset =
_renderEditable.maxLines == 1
? Offset(
_renderEditable.offset.pixels - _dragStartViewportOffset,
0.0,
)
: Offset(
0.0,
_renderEditable.offset.pixels - _dragStartViewportOffset,
);
_renderEditable.selectPositionAt(
from: _startDetails.globalPosition - startOffset,
@@ -145,8 +154,9 @@ class _BasicTextFieldState extends State<BasicTextField> {
onPanUpdate: (dragUpdateDetails) => _onDragUpdate(dragUpdateDetails),
onSecondaryTapDown: (secondaryTapDownDetails) {
_renderEditable.selectWordsInRange(
from: secondaryTapDownDetails.globalPosition,
cause: SelectionChangedCause.tap);
from: secondaryTapDownDetails.globalPosition,
cause: SelectionChangedCause.tap,
);
_renderEditable.handleSecondaryTapDown(secondaryTapDownDetails);
_textInputClient!.hideToolbar();
_textInputClient!.showToolbar();
@@ -172,19 +182,20 @@ class _BasicTextFieldState extends State<BasicTextField> {
case TargetPlatform.linux:
case TargetPlatform.windows:
_renderEditable.selectWordsInRange(
from: longPressMoveUpdateDetails.globalPosition -
from:
longPressMoveUpdateDetails.globalPosition -
longPressMoveUpdateDetails.offsetFromOrigin,
to: longPressMoveUpdateDetails.globalPosition,
cause: SelectionChangedCause.longPress,
);
}
},
onLongPressEnd: (longPressEndDetails) =>
_textInputClient!.showToolbar(),
onHorizontalDragStart: (dragStartDetails) =>
_onDragStart(dragStartDetails),
onHorizontalDragUpdate: (dragUpdateDetails) =>
_onDragUpdate(dragUpdateDetails),
onLongPressEnd:
(longPressEndDetails) => _textInputClient!.showToolbar(),
onHorizontalDragStart:
(dragStartDetails) => _onDragStart(dragStartDetails),
onHorizontalDragUpdate:
(dragUpdateDetails) => _onDragUpdate(dragUpdateDetails),
child: SizedBox(
height: double.infinity,
width: MediaQuery.of(context).size.width,

View File

@@ -12,24 +12,25 @@ import 'replacements.dart';
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
typedef SelectionChangedCallback = void Function(
TextSelection selection, SelectionChangedCause? cause);
typedef SelectionChangedCallback =
void Function(TextSelection selection, SelectionChangedCause? cause);
/// Signature for a widget builder that builds a context menu for the given
/// editable field.
typedef BasicTextFieldContextMenuBuilder = Widget Function(
BuildContext context,
ClipboardStatus clipboardStatus,
VoidCallback? onCopy,
VoidCallback? onCut,
VoidCallback? onPaste,
VoidCallback? onSelectAll,
VoidCallback? onLookUp,
VoidCallback? onLiveTextInput,
VoidCallback? onSearchWeb,
VoidCallback? onShare,
TextSelectionToolbarAnchors anchors,
);
typedef BasicTextFieldContextMenuBuilder =
Widget Function(
BuildContext context,
ClipboardStatus clipboardStatus,
VoidCallback? onCopy,
VoidCallback? onCut,
VoidCallback? onPaste,
VoidCallback? onSelectAll,
VoidCallback? onLookUp,
VoidCallback? onLiveTextInput,
VoidCallback? onSearchWeb,
VoidCallback? onShare,
TextSelectionToolbarAnchors anchors,
);
/// A basic text input client. An implementation of [DeltaTextInputClient] meant to
/// send/receive information from the framework to the platform's text input plugin
@@ -112,7 +113,9 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
@override
void didChangeInputControl(
TextInputControl? oldControl, TextInputControl? newControl) {
TextInputControl? oldControl,
TextInputControl? newControl,
) {
if (_hasFocus && _hasInputConnection) {
oldControl?.hide();
newControl?.show();
@@ -172,7 +175,9 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
}
@override
void updateEditingValue(TextEditingValue value) {/* Not using */}
void updateEditingValue(TextEditingValue value) {
/* Not using */
}
@override
void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
@@ -193,7 +198,7 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
final bool selectionChanged =
_value.selection.start != value.selection.start ||
_value.selection.end != value.selection.end;
_value.selection.end != value.selection.end;
manager.updateTextEditingDeltaHistory(textEditingDeltas);
_value = value;
@@ -206,8 +211,10 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
}
if (selectionChanged) {
manager.updateToggleButtonsStateOnSelectionChanged(value.selection,
widget.controller as ReplacementTextEditingController);
manager.updateToggleButtonsStateOnSelectionChanged(
value.selection,
widget.controller as ReplacementTextEditingController,
);
}
}
@@ -302,11 +309,14 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
if (_hasFocus) {
if (!_value.selection.isValid) {
// Place cursor at the end if the selection is invalid when we receive focus.
final TextSelection validSelection =
TextSelection.collapsed(offset: _value.text.length);
final TextSelection validSelection = TextSelection.collapsed(
offset: _value.text.length,
);
_handleSelectionChanged(validSelection, null);
manager.updateToggleButtonsStateOnSelectionChanged(validSelection,
widget.controller as ReplacementTextEditingController);
manager.updateToggleButtonsStateOnSelectionChanged(
validSelection,
widget.controller as ReplacementTextEditingController,
);
}
}
}
@@ -331,32 +341,38 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
// These actions have yet to be implemented for this sample.
static final Map<Type, Action<Intent>> _unsupportedActions =
<Type, Action<Intent>>{
DeleteToNextWordBoundaryIntent: DoNothingAction(consumesKey: false),
DeleteToLineBreakIntent: DoNothingAction(consumesKey: false),
ExtendSelectionToNextWordBoundaryIntent:
DoNothingAction(consumesKey: false),
ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent:
DoNothingAction(consumesKey: false),
ExtendSelectionToLineBreakIntent: DoNothingAction(consumesKey: false),
ExtendSelectionVerticallyToAdjacentLineIntent:
DoNothingAction(consumesKey: false),
ExtendSelectionVerticallyToAdjacentPageIntent:
DoNothingAction(consumesKey: false),
ExtendSelectionToNextParagraphBoundaryIntent:
DoNothingAction(consumesKey: false),
ExtendSelectionToDocumentBoundaryIntent:
DoNothingAction(consumesKey: false),
ExtendSelectionByPageIntent: DoNothingAction(consumesKey: false),
ExpandSelectionToDocumentBoundaryIntent:
DoNothingAction(consumesKey: false),
ExpandSelectionToLineBreakIntent: DoNothingAction(consumesKey: false),
ScrollToDocumentBoundaryIntent: DoNothingAction(consumesKey: false),
RedoTextIntent: DoNothingAction(consumesKey: false),
ReplaceTextIntent: DoNothingAction(consumesKey: false),
UndoTextIntent: DoNothingAction(consumesKey: false),
UpdateSelectionIntent: DoNothingAction(consumesKey: false),
TransposeCharactersIntent: DoNothingAction(consumesKey: false),
};
DeleteToNextWordBoundaryIntent: DoNothingAction(consumesKey: false),
DeleteToLineBreakIntent: DoNothingAction(consumesKey: false),
ExtendSelectionToNextWordBoundaryIntent: DoNothingAction(
consumesKey: false,
),
ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent:
DoNothingAction(consumesKey: false),
ExtendSelectionToLineBreakIntent: DoNothingAction(consumesKey: false),
ExtendSelectionVerticallyToAdjacentLineIntent: DoNothingAction(
consumesKey: false,
),
ExtendSelectionVerticallyToAdjacentPageIntent: DoNothingAction(
consumesKey: false,
),
ExtendSelectionToNextParagraphBoundaryIntent: DoNothingAction(
consumesKey: false,
),
ExtendSelectionToDocumentBoundaryIntent: DoNothingAction(
consumesKey: false,
),
ExtendSelectionByPageIntent: DoNothingAction(consumesKey: false),
ExpandSelectionToDocumentBoundaryIntent: DoNothingAction(
consumesKey: false,
),
ExpandSelectionToLineBreakIntent: DoNothingAction(consumesKey: false),
ScrollToDocumentBoundaryIntent: DoNothingAction(consumesKey: false),
RedoTextIntent: DoNothingAction(consumesKey: false),
ReplaceTextIntent: DoNothingAction(consumesKey: false),
UndoTextIntent: DoNothingAction(consumesKey: false),
UpdateSelectionIntent: DoNothingAction(consumesKey: false),
TransposeCharactersIntent: DoNothingAction(consumesKey: false),
};
/// Keyboard text editing actions.
// The Handling of the default text editing shortcuts with deltas
@@ -372,9 +388,10 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
),
ExtendSelectionByCharacterIntent:
CallbackAction<ExtendSelectionByCharacterIntent>(
onInvoke: (intent) =>
_extendSelection(intent.forward, intent.collapseSelection),
),
onInvoke:
(intent) =>
_extendSelection(intent.forward, intent.collapseSelection),
),
SelectAllTextIntent: CallbackAction<SelectAllTextIntent>(
onInvoke: (intent) => selectAll(intent.cause),
),
@@ -384,9 +401,7 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
PasteTextIntent: CallbackAction<PasteTextIntent>(
onInvoke: (intent) => pasteText(intent.cause),
),
DoNothingAndStopPropagationTextIntent: DoNothingAction(
consumesKey: false,
),
DoNothingAndStopPropagationTextIntent: DoNothingAction(consumesKey: false),
..._unsupportedActions,
};
@@ -447,22 +462,24 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
_selection.isNormalized ? _selection.start : _selection.end;
final int lastOffset =
_selection.isNormalized ? _selection.end : _selection.start;
selection =
TextSelection.collapsed(offset: forward ? lastOffset : firstOffset);
selection = TextSelection.collapsed(
offset: forward ? lastOffset : firstOffset,
);
} else {
if (forward && _selection.baseOffset == _value.text.length) return;
if (!forward && _selection.baseOffset == 0) return;
final int adjustment = forward
? _value.text
.substring(_selection.baseOffset)
.characters
.first
.length
: -_value.text
.substring(0, _selection.baseOffset)
.characters
.last
.length;
final int adjustment =
forward
? _value.text
.substring(_selection.baseOffset)
.characters
.first
.length
: -_value.text
.substring(0, _selection.baseOffset)
.characters
.last
.length;
selection = TextSelection.collapsed(
offset: _selection.baseOffset + adjustment,
);
@@ -470,13 +487,18 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
} else {
if (forward && _selection.extentOffset == _value.text.length) return;
if (!forward && _selection.extentOffset == 0) return;
final int adjustment = forward
? _value.text.substring(_selection.baseOffset).characters.first.length
: -_value.text
.substring(0, _selection.baseOffset)
.characters
.last
.length;
final int adjustment =
forward
? _value.text
.substring(_selection.baseOffset)
.characters
.first
.length
: -_value.text
.substring(0, _selection.baseOffset)
.characters
.last
.length;
selection = TextSelection(
baseOffset: _selection.baseOffset,
extentOffset: _selection.extentOffset + adjustment,
@@ -494,7 +516,9 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
}
void _userUpdateTextEditingValueWithDelta(
TextEditingDelta textEditingDelta, SelectionChangedCause cause) {
TextEditingDelta textEditingDelta,
SelectionChangedCause cause,
) {
TextEditingValue value = _value;
value = textEditingDelta.apply(value);
@@ -538,14 +562,16 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
void _updateComposingRectIfNeeded() {
final TextRange composingRange = _value.composing;
assert(mounted);
Rect? composingRect =
renderEditable.getRectForComposingRange(composingRange);
Rect? composingRect = renderEditable.getRectForComposingRange(
composingRange,
);
// Send the caret location instead if there's no marked text yet.
if (composingRect == null) {
assert(!composingRange.isValid || composingRange.isCollapsed);
final int offset = composingRange.isValid ? composingRange.start : 0;
composingRect =
renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
composingRect = renderEditable.getLocalRectForCaret(
TextPosition(offset: offset),
);
}
_textInputConnection!.setComposingRect(composingRect);
}
@@ -555,10 +581,12 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
if (selection == null || !selection.isValid || !selection.isCollapsed) {
return;
}
final TextPosition currentTextPosition =
TextPosition(offset: selection.baseOffset);
final Rect caretRect =
renderEditable.getLocalRectForCaret(currentTextPosition);
final TextPosition currentTextPosition = TextPosition(
offset: selection.baseOffset,
);
final Rect caretRect = renderEditable.getLocalRectForCaret(
currentTextPosition,
);
_textInputConnection!.setCaretRect(caretRect);
}
@@ -574,8 +602,9 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
}
_updateComposingRectIfNeeded();
_updateCaretRectIfNeeded();
SchedulerBinding.instance
.addPostFrameCallback(_schedulePeriodicPostFrameCallbacks);
SchedulerBinding.instance.addPostFrameCallback(
_schedulePeriodicPostFrameCallbacks,
);
}
/// [TextSelectionDelegate] method implementations.
@@ -620,7 +649,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
TextEditingDeltaNonTextUpdate(
oldText: textEditingValue.text,
selection: TextSelection.collapsed(
offset: textEditingValue.selection.end),
offset: textEditingValue.selection.end,
),
composing: TextRange.empty,
),
cause,
@@ -638,8 +668,10 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
if (cutRange.isCollapsed) return;
Clipboard.setData(ClipboardData(text: cutRange.textInside(text)));
final int lastSelectionIndex =
math.min(cutRange.baseOffset, cutRange.extentOffset);
final int lastSelectionIndex = math.min(
cutRange.baseOffset,
cutRange.extentOffset,
);
_userUpdateTextEditingValueWithDelta(
TextEditingDeltaReplacement(
oldText: textEditingValue.text,
@@ -665,7 +697,9 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
// After the paste, the cursor should be collapsed and located after the
// pasted content.
final int lastSelectionIndex = math.max(
pasteRange.baseOffset, pasteRange.baseOffset + data.text!.length);
pasteRange.baseOffset,
pasteRange.baseOffset + data.text!.length,
);
_userUpdateTextEditingValueWithDelta(
TextEditingDeltaReplacement(
@@ -683,8 +717,10 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
@override
void selectAll(SelectionChangedCause cause) {
final TextSelection newSelection = _value.selection
.copyWith(baseOffset: 0, extentOffset: _value.text.length);
final TextSelection newSelection = _value.selection.copyWith(
baseOffset: 0,
extentOffset: _value.text.length,
);
_userUpdateTextEditingValueWithDelta(
TextEditingDeltaNonTextUpdate(
oldText: textEditingValue.text,
@@ -712,7 +748,9 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
@override
void userUpdateTextEditingValue(
TextEditingValue value, SelectionChangedCause cause) {
TextEditingValue value,
SelectionChangedCause cause,
) {
if (value == _value) return;
final bool selectionChanged = _value.selection != value.selection;
@@ -727,10 +765,10 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
if (selectionChanged && !textChanged) {
final TextEditingDeltaNonTextUpdate selectionUpdate =
TextEditingDeltaNonTextUpdate(
oldText: value.text,
selection: value.selection,
composing: value.composing,
);
oldText: value.text,
selection: value.selection,
composing: value.composing,
);
if (widget.controller is ReplacementTextEditingController) {
(widget.controller as ReplacementTextEditingController)
.syncReplacementRanges(selectionUpdate);
@@ -741,7 +779,7 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
final bool selectionRangeChanged =
_value.selection.start != value.selection.start ||
_value.selection.end != value.selection.end;
_value.selection.end != value.selection.end;
_value = value;
@@ -749,8 +787,10 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
_handleSelectionChanged(_value.selection, cause);
if (selectionRangeChanged) {
manager.updateToggleButtonsStateOnSelectionChanged(_value.selection,
widget.controller as ReplacementTextEditingController);
manager.updateToggleButtonsStateOnSelectionChanged(
_value.selection,
widget.controller as ReplacementTextEditingController,
);
}
}
}
@@ -776,7 +816,9 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
_textKey.currentContext!.findRenderObject()! as RenderEditable;
void _handleSelectionChanged(
TextSelection selection, SelectionChangedCause? cause) {
TextSelection selection,
SelectionChangedCause? cause,
) {
// We return early if the selection is not valid. This can happen when the
// text of the editable is updated at the same time as the selection is
// changed by a gesture event.
@@ -820,13 +862,16 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
try {
widget.onSelectionChanged.call(selection, cause);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets',
context:
ErrorDescription('while calling onSelectionChanged for $cause'),
));
FlutterError.reportError(
FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets',
context: ErrorDescription(
'while calling onSelectionChanged for $cause',
),
),
);
}
}
@@ -846,40 +891,41 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
onSelectionHandleTapped: () {
_toggleToolbar();
},
contextMenuBuilder: widget.contextMenuBuilder == null || kIsWeb
? null
: (context) {
return widget.contextMenuBuilder!(
context,
_clipboardStatus!.value,
copyEnabled
? () => copySelection(SelectionChangedCause.toolbar)
: null,
cutEnabled
? () => cutSelection(SelectionChangedCause.toolbar)
: null,
pasteEnabled
? () => pasteText(SelectionChangedCause.toolbar)
: null,
selectAllEnabled
? () => selectAll(SelectionChangedCause.toolbar)
: null,
lookUpEnabled
? () => _lookUpSelection(SelectionChangedCause.toolbar)
: null,
liveTextInputEnabled
? () => _startLiveTextInput(SelectionChangedCause.toolbar)
: null,
searchWebEnabled
? () =>
_searchWebForSelection(SelectionChangedCause.toolbar)
: null,
shareEnabled
? () => _shareSelection(SelectionChangedCause.toolbar)
: null,
_contextMenuAnchors,
);
},
contextMenuBuilder:
widget.contextMenuBuilder == null || kIsWeb
? null
: (context) {
return widget.contextMenuBuilder!(
context,
_clipboardStatus!.value,
copyEnabled
? () => copySelection(SelectionChangedCause.toolbar)
: null,
cutEnabled
? () => cutSelection(SelectionChangedCause.toolbar)
: null,
pasteEnabled
? () => pasteText(SelectionChangedCause.toolbar)
: null,
selectAllEnabled
? () => selectAll(SelectionChangedCause.toolbar)
: null,
lookUpEnabled
? () => _lookUpSelection(SelectionChangedCause.toolbar)
: null,
liveTextInputEnabled
? () => _startLiveTextInput(SelectionChangedCause.toolbar)
: null,
searchWebEnabled
? () =>
_searchWebForSelection(SelectionChangedCause.toolbar)
: null,
shareEnabled
? () => _shareSelection(SelectionChangedCause.toolbar)
: null,
_contextMenuAnchors,
);
},
magnifierConfiguration: TextMagnifierConfiguration.disabled,
);
@@ -935,18 +981,20 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
final String selectedGraphemes = selection.textInside(currText);
final int firstSelectedGraphemeExtent =
selectedGraphemes.characters.first.length;
final Rect? startCharacterRect =
renderEditable.getRectForComposingRange(TextRange(
start: selection.start,
end: selection.start + firstSelectedGraphemeExtent,
));
final Rect? startCharacterRect = renderEditable.getRectForComposingRange(
TextRange(
start: selection.start,
end: selection.start + firstSelectedGraphemeExtent,
),
);
final int lastSelectedGraphemeExtent =
selectedGraphemes.characters.last.length;
final Rect? endCharacterRect =
renderEditable.getRectForComposingRange(TextRange(
start: selection.end - lastSelectedGraphemeExtent,
end: selection.end,
));
final Rect? endCharacterRect = renderEditable.getRectForComposingRange(
TextRange(
start: selection.end - lastSelectedGraphemeExtent,
end: selection.end,
),
);
return _GlyphHeights(
start: startCharacterRect?.height ?? renderEditable.preferredLineHeight,
end: endCharacterRect?.height ?? renderEditable.preferredLineHeight,
@@ -963,8 +1011,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
final _GlyphHeights glyphHeights = _getGlyphHeights();
final TextSelection selection = textEditingValue.selection;
final List<TextSelectionPoint> points =
renderEditable.getEndpointsForSelection(selection);
final List<TextSelectionPoint> points = renderEditable
.getEndpointsForSelection(selection);
return TextSelectionToolbarAnchors.fromSelection(
renderBox: renderEditable,
startGlyphHeight: glyphHeights.start,
@@ -1015,15 +1063,13 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
/// Currently this is only implemented for iOS.
/// Throws an error if the selection is empty or collapsed.
Future<void> _lookUpSelection(SelectionChangedCause cause) async {
final String text =
textEditingValue.selection.textInside(textEditingValue.text);
final String text = textEditingValue.selection.textInside(
textEditingValue.text,
);
if (text.isEmpty) {
return;
}
await SystemChannels.platform.invokeMethod(
'LookUp.invoke',
text,
);
await SystemChannels.platform.invokeMethod('LookUp.invoke', text);
}
@override
@@ -1042,13 +1088,11 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
///
/// Currently this is only implemented for iOS.
Future<void> _searchWebForSelection(SelectionChangedCause cause) async {
final String text =
textEditingValue.selection.textInside(textEditingValue.text);
final String text = textEditingValue.selection.textInside(
textEditingValue.text,
);
if (text.isNotEmpty) {
await SystemChannels.platform.invokeMethod(
'SearchWeb.invoke',
text,
);
await SystemChannels.platform.invokeMethod('SearchWeb.invoke', text);
}
}
@@ -1075,13 +1119,11 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
///
/// Currently this is only implemented for iOS and Android.
Future<void> _shareSelection(SelectionChangedCause cause) async {
final String text =
textEditingValue.selection.textInside(textEditingValue.text);
final String text = textEditingValue.selection.textInside(
textEditingValue.text,
);
if (text.isNotEmpty) {
await SystemChannels.platform.invokeMethod(
'Share.invoke',
text,
);
await SystemChannels.platform.invokeMethod('Share.invoke', text);
}
}
@@ -1322,10 +1364,7 @@ class _Editable extends MultiChildRenderObjectWidget {
/// The start and end glyph heights of some range of text.
@immutable
class _GlyphHeights {
const _GlyphHeights({
required this.start,
required this.end,
});
const _GlyphHeights({required this.start, required this.end});
/// The glyph height of the first line.
final double start;

View File

@@ -4,11 +4,7 @@ import 'app_state.dart';
import 'app_state_manager.dart';
/// The toggle buttons that can be selected.
enum ToggleButtonsState {
bold,
italic,
underline,
}
enum ToggleButtonsState { bold, italic, underline }
class FormattingToolbar extends StatelessWidget {
const FormattingToolbar({super.key});
@@ -25,15 +21,20 @@ class FormattingToolbar extends StatelessWidget {
ToggleButtons(
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
isSelected: [
manager.appState.toggleButtonsState
.contains(ToggleButtonsState.bold),
manager.appState.toggleButtonsState
.contains(ToggleButtonsState.italic),
manager.appState.toggleButtonsState
.contains(ToggleButtonsState.underline),
manager.appState.toggleButtonsState.contains(
ToggleButtonsState.bold,
),
manager.appState.toggleButtonsState.contains(
ToggleButtonsState.italic,
),
manager.appState.toggleButtonsState.contains(
ToggleButtonsState.underline,
),
],
onPressed: (index) => AppStateWidget.of(context)
.updateToggleButtonsStateOnButtonPressed(index),
onPressed:
(index) => AppStateWidget.of(
context,
).updateToggleButtonsStateOnButtonPressed(index),
children: const [
Icon(Icons.format_bold),
Icon(Icons.format_italic),

View File

@@ -20,9 +20,7 @@ class MyApp extends StatelessWidget {
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Simplistic Editor',
theme: ThemeData(
primarySwatch: Colors.blue,
),
theme: ThemeData(primarySwatch: Colors.blue),
home: const MyHomePage(title: 'Simplistic Editor'),
),
);
@@ -56,7 +54,9 @@ class _MyHomePageState extends State<MyHomePage> {
}
static Route<Object?> _aboutDialogBuilder(
BuildContext context, Object? arguments) {
BuildContext context,
Object? arguments,
) {
const String aboutContent =
'TextEditingDeltas are a new feature in the latest Flutter stable release that give the user'
' finer grain control over the changes that occur during text input. There are four types of'
@@ -67,10 +67,11 @@ class _MyHomePageState extends State<MyHomePage> {
' more powerful rich text editing applications such as this small example. This feature is supported on all platforms.';
return DialogRoute<void>(
context: context,
builder: (context) => const AlertDialog(
title: Center(child: Text('About')),
content: Text(aboutContent),
),
builder:
(context) => const AlertDialog(
title: Center(child: Text('About')),
content: Text(aboutContent),
),
);
}
@@ -99,17 +100,12 @@ class _MyHomePageState extends State<MyHomePage> {
padding: const EdgeInsets.symmetric(horizontal: 35.0),
child: BasicTextField(
controller: _replacementTextEditingController,
style: const TextStyle(
fontSize: 18.0,
color: Colors.black,
),
style: const TextStyle(fontSize: 18.0, color: Colors.black),
focusNode: _focusNode,
),
),
),
const Expanded(
child: TextEditingDeltaHistoryView(),
),
const Expanded(child: TextEditingDeltaHistoryView()),
],
),
),

View File

@@ -70,18 +70,12 @@ class TextEditingInlineSpanReplacement {
range.end > deletedRange.start) &&
range.end <= deletedRange.end) {
return copy(
range: TextRange(
start: range.start,
end: deletedRange.start,
),
range: TextRange(start: range.start, end: deletedRange.start),
);
} else if (range.start < deletedRange.start &&
range.end > deletedRange.end) {
return copy(
range: TextRange(
start: range.start,
end: range.end - deletedLength,
),
range: TextRange(start: range.start, end: range.end - deletedLength),
);
} else if (range.start >= deletedRange.start &&
range.end <= deletedRange.end) {
@@ -96,46 +90,29 @@ class TextEditingInlineSpanReplacement {
);
} else if (range.end <= deletedRange.start &&
range.end < deletedRange.end) {
return copy(
range: TextRange(
start: range.start,
end: range.end,
),
);
return copy(range: TextRange(start: range.start, end: range.end));
}
return null;
}
TextEditingInlineSpanReplacement? onInsertion(
TextEditingDeltaInsertion delta) {
TextEditingDeltaInsertion delta,
) {
final int insertionOffset = delta.insertionOffset;
final int insertedLength = delta.textInserted.length;
if (range.end == insertionOffset) {
if (expand) {
return copy(
range: TextRange(
start: range.start,
end: range.end + insertedLength,
),
range: TextRange(start: range.start, end: range.end + insertedLength),
);
} else {
return copy(
range: TextRange(
start: range.start,
end: range.end,
),
);
return copy(range: TextRange(start: range.start, end: range.end));
}
}
if (range.start < insertionOffset && range.end < insertionOffset) {
return copy(
range: TextRange(
start: range.start,
end: range.end,
),
);
return copy(range: TextRange(start: range.start, end: range.end));
} else if (range.start >= insertionOffset && range.end > insertionOffset) {
return copy(
range: TextRange(
@@ -145,10 +122,7 @@ class TextEditingInlineSpanReplacement {
);
} else if (range.start < insertionOffset && range.end > insertionOffset) {
return copy(
range: TextRange(
start: range.start,
end: range.end + insertedLength,
),
range: TextRange(start: range.start, end: range.end + insertedLength),
);
}
@@ -156,7 +130,8 @@ class TextEditingInlineSpanReplacement {
}
List<TextEditingInlineSpanReplacement>? onReplacement(
TextEditingDeltaReplacement delta) {
TextEditingDeltaReplacement delta,
) {
final TextRange replacedRange = delta.replacedRange;
final bool replacementShortenedText =
delta.replacementText.length < delta.textReplaced.length;
@@ -164,9 +139,10 @@ class TextEditingInlineSpanReplacement {
delta.replacementText.length > delta.textReplaced.length;
final bool replacementEqualLength =
delta.replacementText.length == delta.textReplaced.length;
final int changedOffset = replacementShortenedText
? delta.textReplaced.length - delta.replacementText.length
: delta.replacementText.length - delta.textReplaced.length;
final int changedOffset =
replacementShortenedText
? delta.textReplaced.length - delta.replacementText.length
: delta.replacementText.length - delta.textReplaced.length;
if (range.start >= replacedRange.start &&
(range.start < replacedRange.end && range.end > replacedRange.end)) {
@@ -190,35 +166,20 @@ class TextEditingInlineSpanReplacement {
];
} else if (replacementEqualLength) {
return [
copy(
range: TextRange(
start: replacedRange.end,
end: range.end,
),
),
copy(range: TextRange(start: replacedRange.end, end: range.end)),
];
}
} else if ((range.start < replacedRange.start &&
range.end > replacedRange.start) &&
range.end <= replacedRange.end) {
return [
copy(
range: TextRange(
start: range.start,
end: replacedRange.start,
),
),
copy(range: TextRange(start: range.start, end: replacedRange.start)),
];
} else if (range.start < replacedRange.start &&
range.end > replacedRange.end) {
if (replacementShortenedText) {
return [
copy(
range: TextRange(
start: range.start,
end: replacedRange.start,
),
),
copy(range: TextRange(start: range.start, end: replacedRange.start)),
copy(
range: TextRange(
start: replacedRange.end - changedOffset,
@@ -228,12 +189,7 @@ class TextEditingInlineSpanReplacement {
];
} else if (replacementLengthenedText) {
return [
copy(
range: TextRange(
start: range.start,
end: replacedRange.start,
),
),
copy(range: TextRange(start: range.start, end: replacedRange.start)),
copy(
range: TextRange(
start: replacedRange.end + changedOffset,
@@ -243,18 +199,8 @@ class TextEditingInlineSpanReplacement {
];
} else if (replacementEqualLength) {
return [
copy(
range: TextRange(
start: range.start,
end: replacedRange.start,
),
),
copy(
range: TextRange(
start: replacedRange.end,
end: range.end,
),
),
copy(range: TextRange(start: range.start, end: replacedRange.start)),
copy(range: TextRange(start: replacedRange.end, end: range.end)),
];
}
} else if (range.start >= replacedRange.start &&
@@ -286,21 +232,15 @@ class TextEditingInlineSpanReplacement {
}
} else if (range.end <= replacedRange.start &&
range.end < replacedRange.end) {
return [
copy(
range: TextRange(
start: range.start,
end: range.end,
),
),
];
return [copy(range: TextRange(start: range.start, end: range.end))];
}
return null;
}
TextEditingInlineSpanReplacement? onNonTextUpdate(
TextEditingDeltaNonTextUpdate delta) {
TextEditingDeltaNonTextUpdate delta,
) {
if (range.isCollapsed) {
if (range.start != delta.selection.start &&
range.end != delta.selection.end) {
@@ -313,41 +253,21 @@ class TextEditingInlineSpanReplacement {
List<TextEditingInlineSpanReplacement>? removeRange(TextRange removalRange) {
if (range.start >= removalRange.start &&
(range.start < removalRange.end && range.end > removalRange.end)) {
return [
copy(
range: TextRange(
start: removalRange.end,
end: range.end,
),
),
];
return [copy(range: TextRange(start: removalRange.end, end: range.end))];
} else if ((range.start < removalRange.start &&
range.end > removalRange.start) &&
range.end <= removalRange.end) {
return [
copy(
range: TextRange(
start: range.start,
end: removalRange.start,
),
),
copy(range: TextRange(start: range.start, end: removalRange.start)),
];
} else if (range.start < removalRange.start &&
range.end > removalRange.end) {
return [
copy(
range: TextRange(
start: range.start,
end: removalRange.start,
),
range: TextRange(start: range.start, end: removalRange.start),
expand: removalRange.isCollapsed ? false : expand,
),
copy(
range: TextRange(
start: removalRange.end,
end: range.end,
),
),
copy(range: TextRange(start: removalRange.end, end: range.end)),
];
} else if (range.start >= removalRange.start &&
range.end <= removalRange.end) {
@@ -369,7 +289,10 @@ class TextEditingInlineSpanReplacement {
/// is updated to the specified value.
TextEditingInlineSpanReplacement copy({TextRange? range, bool? expand}) {
return TextEditingInlineSpanReplacement(
range ?? this.range, generator, expand ?? this.expand);
range ?? this.range,
generator,
expand ?? this.expand,
);
}
@override
@@ -397,10 +320,11 @@ class ReplacementTextEditingController extends TextEditingController {
/// Creates a controller for an editable text field from an initial [TextEditingValue].
///
/// This constructor treats a null [value] argument as if it were [TextEditingValue.empty].
ReplacementTextEditingController.fromValue(super.value,
{List<TextEditingInlineSpanReplacement>? replacements,
this.composingRegionReplaceable = true})
: super.fromValue();
ReplacementTextEditingController.fromValue(
super.value, {
List<TextEditingInlineSpanReplacement>? replacements,
this.composingRegionReplaceable = true,
}) : super.fromValue();
/// The [TextEditingInlineSpanReplacement]s that are evaluated on the editing value.
///
@@ -528,9 +452,9 @@ class ReplacementTextEditingController extends TextEditingController {
TextStyle? style,
required bool withComposing,
}) {
assert(!value.composing.isValid ||
!withComposing ||
value.isComposingRangeValid);
assert(
!value.composing.isValid || !withComposing || value.isComposingRangeValid,
);
// Keep a mapping of TextRanges to the InlineSpan to replace it with.
final Map<TextRange, InlineSpan> rangeSpanMapping =
@@ -558,15 +482,20 @@ class ReplacementTextEditingController extends TextEditingController {
if (composingRegionReplaceable &&
value.isComposingRangeValid &&
withComposing) {
_addToMappingWithOverlaps((value, range) {
final TextStyle composingStyle = style != null
? style.merge(const TextStyle(decoration: TextDecoration.underline))
: const TextStyle(decoration: TextDecoration.underline);
return TextSpan(
style: composingStyle,
text: value,
);
}, value.composing, rangeSpanMapping, value.text);
_addToMappingWithOverlaps(
(value, range) {
final TextStyle composingStyle =
style != null
? style.merge(
const TextStyle(decoration: TextDecoration.underline),
)
: const TextStyle(decoration: TextDecoration.underline);
return TextSpan(style: composingStyle, text: value);
},
value.composing,
rangeSpanMapping,
value.text,
);
}
// Sort the matches by start index. Since no overlapping exists, this is safe.
@@ -579,28 +508,30 @@ class ReplacementTextEditingController extends TextEditingController {
int previousEndIndex = 0;
for (final TextRange range in sortedRanges) {
if (range.start > previousEndIndex) {
spans.add(TextSpan(
text: value.text.substring(previousEndIndex, range.start)));
spans.add(
TextSpan(text: value.text.substring(previousEndIndex, range.start)),
);
}
spans.add(rangeSpanMapping[range]!);
previousEndIndex = range.end;
}
// Add any trailing text as a regular TextSpan.
if (previousEndIndex < value.text.length) {
spans.add(TextSpan(
text: value.text.substring(previousEndIndex, value.text.length)));
spans.add(
TextSpan(
text: value.text.substring(previousEndIndex, value.text.length),
),
);
}
return TextSpan(
style: style,
children: spans,
);
return TextSpan(style: style, children: spans);
}
static void _addToMappingWithOverlaps(
InlineSpanGenerator generator,
TextRange matchedRange,
Map<TextRange, InlineSpan> rangeSpanMapping,
String text) {
InlineSpanGenerator generator,
TextRange matchedRange,
Map<TextRange, InlineSpan> rangeSpanMapping,
String text,
) {
// In some cases we should allow for overlap.
// For example in the case of two TextSpans matching the same range for replacement,
// we should try to merge the styles into one TextStyle and build a new TextSpan.
@@ -620,14 +551,14 @@ class ReplacementTextEditingController extends TextEditingController {
overlappingTriples.add(<dynamic>[
matchedRange.start,
matchedRange.end,
generator(matchedRange.textInside(text), matchedRange).style
generator(matchedRange.textInside(text), matchedRange).style,
]);
for (final TextRange overlappingRange in overlapRanges) {
overlappingTriples.add(<dynamic>[
overlappingRange.start,
overlappingRange.end,
rangeSpanMapping[overlappingRange]!.style
rangeSpanMapping[overlappingRange]!.style,
]);
rangeSpanMapping.remove(overlappingRange);
}
@@ -643,8 +574,10 @@ class ReplacementTextEditingController extends TextEditingController {
if (math.max(tripleA[0] as int, tripleB[0] as int) <=
math.min(tripleA[1] as int, tripleB[1] as int) &&
tripleA[2] == tripleB[2]) {
toRemoveRangesThatHaveBeenMerged
.addAll(<dynamic>[tripleA, tripleB]);
toRemoveRangesThatHaveBeenMerged.addAll(<dynamic>[
tripleA,
tripleB,
]);
tripleA = <dynamic>[
math.min(tripleA[0] as int, tripleB[0] as int),
math.max(tripleA[1] as int, tripleB[1] as int),
@@ -697,8 +630,10 @@ class ReplacementTextEditingController extends TextEditingController {
styles = styles.difference(end[endPoints[i]]!);
styles.addAll(start[endPoints[i]]!);
TextStyle? mergedStyles;
final TextRange uniqueRange =
TextRange(start: endPoints[i], end: otherEndPoints[i]);
final TextRange uniqueRange = TextRange(
start: endPoints[i],
end: otherEndPoints[i],
);
for (final TextStyle style in styles) {
if (mergedStyles == null) {
mergedStyles = style;
@@ -706,14 +641,18 @@ class ReplacementTextEditingController extends TextEditingController {
mergedStyles = mergedStyles.merge(style);
}
}
rangeSpanMapping[uniqueRange] =
TextSpan(text: uniqueRange.textInside(text), style: mergedStyles);
rangeSpanMapping[uniqueRange] = TextSpan(
text: uniqueRange.textInside(text),
style: mergedStyles,
);
}
}
if (!overlap) {
rangeSpanMapping[matchedRange] =
generator(matchedRange.textInside(text), matchedRange);
rangeSpanMapping[matchedRange] = generator(
matchedRange.textInside(text),
matchedRange,
);
}
// Clean up collapsed ranges that we don't need to style.
@@ -734,9 +673,10 @@ class ReplacementTextEditingController extends TextEditingController {
for (final TextEditingInlineSpanReplacement replacement in replacements!) {
if (replacement.range.end == selection.start) {
TextStyle? replacementStyle = (replacement.generator(
'', const TextRange.collapsed(0)) as TextSpan)
.style;
TextStyle? replacementStyle =
(replacement.generator('', const TextRange.collapsed(0))
as TextSpan)
.style;
if (replacementStyle! == style) {
toRemove.add(replacement);
toAdd.add(replacement.copy(expand: false));
@@ -771,12 +711,14 @@ class ReplacementTextEditingController extends TextEditingController {
if (selection.end != replacement.range.start) {
if (selection.start == replacement.range.end) {
if (replacement.expand) {
stylesAtSelection
.add(replacement.generator('', replacement.range).style!);
stylesAtSelection.add(
replacement.generator('', replacement.range).style!,
);
}
} else {
stylesAtSelection
.add(replacement.generator('', replacement.range).style!);
stylesAtSelection.add(
replacement.generator('', replacement.range).style!,
);
}
}
}
@@ -785,8 +727,9 @@ class ReplacementTextEditingController extends TextEditingController {
math.min(replacement.range.end, selection.end)) {
if (replacement.range.start <= selection.start &&
replacement.range.end >= selection.end) {
stylesAtSelection
.add(replacement.generator('', replacement.range).style!);
stylesAtSelection.add(
replacement.generator('', replacement.range).style!,
);
}
}
}
@@ -801,8 +744,10 @@ class ReplacementTextEditingController extends TextEditingController {
for (int i = 0; i < replacements!.length; i++) {
TextEditingInlineSpanReplacement replacement = replacements![i];
InlineSpan replacementSpan =
replacement.generator('', const TextRange.collapsed(0));
InlineSpan replacementSpan = replacement.generator(
'',
const TextRange.collapsed(0),
);
TextStyle? replacementStyle = replacementSpan.style;
late final TextEditingInlineSpanReplacement? mutatedReplacement;
@@ -810,8 +755,8 @@ class ReplacementTextEditingController extends TextEditingController {
math.min(replacement.range.end, removalRange.end)) &&
replacementStyle != null) {
if (replacementStyle == attribute!) {
List<TextEditingInlineSpanReplacement>? newReplacements =
replacement.removeRange(removalRange);
List<TextEditingInlineSpanReplacement>? newReplacements = replacement
.removeRange(removalRange);
if (newReplacements != null) {
if (newReplacements.length == 1) {

View File

@@ -7,7 +7,8 @@ class TextEditingDeltaHistoryView extends StatelessWidget {
const TextEditingDeltaHistoryView({super.key});
List<Widget> _buildTextEditingDeltaHistoryViews(
List<TextEditingDelta> textEditingDeltas) {
List<TextEditingDelta> textEditingDeltas,
) {
List<Widget> textEditingDeltaViews = [];
for (final TextEditingDelta delta in textEditingDeltas) {
@@ -68,7 +69,8 @@ class TextEditingDeltaHistoryView extends StatelessWidget {
children: [
Expanded(
child: Tooltip(
message: 'The type of text input that is occurring.'
message:
'The type of text input that is occurring.'
' Check out the documentation for TextEditingDelta for more information.',
child: _buildTextEditingDeltaViewHeading('Delta Type'),
),
@@ -127,7 +129,8 @@ class TextEditingDeltaHistoryView extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 35.0),
itemBuilder: (context, index) {
return _buildTextEditingDeltaHistoryViews(
manager.appState.textEditingDeltaHistory)[index];
manager.appState.textEditingDeltaHistory,
)[index];
},
itemCount: manager.appState.textEditingDeltaHistory.length,
separatorBuilder: (context, index) {

View File

@@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ^3.5.0
sdk: ^3.7.0-0
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions

View File

@@ -18,11 +18,17 @@ void main() {
// Elements on Style ToggleButton Toolbar.
expect(
find.widgetWithIcon(ToggleButtons, Icons.format_bold), findsOneWidget);
expect(find.widgetWithIcon(ToggleButtons, Icons.format_italic),
findsOneWidget);
expect(find.widgetWithIcon(ToggleButtons, Icons.format_underline),
findsOneWidget);
find.widgetWithIcon(ToggleButtons, Icons.format_bold),
findsOneWidget,
);
expect(
find.widgetWithIcon(ToggleButtons, Icons.format_italic),
findsOneWidget,
);
expect(
find.widgetWithIcon(ToggleButtons, Icons.format_underline),
findsOneWidget,
);
// Elements on the main screen
// Delta labels.
@@ -35,24 +41,34 @@ void main() {
// Selection delta is generated and delta history is visible.
await tester.tap(find.byType(BasicTextInputClient));
await tester.pumpAndSettle();
expect(find.widgetWithText(TextEditingDeltaView, "NonTextUpdate"),
findsOneWidget);
expect(
find.widgetWithText(TextEditingDeltaView, "NonTextUpdate"),
findsOneWidget,
);
// Find tooltips.
expect(find.byTooltip('The text that is being inserted or deleted'),
findsOneWidget);
expect(
find.byTooltip(
'The type of text input that is occurring. Check out the documentation for TextEditingDelta for more information.'),
findsOneWidget);
find.byTooltip('The text that is being inserted or deleted'),
findsOneWidget,
);
expect(
find.byTooltip(
'The offset in the text where the text input is occurring.'),
findsOneWidget);
find.byTooltip(
'The type of text input that is occurring. Check out the documentation for TextEditingDelta for more information.',
),
findsOneWidget,
);
expect(
find.byTooltip(
'The new text selection range after the text input has occurred.'),
findsOneWidget);
find.byTooltip(
'The offset in the text where the text input is occurring.',
),
findsOneWidget,
);
expect(
find.byTooltip(
'The new text selection range after the text input has occurred.',
),
findsOneWidget,
);
// About Dialog
expect(find.widgetWithIcon(IconButton, Icons.info_outline), findsOneWidget);