mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Beta (#1234)
This commit is contained in:
@@ -22,8 +22,10 @@ class BasicTextField extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BasicTextFieldState extends State<BasicTextField> {
|
||||
final GlobalKey<BasicTextInputClientState> textInputClientKey = GlobalKey<BasicTextInputClientState>();
|
||||
BasicTextInputClientState? get _textInputClient => textInputClientKey.currentState;
|
||||
final GlobalKey<BasicTextInputClientState> textInputClientKey =
|
||||
GlobalKey<BasicTextInputClientState>();
|
||||
BasicTextInputClientState? get _textInputClient =>
|
||||
textInputClientKey.currentState;
|
||||
RenderEditable get _renderEditable => _textInputClient!.renderEditable;
|
||||
|
||||
// For text selection gestures.
|
||||
@@ -42,8 +44,8 @@ class _BasicTextFieldState extends State<BasicTextField> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cause == SelectionChangedCause.longPress
|
||||
|| cause == SelectionChangedCause.scribble) {
|
||||
if (cause == SelectionChangedCause.longPress ||
|
||||
cause == SelectionChangedCause.scribble) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,7 +56,8 @@ class _BasicTextFieldState extends State<BasicTextField> {
|
||||
return false;
|
||||
}
|
||||
|
||||
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
|
||||
void _handleSelectionChanged(
|
||||
TextSelection selection, SelectionChangedCause? cause) {
|
||||
final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
|
||||
if (willShowSelectionHandles != _showSelectionHandles) {
|
||||
setState(() {
|
||||
@@ -128,16 +131,20 @@ class _BasicTextFieldState extends State<BasicTextField> {
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
_renderEditable.selectWordsInRange(
|
||||
from: longPressMoveUpdateDetails.globalPosition - longPressMoveUpdateDetails.offsetFromOrigin,
|
||||
from: longPressMoveUpdateDetails.globalPosition -
|
||||
longPressMoveUpdateDetails.offsetFromOrigin,
|
||||
to: longPressMoveUpdateDetails.globalPosition,
|
||||
cause: SelectionChangedCause.longPress,
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
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,
|
||||
@@ -160,4 +167,4 @@ class _BasicTextFieldState extends State<BasicTextField> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import 'toggle_button_state_manager.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);
|
||||
|
||||
/// A basic text input client. An implementation of [DeltaTextInputClient] meant to
|
||||
/// send/receive information from the framework to the platform's text input plugin
|
||||
@@ -39,11 +40,13 @@ class BasicTextInputClient extends StatefulWidget {
|
||||
}
|
||||
|
||||
class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
with TextSelectionDelegate implements DeltaTextInputClient {
|
||||
with TextSelectionDelegate
|
||||
implements DeltaTextInputClient {
|
||||
final GlobalKey _textKey = GlobalKey();
|
||||
late final ToggleButtonsStateManager toggleButtonStateManager;
|
||||
late final TextEditingDeltaHistoryManager textEditingDeltaHistoryManager;
|
||||
final ClipboardStatusNotifier? _clipboardStatus = kIsWeb ? null : ClipboardStatusNotifier();
|
||||
final ClipboardStatusNotifier? _clipboardStatus =
|
||||
kIsWeb ? null : ClipboardStatusNotifier();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -124,7 +127,7 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
}
|
||||
|
||||
@override
|
||||
void updateEditingValue(TextEditingValue value) { /* Not using */}
|
||||
void updateEditingValue(TextEditingValue value) {/* Not using */}
|
||||
|
||||
@override
|
||||
void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
|
||||
@@ -143,14 +146,18 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
return;
|
||||
}
|
||||
|
||||
final bool selectionChanged = _value.selection.start != value.selection.start || _value.selection.end != value.selection.end;
|
||||
textEditingDeltaHistoryManager.updateTextEditingDeltaHistoryOnInput(textEditingDeltas);
|
||||
final bool selectionChanged =
|
||||
_value.selection.start != value.selection.start ||
|
||||
_value.selection.end != value.selection.end;
|
||||
textEditingDeltaHistoryManager
|
||||
.updateTextEditingDeltaHistoryOnInput(textEditingDeltas);
|
||||
|
||||
_value = value;
|
||||
|
||||
if (widget.controller is ReplacementTextEditingController) {
|
||||
for (final TextEditingDelta delta in textEditingDeltas) {
|
||||
(widget.controller as ReplacementTextEditingController).syncReplacementRanges(delta);
|
||||
(widget.controller as ReplacementTextEditingController)
|
||||
.syncReplacementRanges(delta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +253,8 @@ 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);
|
||||
toggleButtonStateManager.updateToggleButtonsOnSelection(validSelection);
|
||||
}
|
||||
@@ -264,17 +272,20 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
);
|
||||
}
|
||||
|
||||
void _userUpdateTextEditingValueWithDelta(TextEditingDelta textEditingDelta, SelectionChangedCause cause) {
|
||||
void _userUpdateTextEditingValueWithDelta(
|
||||
TextEditingDelta textEditingDelta, SelectionChangedCause cause) {
|
||||
TextEditingValue value = _value;
|
||||
|
||||
value = textEditingDelta.apply(value);
|
||||
|
||||
if (widget.controller is ReplacementTextEditingController) {
|
||||
(widget.controller as ReplacementTextEditingController).syncReplacementRanges(textEditingDelta);
|
||||
(widget.controller as ReplacementTextEditingController)
|
||||
.syncReplacementRanges(textEditingDelta);
|
||||
}
|
||||
|
||||
if (value != _value) {
|
||||
textEditingDeltaHistoryManager.updateTextEditingDeltaHistoryOnInput([textEditingDelta]);
|
||||
textEditingDeltaHistoryManager
|
||||
.updateTextEditingDeltaHistoryOnInput([textEditingDelta]);
|
||||
}
|
||||
|
||||
userUpdateTextEditingValue(value, cause);
|
||||
@@ -292,16 +303,18 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
DeleteCharacterIntent: CallbackAction<DeleteCharacterIntent>(
|
||||
onInvoke: (intent) => _delete(),
|
||||
),
|
||||
ExtendSelectionByCharacterIntent: CallbackAction<ExtendSelectionByCharacterIntent>(
|
||||
onInvoke: (intent) => _extendSelection(intent.forward, intent.collapseSelection),
|
||||
ExtendSelectionByCharacterIntent:
|
||||
CallbackAction<ExtendSelectionByCharacterIntent>(
|
||||
onInvoke: (intent) =>
|
||||
_extendSelection(intent.forward, intent.collapseSelection),
|
||||
),
|
||||
SelectAllTextIntent : CallbackAction<SelectAllTextIntent>(
|
||||
SelectAllTextIntent: CallbackAction<SelectAllTextIntent>(
|
||||
onInvoke: (intent) => selectAll(intent.cause),
|
||||
),
|
||||
CopySelectionTextIntent : CallbackAction<CopySelectionTextIntent>(
|
||||
CopySelectionTextIntent: CallbackAction<CopySelectionTextIntent>(
|
||||
onInvoke: (intent) => copySelection(intent.cause),
|
||||
),
|
||||
PasteTextIntent : CallbackAction<PasteTextIntent>(
|
||||
PasteTextIntent: CallbackAction<PasteTextIntent>(
|
||||
onInvoke: (intent) => pasteText(intent.cause),
|
||||
),
|
||||
};
|
||||
@@ -311,7 +324,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
|
||||
late final TextRange deletedRange;
|
||||
late final TextRange newComposing;
|
||||
final int deletedLength = _value.text.substring(0, _selection.baseOffset).characters.last.length;
|
||||
final int deletedLength =
|
||||
_value.text.substring(0, _selection.baseOffset).characters.last.length;
|
||||
|
||||
if (_selection.isCollapsed) {
|
||||
if (_selection.baseOffset == 0) return;
|
||||
@@ -323,7 +337,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
deletedRange = _selection;
|
||||
}
|
||||
|
||||
final bool isComposing = _selection.isCollapsed && _value.isComposingRangeValid;
|
||||
final bool isComposing =
|
||||
_selection.isCollapsed && _value.isComposingRangeValid;
|
||||
|
||||
if (isComposing) {
|
||||
newComposing = TextRange.collapsed(deletedRange.start);
|
||||
@@ -347,15 +362,26 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
|
||||
if (collapseSelection) {
|
||||
if (!_selection.isCollapsed) {
|
||||
final int firstOffset = _selection.isNormalized ? _selection.start : _selection.end;
|
||||
final int lastOffset = _selection.isNormalized ? _selection.end : _selection.start;
|
||||
selection = TextSelection.collapsed(offset: forward ? lastOffset : firstOffset);
|
||||
final int firstOffset =
|
||||
_selection.isNormalized ? _selection.start : _selection.end;
|
||||
final int lastOffset =
|
||||
_selection.isNormalized ? _selection.end : _selection.start;
|
||||
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;
|
||||
? _value.text
|
||||
.substring(_selection.baseOffset)
|
||||
.characters
|
||||
.first
|
||||
.length
|
||||
: -_value.text
|
||||
.substring(0, _selection.baseOffset)
|
||||
.characters
|
||||
.last
|
||||
.length;
|
||||
selection = TextSelection.collapsed(
|
||||
offset: _selection.baseOffset + adjustment,
|
||||
);
|
||||
@@ -365,7 +391,11 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
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;
|
||||
: -_value.text
|
||||
.substring(0, _selection.baseOffset)
|
||||
.characters
|
||||
.last
|
||||
.length;
|
||||
selection = TextSelection(
|
||||
baseOffset: _selection.baseOffset,
|
||||
extentOffset: _selection.extentOffset + adjustment,
|
||||
@@ -382,7 +412,6 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// For updates to text editing value.
|
||||
void _didChangeTextEditingValue() {
|
||||
_updateRemoteTextEditingValueIfNeeded();
|
||||
@@ -448,12 +477,13 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
_userUpdateTextEditingValueWithDelta(
|
||||
TextEditingDeltaNonTextUpdate(
|
||||
oldText: textEditingValue.text,
|
||||
selection: TextSelection.collapsed(offset: textEditingValue.selection.end),
|
||||
composing: TextRange.empty,
|
||||
),
|
||||
cause,
|
||||
TextEditingDeltaNonTextUpdate(
|
||||
oldText: textEditingValue.text,
|
||||
selection: TextSelection.collapsed(
|
||||
offset: textEditingValue.selection.end),
|
||||
composing: TextRange.empty,
|
||||
),
|
||||
cause,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -469,14 +499,15 @@ 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,
|
||||
replacementText: '',
|
||||
replacedRange: cutRange,
|
||||
selection: TextSelection.collapsed(offset: lastSelectionIndex),
|
||||
composing: TextRange.empty,
|
||||
oldText: textEditingValue.text,
|
||||
replacementText: '',
|
||||
replacedRange: cutRange,
|
||||
selection: TextSelection.collapsed(offset: lastSelectionIndex),
|
||||
composing: TextRange.empty,
|
||||
),
|
||||
cause,
|
||||
);
|
||||
@@ -505,15 +536,16 @@ 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);
|
||||
final int lastSelectionIndex = math.max(
|
||||
pasteRange.baseOffset, pasteRange.baseOffset + data.text!.length);
|
||||
|
||||
_userUpdateTextEditingValueWithDelta(
|
||||
TextEditingDeltaReplacement(
|
||||
oldText: textEditingValue.text,
|
||||
replacementText: data.text!,
|
||||
replacedRange: pasteRange,
|
||||
selection: TextSelection.collapsed(offset: lastSelectionIndex),
|
||||
composing: TextRange.empty,
|
||||
oldText: textEditingValue.text,
|
||||
replacementText: data.text!,
|
||||
replacedRange: pasteRange,
|
||||
selection: TextSelection.collapsed(offset: lastSelectionIndex),
|
||||
composing: TextRange.empty,
|
||||
),
|
||||
cause,
|
||||
);
|
||||
@@ -523,12 +555,13 @@ 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,
|
||||
selection: newSelection,
|
||||
composing: TextRange.empty
|
||||
oldText: textEditingValue.text,
|
||||
selection: newSelection,
|
||||
composing: TextRange.empty,
|
||||
),
|
||||
cause,
|
||||
);
|
||||
@@ -538,31 +571,38 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
TextEditingValue get textEditingValue => _value;
|
||||
|
||||
@override
|
||||
void userUpdateTextEditingValue(TextEditingValue value, SelectionChangedCause cause) {
|
||||
void userUpdateTextEditingValue(
|
||||
TextEditingValue value, SelectionChangedCause cause) {
|
||||
if (value == _value) return;
|
||||
|
||||
final bool selectionChanged = _value.selection != value.selection;
|
||||
|
||||
if (cause == SelectionChangedCause.drag || cause == SelectionChangedCause.longPress || cause == SelectionChangedCause.tap) {
|
||||
if (cause == SelectionChangedCause.drag ||
|
||||
cause == SelectionChangedCause.longPress ||
|
||||
cause == SelectionChangedCause.tap) {
|
||||
// Here the change is coming from gestures which call on RenderEditable to change the selection.
|
||||
// Create a TextEditingDeltaNonTextUpdate so we can keep track of the delta history. RenderEditable
|
||||
// does not report a delta on selection change.
|
||||
final bool textChanged = _value.text != value.text;
|
||||
if (selectionChanged && !textChanged) {
|
||||
final TextEditingDeltaNonTextUpdate selectionUpdate = TextEditingDeltaNonTextUpdate(
|
||||
oldText: value.text,
|
||||
selection: value.selection,
|
||||
composing: value.composing,
|
||||
final TextEditingDeltaNonTextUpdate selectionUpdate =
|
||||
TextEditingDeltaNonTextUpdate(
|
||||
oldText: value.text,
|
||||
selection: value.selection,
|
||||
composing: value.composing,
|
||||
);
|
||||
if (widget.controller is ReplacementTextEditingController) {
|
||||
(widget.controller as ReplacementTextEditingController).syncReplacementRanges(selectionUpdate);
|
||||
(widget.controller as ReplacementTextEditingController)
|
||||
.syncReplacementRanges(selectionUpdate);
|
||||
}
|
||||
textEditingDeltaHistoryManager.updateTextEditingDeltaHistoryOnInput([selectionUpdate]);
|
||||
textEditingDeltaHistoryManager
|
||||
.updateTextEditingDeltaHistoryOnInput([selectionUpdate]);
|
||||
}
|
||||
}
|
||||
|
||||
final bool selectionRangeChanged = _value.selection.start != value.selection.start
|
||||
|| _value.selection.end != value.selection.end;
|
||||
final bool selectionRangeChanged =
|
||||
_value.selection.start != value.selection.start ||
|
||||
_value.selection.end != value.selection.end;
|
||||
|
||||
_value = value;
|
||||
|
||||
@@ -570,7 +610,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
_handleSelectionChanged(_value.selection, cause);
|
||||
|
||||
if (selectionRangeChanged) {
|
||||
toggleButtonStateManager.updateToggleButtonsOnSelection(_value.selection);
|
||||
toggleButtonStateManager
|
||||
.updateToggleButtonsOnSelection(_value.selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -581,9 +622,11 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
final LayerLink _toolbarLayerLink = LayerLink();
|
||||
|
||||
TextSelectionOverlay? _selectionOverlay;
|
||||
RenderEditable get renderEditable => _textKey.currentContext!.findRenderObject()! as RenderEditable;
|
||||
RenderEditable get renderEditable =>
|
||||
_textKey.currentContext!.findRenderObject()! as RenderEditable;
|
||||
|
||||
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
|
||||
void _handleSelectionChanged(
|
||||
TextSelection selection, SelectionChangedCause? cause) {
|
||||
// We return early if the selection is not valid. This can happen when the
|
||||
// text of [EditableText] is updated at the same time as the selection is
|
||||
// changed by a gesture event.
|
||||
@@ -647,20 +690,27 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
library: 'widgets',
|
||||
context: ErrorDescription('while calling onSelectionChanged for $cause'),
|
||||
context:
|
||||
ErrorDescription('while calling onSelectionChanged for $cause'),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
static final Map<ShortcutActivator, Intent> _defaultWebShortcuts = <ShortcutActivator, Intent>{
|
||||
static final Map<ShortcutActivator, Intent> _defaultWebShortcuts =
|
||||
<ShortcutActivator, Intent>{
|
||||
// Activation
|
||||
const SingleActivator(LogicalKeyboardKey.space): DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.space):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
|
||||
// Scrolling
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp): DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft): DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight): DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
};
|
||||
|
||||
@override
|
||||
@@ -684,7 +734,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
cursorColor: Colors.blue,
|
||||
backgroundCursorColor: Colors.grey[100],
|
||||
showCursor: ValueNotifier<bool>(_hasFocus),
|
||||
forceLine: true, // Whether text field will take full line regardless of width.
|
||||
forceLine:
|
||||
true, // Whether text field will take full line regardless of width.
|
||||
readOnly: false, // editable text-field.
|
||||
hasFocus: _hasFocus,
|
||||
maxLines: null, // multi-line text-field.
|
||||
@@ -699,7 +750,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
textHeightBehavior: DefaultTextHeightBehavior.of(context),
|
||||
textWidthBasis: TextWidthBasis.parent,
|
||||
obscuringCharacter: '•',
|
||||
obscureText: false, // This is a non-private text field that does not require obfuscation.
|
||||
obscureText:
|
||||
false, // This is a non-private text field that does not require obfuscation.
|
||||
offset: position,
|
||||
onCaretChanged: null,
|
||||
rendererIgnoresPointer: true,
|
||||
@@ -708,7 +760,8 @@ class BasicTextInputClientState extends State<BasicTextInputClient>
|
||||
cursorRadius: const Radius.circular(2.0),
|
||||
cursorOffset: Offset.zero,
|
||||
paintCursorAboveText: false,
|
||||
enableInteractiveSelection: true, // make true to enable selection on mobile.
|
||||
enableInteractiveSelection:
|
||||
true, // make true to enable selection on mobile.
|
||||
textSelectionDelegate: this,
|
||||
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
|
||||
promptRectRange: null,
|
||||
@@ -900,4 +953,4 @@ class _Editable extends MultiChildRenderObjectWidget {
|
||||
..clipBehavior = clipBehavior
|
||||
..setPromptRectRange(promptRectRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +38,15 @@ class MyHomePage extends StatefulWidget {
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final ReplacementTextEditingController _replacementTextEditingController =
|
||||
ReplacementTextEditingController(
|
||||
ReplacementTextEditingController(
|
||||
text: 'The quick brown fox jumps over the lazy dog.',
|
||||
);
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
final List<bool> _isSelected = [false, false, false];
|
||||
final List<TextEditingDelta> _textEditingDeltaHistory = [];
|
||||
|
||||
void _updateTextEditingDeltaHistory(List<TextEditingDelta> textEditingDeltas) {
|
||||
void _updateTextEditingDeltaHistory(
|
||||
List<TextEditingDelta> textEditingDeltas) {
|
||||
for (final TextEditingDelta delta in textEditingDeltas) {
|
||||
_textEditingDeltaHistory.add(delta);
|
||||
}
|
||||
@@ -53,7 +54,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
List<Widget> _buildTextEditingDeltaHistoryViews(List<TextEditingDelta> textEditingDeltas) {
|
||||
List<Widget> _buildTextEditingDeltaHistoryViews(
|
||||
List<TextEditingDelta> textEditingDeltas) {
|
||||
List<Widget> textEditingDeltaViews = [];
|
||||
|
||||
for (final TextEditingDelta delta in textEditingDeltas) {
|
||||
@@ -61,7 +63,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
|
||||
if (delta is TextEditingDeltaInsertion) {
|
||||
deltaView = TextEditingDeltaView(
|
||||
deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaType:
|
||||
delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaText: delta.textInserted,
|
||||
deltaRange: TextRange.collapsed(delta.insertionOffset),
|
||||
newSelection: delta.selection,
|
||||
@@ -69,7 +72,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
);
|
||||
} else if (delta is TextEditingDeltaDeletion) {
|
||||
deltaView = TextEditingDeltaView(
|
||||
deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaType:
|
||||
delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaText: delta.textDeleted,
|
||||
deltaRange: delta.deletedRange,
|
||||
newSelection: delta.selection,
|
||||
@@ -77,7 +81,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
);
|
||||
} else if (delta is TextEditingDeltaReplacement) {
|
||||
deltaView = TextEditingDeltaView(
|
||||
deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaType:
|
||||
delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaText: delta.replacementText,
|
||||
deltaRange: delta.replacedRange,
|
||||
newSelection: delta.selection,
|
||||
@@ -85,7 +90,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
);
|
||||
} else if (delta is TextEditingDeltaNonTextUpdate) {
|
||||
deltaView = TextEditingDeltaView(
|
||||
deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaType:
|
||||
delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
|
||||
deltaText: '',
|
||||
deltaRange: TextRange.empty,
|
||||
newSelection: delta.selection,
|
||||
@@ -111,7 +117,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
// 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 = _replacementTextEditingController.getReplacementsAtSelection(selection);
|
||||
final List<TextStyle> replacementStyles =
|
||||
_replacementTextEditingController.getReplacementsAtSelection(selection);
|
||||
final List<bool> hasChanged = [false, false, false];
|
||||
|
||||
if (replacementStyles.isEmpty) {
|
||||
@@ -157,9 +164,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
|
||||
void _updateToggleButtonsStateOnButtonPressed(int index) {
|
||||
Map<int, TextStyle> attributeMap = const <int, TextStyle>{
|
||||
0 : TextStyle(fontWeight: FontWeight.bold),
|
||||
1 : TextStyle(fontStyle: FontStyle.italic),
|
||||
2 : TextStyle(decoration: TextDecoration.underline),
|
||||
0: TextStyle(fontWeight: FontWeight.bold),
|
||||
1: TextStyle(fontStyle: FontStyle.italic),
|
||||
2: TextStyle(decoration: TextDecoration.underline),
|
||||
};
|
||||
|
||||
final TextRange replacementRange = TextRange(
|
||||
@@ -172,14 +179,15 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
_replacementTextEditingController.applyReplacement(
|
||||
TextEditingInlineSpanReplacement(
|
||||
replacementRange,
|
||||
(string, range) => TextSpan(text: string, style: attributeMap[index]),
|
||||
(string, range) => TextSpan(text: string, style: attributeMap[index]),
|
||||
true,
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
} else {
|
||||
_replacementTextEditingController.disableExpand(attributeMap[index]!);
|
||||
_replacementTextEditingController.removeReplacementsAtRange(replacementRange, attributeMap[index]);
|
||||
_replacementTextEditingController.removeReplacementsAtRange(
|
||||
replacementRange, attributeMap[index]);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
@@ -188,8 +196,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
return Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.underline,
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -200,35 +208,38 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Tooltip(
|
||||
message: 'The type of text input that is occurring.'
|
||||
' Check out the documentation for TextEditingDelta for more information.',
|
||||
child: _buildTextEditingDeltaViewHeading('Delta Type'),
|
||||
),
|
||||
child: Tooltip(
|
||||
message: 'The type of text input that is occurring.'
|
||||
' Check out the documentation for TextEditingDelta for more information.',
|
||||
child: _buildTextEditingDeltaViewHeading('Delta Type'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Tooltip(
|
||||
message: 'The text that is being inserted or deleted',
|
||||
child: _buildTextEditingDeltaViewHeading('Delta Text'),
|
||||
),
|
||||
child: Tooltip(
|
||||
message: 'The text that is being inserted or deleted',
|
||||
child: _buildTextEditingDeltaViewHeading('Delta Text'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Tooltip(
|
||||
message: 'The offset in the text where the text input is occurring.',
|
||||
child: _buildTextEditingDeltaViewHeading('Delta Offset'),
|
||||
),
|
||||
child: Tooltip(
|
||||
message:
|
||||
'The offset in the text where the text input is occurring.',
|
||||
child: _buildTextEditingDeltaViewHeading('Delta Offset'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Tooltip(
|
||||
message: 'The new text selection range after the text input has occurred.',
|
||||
child: _buildTextEditingDeltaViewHeading('New Selection'),
|
||||
),
|
||||
child: Tooltip(
|
||||
message:
|
||||
'The new text selection range after the text input has occurred.',
|
||||
child: _buildTextEditingDeltaViewHeading('New Selection'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Tooltip(
|
||||
message: 'The new composing range after the text input has occurred.',
|
||||
child: _buildTextEditingDeltaViewHeading('New Composing'),
|
||||
),
|
||||
child: Tooltip(
|
||||
message:
|
||||
'The new composing range after the text input has occurred.',
|
||||
child: _buildTextEditingDeltaViewHeading('New Composing'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -247,8 +258,7 @@ 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(
|
||||
builder: (context) => const AlertDialog(
|
||||
title: Center(child: Text('About')),
|
||||
content: Text(aboutContent),
|
||||
),
|
||||
@@ -264,7 +274,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).restorablePush(_aboutDialogBuilder);
|
||||
},
|
||||
},
|
||||
icon: const Icon(Icons.info_outline),
|
||||
),
|
||||
],
|
||||
@@ -274,8 +284,10 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
child: Center(
|
||||
child: ToggleButtonsStateManager(
|
||||
isToggleButtonsSelected: _isSelected,
|
||||
updateToggleButtonsStateOnButtonPressed: _updateToggleButtonsStateOnButtonPressed,
|
||||
updateToggleButtonStateOnSelectionChanged: _updateToggleButtonsStateOnSelectionChanged,
|
||||
updateToggleButtonsStateOnButtonPressed:
|
||||
_updateToggleButtonsStateOnButtonPressed,
|
||||
updateToggleButtonStateOnSelectionChanged:
|
||||
_updateToggleButtonsStateOnSelectionChanged,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
@@ -285,24 +297,27 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
children: [
|
||||
ToggleButtonsStateManager(
|
||||
isToggleButtonsSelected: _isSelected,
|
||||
updateToggleButtonsStateOnButtonPressed: _updateToggleButtonsStateOnButtonPressed,
|
||||
updateToggleButtonStateOnSelectionChanged: _updateToggleButtonsStateOnSelectionChanged,
|
||||
child: Builder(
|
||||
builder: (innerContext) {
|
||||
final ToggleButtonsStateManager manager = ToggleButtonsStateManager.of(innerContext);
|
||||
updateToggleButtonsStateOnButtonPressed:
|
||||
_updateToggleButtonsStateOnButtonPressed,
|
||||
updateToggleButtonStateOnSelectionChanged:
|
||||
_updateToggleButtonsStateOnSelectionChanged,
|
||||
child: Builder(builder: (innerContext) {
|
||||
final ToggleButtonsStateManager manager =
|
||||
ToggleButtonsStateManager.of(innerContext);
|
||||
|
||||
return ToggleButtons(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
|
||||
isSelected: manager.toggleButtonsState,
|
||||
onPressed: (index) => manager.updateToggleButtonsOnButtonPressed(index),
|
||||
children: const [
|
||||
Icon(Icons.format_bold),
|
||||
Icon(Icons.format_italic),
|
||||
Icon(Icons.format_underline),
|
||||
],
|
||||
);
|
||||
}
|
||||
),
|
||||
return ToggleButtons(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(4.0)),
|
||||
isSelected: manager.toggleButtonsState,
|
||||
onPressed: (index) => manager
|
||||
.updateToggleButtonsOnButtonPressed(index),
|
||||
children: const [
|
||||
Icon(Icons.format_bold),
|
||||
Icon(Icons.format_italic),
|
||||
Icon(Icons.format_underline),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -312,14 +327,17 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 35.0),
|
||||
child: ToggleButtonsStateManager(
|
||||
isToggleButtonsSelected: _isSelected,
|
||||
updateToggleButtonsStateOnButtonPressed: _updateToggleButtonsStateOnButtonPressed,
|
||||
updateToggleButtonStateOnSelectionChanged: _updateToggleButtonsStateOnSelectionChanged,
|
||||
updateToggleButtonsStateOnButtonPressed:
|
||||
_updateToggleButtonsStateOnButtonPressed,
|
||||
updateToggleButtonStateOnSelectionChanged:
|
||||
_updateToggleButtonsStateOnSelectionChanged,
|
||||
child: TextEditingDeltaHistoryManager(
|
||||
history: _textEditingDeltaHistory,
|
||||
updateHistoryOnInput: _updateTextEditingDeltaHistory,
|
||||
child: BasicTextField(
|
||||
controller: _replacementTextEditingController,
|
||||
style: const TextStyle(fontSize: 18.0, color: Colors.black),
|
||||
style: const TextStyle(
|
||||
fontSize: 18.0, color: Colors.black),
|
||||
focusNode: _focusNode,
|
||||
),
|
||||
),
|
||||
@@ -336,18 +354,23 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
updateHistoryOnInput: _updateTextEditingDeltaHistory,
|
||||
child: Builder(
|
||||
builder: (innerContext) {
|
||||
final TextEditingDeltaHistoryManager manager = TextEditingDeltaHistoryManager.of(innerContext);
|
||||
final TextEditingDeltaHistoryManager manager =
|
||||
TextEditingDeltaHistoryManager.of(
|
||||
innerContext);
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 35.0),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 35.0),
|
||||
itemBuilder: (context, index) {
|
||||
return _buildTextEditingDeltaHistoryViews(manager.textEditingDeltaHistory)[index];
|
||||
return _buildTextEditingDeltaHistoryViews(
|
||||
manager.textEditingDeltaHistory)[index];
|
||||
},
|
||||
itemCount: manager.textEditingDeltaHistory.length,
|
||||
itemCount:
|
||||
manager.textEditingDeltaHistory.length,
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 2.0);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -371,7 +394,7 @@ class TextEditingDeltaView extends StatelessWidget {
|
||||
required this.deltaText,
|
||||
required this.deltaRange,
|
||||
required this.newSelection,
|
||||
required this.newComposing
|
||||
required this.newComposing,
|
||||
}) : super(key: key);
|
||||
|
||||
final String deltaType;
|
||||
|
||||
@@ -58,39 +58,44 @@ class TextEditingInlineSpanReplacement {
|
||||
final TextRange deletedRange = delta.deletedRange;
|
||||
final int deletedLength = delta.textDeleted.length;
|
||||
|
||||
if (range.start >= deletedRange.start
|
||||
&& (range.start < deletedRange.end && range.end > deletedRange.end)) {
|
||||
if (range.start >= deletedRange.start &&
|
||||
(range.start < deletedRange.end && range.end > deletedRange.end)) {
|
||||
return copy(
|
||||
range: TextRange(
|
||||
start: deletedRange.end - deletedLength,
|
||||
end: range.end - deletedLength,
|
||||
),
|
||||
);
|
||||
} else if ((range.start < deletedRange.start && range.end > deletedRange.start)
|
||||
&& range.end <= deletedRange.end) {
|
||||
} else if ((range.start < deletedRange.start &&
|
||||
range.end > deletedRange.start) &&
|
||||
range.end <= deletedRange.end) {
|
||||
return copy(
|
||||
range: TextRange(
|
||||
start: range.start,
|
||||
end: deletedRange.start,
|
||||
),
|
||||
);
|
||||
} else if (range.start < deletedRange.start && range.end > deletedRange.end) {
|
||||
} else if (range.start < deletedRange.start &&
|
||||
range.end > deletedRange.end) {
|
||||
return copy(
|
||||
range: TextRange(
|
||||
start: range.start,
|
||||
end: range.end - deletedLength,
|
||||
),
|
||||
);
|
||||
} else if (range.start >= deletedRange.start && range.end <= deletedRange.end) {
|
||||
} else if (range.start >= deletedRange.start &&
|
||||
range.end <= deletedRange.end) {
|
||||
return null;
|
||||
} else if (range.start > deletedRange.start && range.start >= deletedRange.end) {
|
||||
} else if (range.start > deletedRange.start &&
|
||||
range.start >= deletedRange.end) {
|
||||
return copy(
|
||||
range: TextRange(
|
||||
start: range.start - deletedLength,
|
||||
end: range.end - deletedLength,
|
||||
),
|
||||
);
|
||||
} else if (range.end <= deletedRange.start && range.end < deletedRange.end) {
|
||||
} else if (range.end <= deletedRange.start &&
|
||||
range.end < deletedRange.end) {
|
||||
return copy(
|
||||
range: TextRange(
|
||||
start: range.start,
|
||||
@@ -102,7 +107,8 @@ class TextEditingInlineSpanReplacement {
|
||||
return null;
|
||||
}
|
||||
|
||||
TextEditingInlineSpanReplacement? onInsertion(TextEditingDeltaInsertion delta) {
|
||||
TextEditingInlineSpanReplacement? onInsertion(
|
||||
TextEditingDeltaInsertion delta) {
|
||||
final int insertionOffset = delta.insertionOffset;
|
||||
final int insertedLength = delta.textInserted.length;
|
||||
|
||||
@@ -122,7 +128,8 @@ class TextEditingInlineSpanReplacement {
|
||||
),
|
||||
);
|
||||
}
|
||||
} if (range.start < insertionOffset && range.end < insertionOffset) {
|
||||
}
|
||||
if (range.start < insertionOffset && range.end < insertionOffset) {
|
||||
return copy(
|
||||
range: TextRange(
|
||||
start: range.start,
|
||||
@@ -148,20 +155,21 @@ class TextEditingInlineSpanReplacement {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<TextEditingInlineSpanReplacement>? onReplacement(TextEditingDeltaReplacement delta) {
|
||||
List<TextEditingInlineSpanReplacement>? onReplacement(
|
||||
TextEditingDeltaReplacement delta) {
|
||||
final TextRange replacedRange = delta.replacedRange;
|
||||
final bool replacementShortenedText = delta.replacementText.length <
|
||||
delta.textReplaced.length;
|
||||
final bool replacementLengthenedText = 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
|
||||
final bool replacementShortenedText =
|
||||
delta.replacementText.length < delta.textReplaced.length;
|
||||
final bool replacementLengthenedText =
|
||||
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;
|
||||
|
||||
if (range.start >= replacedRange.start
|
||||
&& (range.start < replacedRange.end && range.end > replacedRange.end)) {
|
||||
if (range.start >= replacedRange.start &&
|
||||
(range.start < replacedRange.end && range.end > replacedRange.end)) {
|
||||
if (replacementShortenedText) {
|
||||
return [
|
||||
copy(
|
||||
@@ -190,8 +198,9 @@ class TextEditingInlineSpanReplacement {
|
||||
),
|
||||
];
|
||||
}
|
||||
} else if ((range.start < replacedRange.start && range.end > replacedRange.start)
|
||||
&& range.end <= replacedRange.end) {
|
||||
} else if ((range.start < replacedRange.start &&
|
||||
range.end > replacedRange.start) &&
|
||||
range.end <= replacedRange.end) {
|
||||
return [
|
||||
copy(
|
||||
range: TextRange(
|
||||
@@ -200,7 +209,8 @@ class TextEditingInlineSpanReplacement {
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (range.start < replacedRange.start && range.end > replacedRange.end) {
|
||||
} else if (range.start < replacedRange.start &&
|
||||
range.end > replacedRange.end) {
|
||||
if (replacementShortenedText) {
|
||||
return [
|
||||
copy(
|
||||
@@ -247,10 +257,12 @@ class TextEditingInlineSpanReplacement {
|
||||
),
|
||||
];
|
||||
}
|
||||
} else if (range.start >= replacedRange.start && range.end <= replacedRange.end) {
|
||||
} else if (range.start >= replacedRange.start &&
|
||||
range.end <= replacedRange.end) {
|
||||
// remove attribute.
|
||||
return null;
|
||||
} else if (range.start > replacedRange.start && range.start >= replacedRange.end) {
|
||||
} else if (range.start > replacedRange.start &&
|
||||
range.start >= replacedRange.end) {
|
||||
if (replacementShortenedText) {
|
||||
return [
|
||||
copy(
|
||||
@@ -272,7 +284,8 @@ class TextEditingInlineSpanReplacement {
|
||||
} else if (replacementEqualLength) {
|
||||
return [this];
|
||||
}
|
||||
} else if (range.end <= replacedRange.start && range.end < replacedRange.end) {
|
||||
} else if (range.end <= replacedRange.start &&
|
||||
range.end < replacedRange.end) {
|
||||
return [
|
||||
copy(
|
||||
range: TextRange(
|
||||
@@ -286,9 +299,11 @@ class TextEditingInlineSpanReplacement {
|
||||
return null;
|
||||
}
|
||||
|
||||
TextEditingInlineSpanReplacement? onNonTextUpdate(TextEditingDeltaNonTextUpdate delta) {
|
||||
TextEditingInlineSpanReplacement? onNonTextUpdate(
|
||||
TextEditingDeltaNonTextUpdate delta) {
|
||||
if (range.isCollapsed) {
|
||||
if (range.start != delta.selection.start && range.end != delta.selection.end) {
|
||||
if (range.start != delta.selection.start &&
|
||||
range.end != delta.selection.end) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -296,8 +311,8 @@ class TextEditingInlineSpanReplacement {
|
||||
}
|
||||
|
||||
List<TextEditingInlineSpanReplacement>? removeRange(TextRange removalRange) {
|
||||
if (range.start >= removalRange.start
|
||||
&& (range.start < removalRange.end && range.end > removalRange.end)) {
|
||||
if (range.start >= removalRange.start &&
|
||||
(range.start < removalRange.end && range.end > removalRange.end)) {
|
||||
return [
|
||||
copy(
|
||||
range: TextRange(
|
||||
@@ -306,8 +321,9 @@ class TextEditingInlineSpanReplacement {
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if ((range.start < removalRange.start && range.end > removalRange.start)
|
||||
&& range.end <= removalRange.end) {
|
||||
} else if ((range.start < removalRange.start &&
|
||||
range.end > removalRange.start) &&
|
||||
range.end <= removalRange.end) {
|
||||
return [
|
||||
copy(
|
||||
range: TextRange(
|
||||
@@ -316,7 +332,8 @@ class TextEditingInlineSpanReplacement {
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (range.start < removalRange.start && range.end > removalRange.end) {
|
||||
} else if (range.start < removalRange.start &&
|
||||
range.end > removalRange.end) {
|
||||
return [
|
||||
copy(
|
||||
range: TextRange(
|
||||
@@ -332,11 +349,14 @@ class TextEditingInlineSpanReplacement {
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (range.start >= removalRange.start && range.end <= removalRange.end) {
|
||||
} else if (range.start >= removalRange.start &&
|
||||
range.end <= removalRange.end) {
|
||||
return null;
|
||||
} else if (range.start > removalRange.start && range.start >= removalRange.end) {
|
||||
} else if (range.start > removalRange.start &&
|
||||
range.start >= removalRange.end) {
|
||||
return [this];
|
||||
} else if (range.end <= removalRange.start && range.end < removalRange.end) {
|
||||
} else if (range.end <= removalRange.start &&
|
||||
range.end < removalRange.end) {
|
||||
return [this];
|
||||
} else if (removalRange.isCollapsed && range.end == removalRange.start) {
|
||||
return [this];
|
||||
@@ -348,7 +368,8 @@ class TextEditingInlineSpanReplacement {
|
||||
/// Creates a new replacement with all properties copied except for range, which
|
||||
/// is updated to the specified value.
|
||||
TextEditingInlineSpanReplacement copy({TextRange? range, bool? expand}) {
|
||||
return TextEditingInlineSpanReplacement(range ?? this.range, generator, expand ?? this.expand);
|
||||
return TextEditingInlineSpanReplacement(
|
||||
range ?? this.range, generator, expand ?? this.expand);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -371,7 +392,7 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
String? text,
|
||||
List<TextEditingInlineSpanReplacement>? replacements,
|
||||
this.composingRegionReplaceable = true,
|
||||
}) : replacements = replacements ?? [],
|
||||
}) : replacements = replacements ?? [],
|
||||
super(text: text);
|
||||
|
||||
/// Creates a controller for an editable text field from an initial [TextEditingValue].
|
||||
@@ -379,7 +400,7 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
/// This constructor treats a null [value] argument as if it were [TextEditingValue.empty].
|
||||
ReplacementTextEditingController.fromValue(TextEditingValue? value,
|
||||
{List<TextEditingInlineSpanReplacement>? replacements,
|
||||
this.composingRegionReplaceable = true})
|
||||
this.composingRegionReplaceable = true})
|
||||
: super.fromValue(value);
|
||||
|
||||
/// The [TextEditingInlineSpanReplacement]s that are evaluated on the editing value.
|
||||
@@ -494,7 +515,8 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
}
|
||||
}
|
||||
|
||||
for (final TextEditingInlineSpanReplacement replacementToRemove in toRemove) {
|
||||
for (final TextEditingInlineSpanReplacement replacementToRemove
|
||||
in toRemove) {
|
||||
replacements!.remove(replacementToRemove);
|
||||
}
|
||||
|
||||
@@ -507,17 +529,19 @@ 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 = <TextRange, InlineSpan>{};
|
||||
final Map<TextRange, InlineSpan> rangeSpanMapping =
|
||||
<TextRange, InlineSpan>{};
|
||||
|
||||
// Iterate through TextEditingInlineSpanReplacements, handling overlapping
|
||||
// replacements and mapping them towards a generated InlineSpan.
|
||||
if (replacements != null) {
|
||||
for (final TextEditingInlineSpanReplacement replacement in replacements!) {
|
||||
for (final TextEditingInlineSpanReplacement replacement
|
||||
in replacements!) {
|
||||
_addToMappingWithOverlaps(
|
||||
replacement.generator,
|
||||
TextRange(start: replacement.range.start, end: replacement.range.end),
|
||||
@@ -532,9 +556,9 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
// be thrown and this EditableText will be built with a broken subtree.
|
||||
//
|
||||
// Add composing region as a replacement to a TextSpan with underline.
|
||||
if (composingRegionReplaceable
|
||||
&& value.isComposingRangeValid
|
||||
&& withComposing) {
|
||||
if (composingRegionReplaceable &&
|
||||
value.isComposingRangeValid &&
|
||||
withComposing) {
|
||||
_addToMappingWithOverlaps((value, range) {
|
||||
final TextStyle composingStyle = style != null
|
||||
? style.merge(const TextStyle(decoration: TextDecoration.underline))
|
||||
@@ -556,7 +580,8 @@ 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;
|
||||
@@ -583,8 +608,8 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
bool overlap = false;
|
||||
List<TextRange> overlapRanges = <TextRange>[];
|
||||
for (final TextRange range in rangeSpanMapping.keys) {
|
||||
if (math.max(matchedRange.start, range.start)
|
||||
<= math.min(matchedRange.end, range.end)) {
|
||||
if (math.max(matchedRange.start, range.start) <=
|
||||
math.min(matchedRange.end, range.end)) {
|
||||
overlap = true;
|
||||
overlapRanges.add(range);
|
||||
}
|
||||
@@ -593,10 +618,18 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
final List<List<dynamic>> overlappingTriples = <List<dynamic>>[];
|
||||
|
||||
if (overlap) {
|
||||
overlappingTriples.add(<dynamic>[matchedRange.start, matchedRange.end, generator(matchedRange.textInside(text), matchedRange).style]);
|
||||
overlappingTriples.add(<dynamic>[
|
||||
matchedRange.start,
|
||||
matchedRange.end,
|
||||
generator(matchedRange.textInside(text), matchedRange).style
|
||||
]);
|
||||
|
||||
for (final TextRange overlappingRange in overlapRanges) {
|
||||
overlappingTriples.add(<dynamic>[overlappingRange.start, overlappingRange.end, rangeSpanMapping[overlappingRange]!.style]);
|
||||
overlappingTriples.add(<dynamic>[
|
||||
overlappingRange.start,
|
||||
overlappingRange.end,
|
||||
rangeSpanMapping[overlappingRange]!.style
|
||||
]);
|
||||
rangeSpanMapping.remove(overlappingRange);
|
||||
}
|
||||
|
||||
@@ -608,10 +641,11 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
if (toRemoveRangesThatHaveBeenMerged.contains(tripleA)) continue;
|
||||
for (int j = i + 1; j < overlappingTriples.length; j++) {
|
||||
final List<dynamic> tripleB = overlappingTriples[j];
|
||||
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]);
|
||||
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]);
|
||||
tripleA = <dynamic>[
|
||||
math.min(tripleA[0] as int, tripleB[0] as int),
|
||||
math.max(tripleA[1] as int, tripleB[1] as int),
|
||||
@@ -621,9 +655,9 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
}
|
||||
}
|
||||
|
||||
if (didOverlap
|
||||
&& !toAddRangesThatHaveBeenMerged.contains(tripleA)
|
||||
&& !toRemoveRangesThatHaveBeenMerged.contains(tripleA)) {
|
||||
if (didOverlap &&
|
||||
!toAddRangesThatHaveBeenMerged.contains(tripleA) &&
|
||||
!toRemoveRangesThatHaveBeenMerged.contains(tripleA)) {
|
||||
toAddRangesThatHaveBeenMerged.add(tripleA);
|
||||
}
|
||||
}
|
||||
@@ -658,15 +692,14 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
}
|
||||
|
||||
Set<TextStyle> styles = <TextStyle>{};
|
||||
List<int> otherEndPoints = endPoints.getRange(1, endPoints.length).toList();
|
||||
List<int> otherEndPoints =
|
||||
endPoints.getRange(1, endPoints.length).toList();
|
||||
for (int i = 0; i < endPoints.length - 1; i++) {
|
||||
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;
|
||||
@@ -674,10 +707,8 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,8 +735,9 @@ 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));
|
||||
@@ -713,11 +745,13 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
}
|
||||
}
|
||||
|
||||
for (final TextEditingInlineSpanReplacement replacementToRemove in toRemove) {
|
||||
for (final TextEditingInlineSpanReplacement replacementToRemove
|
||||
in toRemove) {
|
||||
replacements!.remove(replacementToRemove);
|
||||
}
|
||||
|
||||
for (final TextEditingInlineSpanReplacement replacementWithExpandDisabled in toAdd) {
|
||||
for (final TextEditingInlineSpanReplacement replacementWithExpandDisabled
|
||||
in toAdd) {
|
||||
replacements!.add(replacementWithExpandDisabled);
|
||||
}
|
||||
}
|
||||
@@ -733,30 +767,27 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
|
||||
for (final TextEditingInlineSpanReplacement replacement in replacements!) {
|
||||
if (selection.isCollapsed) {
|
||||
if (math.max(replacement.range.start, selection.start)
|
||||
<= math.min(replacement.range.end, selection.end)) {
|
||||
if (math.max(replacement.range.start, selection.start) <=
|
||||
math.min(replacement.range.end, selection.end)) {
|
||||
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!);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (math.max(replacement.range.start, selection.start)
|
||||
<= math.min(replacement.range.end, selection.end)) {
|
||||
if (math.max(replacement.range.start, selection.start) <=
|
||||
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!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -769,17 +800,19 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
final List<TextEditingInlineSpanReplacement> toRemove = [];
|
||||
final List<TextEditingInlineSpanReplacement> toAdd = [];
|
||||
|
||||
for(int i = 0; i < replacements!.length; i++) {
|
||||
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;
|
||||
|
||||
if ((math.max(replacement.range.start, removalRange.start)
|
||||
<= math.min(replacement.range.end, removalRange.end))
|
||||
&& replacementStyle != null) {
|
||||
if ((math.max(replacement.range.start, removalRange.start) <=
|
||||
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) {
|
||||
@@ -809,4 +842,4 @@ class ReplacementTextEditingController extends TextEditingController {
|
||||
replacements!.remove(replacementToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Signature for the callback that updates text editing delta history when a new delta
|
||||
/// is received.
|
||||
typedef TextEditingDeltaHistoryUpdateCallback = void Function(List<TextEditingDelta> textEditingDeltas);
|
||||
typedef TextEditingDeltaHistoryUpdateCallback = void Function(
|
||||
List<TextEditingDelta> textEditingDeltas);
|
||||
|
||||
class TextEditingDeltaHistoryManager extends InheritedWidget {
|
||||
const TextEditingDeltaHistoryManager({
|
||||
@@ -11,25 +12,29 @@ class TextEditingDeltaHistoryManager extends InheritedWidget {
|
||||
required Widget child,
|
||||
required List<TextEditingDelta> history,
|
||||
required TextEditingDeltaHistoryUpdateCallback updateHistoryOnInput,
|
||||
})
|
||||
: _textEditingDeltaHistory = history,
|
||||
}) : _textEditingDeltaHistory = history,
|
||||
_updateTextEditingDeltaHistoryOnInput = updateHistoryOnInput,
|
||||
super(key: key, child: child);
|
||||
|
||||
static TextEditingDeltaHistoryManager of(BuildContext context) {
|
||||
final TextEditingDeltaHistoryManager? result = context.dependOnInheritedWidgetOfExactType<TextEditingDeltaHistoryManager>();
|
||||
final TextEditingDeltaHistoryManager? result = context
|
||||
.dependOnInheritedWidgetOfExactType<TextEditingDeltaHistoryManager>();
|
||||
assert(result != null, 'No ToggleButtonsStateManager found in context');
|
||||
return result!;
|
||||
}
|
||||
|
||||
final List<TextEditingDelta> _textEditingDeltaHistory;
|
||||
final TextEditingDeltaHistoryUpdateCallback _updateTextEditingDeltaHistoryOnInput;
|
||||
final TextEditingDeltaHistoryUpdateCallback
|
||||
_updateTextEditingDeltaHistoryOnInput;
|
||||
|
||||
List<TextEditingDelta> get textEditingDeltaHistory => _textEditingDeltaHistory;
|
||||
TextEditingDeltaHistoryUpdateCallback get updateTextEditingDeltaHistoryOnInput => _updateTextEditingDeltaHistoryOnInput;
|
||||
List<TextEditingDelta> get textEditingDeltaHistory =>
|
||||
_textEditingDeltaHistory;
|
||||
TextEditingDeltaHistoryUpdateCallback
|
||||
get updateTextEditingDeltaHistoryOnInput =>
|
||||
_updateTextEditingDeltaHistoryOnInput;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(TextEditingDeltaHistoryManager oldWidget) {
|
||||
return textEditingDeltaHistory != oldWidget.textEditingDeltaHistory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,40 +2,52 @@ import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Signature for the callback that updates toggle button state when the user changes the selection
|
||||
/// (including the cursor location).
|
||||
typedef UpdateToggleButtonsStateOnSelectionChangedCallback = void Function(TextSelection selection);
|
||||
typedef UpdateToggleButtonsStateOnSelectionChangedCallback = void Function(
|
||||
TextSelection selection);
|
||||
|
||||
/// Signature for the callback that updates toggle button state when the user
|
||||
/// presses the toggle button.
|
||||
typedef UpdateToggleButtonsStateOnButtonPressedCallback = void Function(int index);
|
||||
typedef UpdateToggleButtonsStateOnButtonPressedCallback = void Function(
|
||||
int index);
|
||||
|
||||
class ToggleButtonsStateManager extends InheritedWidget {
|
||||
const ToggleButtonsStateManager({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
required List<bool> isToggleButtonsSelected,
|
||||
required UpdateToggleButtonsStateOnButtonPressedCallback updateToggleButtonsStateOnButtonPressed,
|
||||
required UpdateToggleButtonsStateOnSelectionChangedCallback updateToggleButtonStateOnSelectionChanged,
|
||||
})
|
||||
: _isToggleButtonsSelected = isToggleButtonsSelected,
|
||||
_updateToggleButtonsStateOnButtonPressed = updateToggleButtonsStateOnButtonPressed,
|
||||
_updateToggleButtonStateOnSelectionChanged = updateToggleButtonStateOnSelectionChanged,
|
||||
required UpdateToggleButtonsStateOnButtonPressedCallback
|
||||
updateToggleButtonsStateOnButtonPressed,
|
||||
required UpdateToggleButtonsStateOnSelectionChangedCallback
|
||||
updateToggleButtonStateOnSelectionChanged,
|
||||
}) : _isToggleButtonsSelected = isToggleButtonsSelected,
|
||||
_updateToggleButtonsStateOnButtonPressed =
|
||||
updateToggleButtonsStateOnButtonPressed,
|
||||
_updateToggleButtonStateOnSelectionChanged =
|
||||
updateToggleButtonStateOnSelectionChanged,
|
||||
super(key: key, child: child);
|
||||
|
||||
static ToggleButtonsStateManager of(BuildContext context) {
|
||||
final ToggleButtonsStateManager? result = context.dependOnInheritedWidgetOfExactType<ToggleButtonsStateManager>();
|
||||
final ToggleButtonsStateManager? result =
|
||||
context.dependOnInheritedWidgetOfExactType<ToggleButtonsStateManager>();
|
||||
assert(result != null, 'No ToggleButtonsStateManager found in context');
|
||||
return result!;
|
||||
}
|
||||
|
||||
final List<bool> _isToggleButtonsSelected;
|
||||
final UpdateToggleButtonsStateOnButtonPressedCallback _updateToggleButtonsStateOnButtonPressed;
|
||||
final UpdateToggleButtonsStateOnSelectionChangedCallback _updateToggleButtonStateOnSelectionChanged;
|
||||
final UpdateToggleButtonsStateOnButtonPressedCallback
|
||||
_updateToggleButtonsStateOnButtonPressed;
|
||||
final UpdateToggleButtonsStateOnSelectionChangedCallback
|
||||
_updateToggleButtonStateOnSelectionChanged;
|
||||
|
||||
List<bool> get toggleButtonsState => _isToggleButtonsSelected;
|
||||
UpdateToggleButtonsStateOnButtonPressedCallback get updateToggleButtonsOnButtonPressed => _updateToggleButtonsStateOnButtonPressed;
|
||||
UpdateToggleButtonsStateOnSelectionChangedCallback get updateToggleButtonsOnSelection => _updateToggleButtonStateOnSelectionChanged;
|
||||
UpdateToggleButtonsStateOnButtonPressedCallback
|
||||
get updateToggleButtonsOnButtonPressed =>
|
||||
_updateToggleButtonsStateOnButtonPressed;
|
||||
UpdateToggleButtonsStateOnSelectionChangedCallback
|
||||
get updateToggleButtonsOnSelection =>
|
||||
_updateToggleButtonStateOnSelectionChanged;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ToggleButtonsStateManager oldWidget) =>
|
||||
toggleButtonsState != oldWidget.toggleButtonsState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,46 +12,51 @@ import 'package:simplistic_editor/basic_text_input_client.dart';
|
||||
import 'package:simplistic_editor/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Default main page shows all components',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
testWidgets('Default main page shows all components', (tester) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
// Elements on the main screen
|
||||
// Delta labels.
|
||||
expect(
|
||||
find.widgetWithText(Tooltip, "Delta Type"), findsOneWidget);
|
||||
expect(find.widgetWithText(Tooltip, "Delta Text"), findsOneWidget);
|
||||
expect(
|
||||
find.widgetWithText(Tooltip, "Delta Offset"),
|
||||
findsOneWidget);
|
||||
expect(
|
||||
find.widgetWithText(Tooltip, "New Selection"), findsOneWidget);
|
||||
expect(find.widgetWithText(Tooltip, "New Composing"), findsOneWidget);
|
||||
// Elements on the main screen
|
||||
// Delta labels.
|
||||
expect(find.widgetWithText(Tooltip, "Delta Type"), findsOneWidget);
|
||||
expect(find.widgetWithText(Tooltip, "Delta Text"), findsOneWidget);
|
||||
expect(find.widgetWithText(Tooltip, "Delta Offset"), findsOneWidget);
|
||||
expect(find.widgetWithText(Tooltip, "New Selection"), findsOneWidget);
|
||||
expect(find.widgetWithText(Tooltip, "New Composing"), findsOneWidget);
|
||||
|
||||
// Selection delta is generated and delta history is visible.
|
||||
await tester.tap(find.byType(BasicTextInputClient));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.widgetWithText(TextEditingDeltaView, "NonTextUpdate"), findsOneWidget);
|
||||
// Selection delta is generated and delta history is visible.
|
||||
await tester.tap(find.byType(BasicTextInputClient));
|
||||
await tester.pumpAndSettle();
|
||||
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);
|
||||
expect(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);
|
||||
// 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);
|
||||
expect(
|
||||
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);
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.info_outline));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.widgetWithText(Center, 'About'), findsOneWidget);
|
||||
});
|
||||
// About Dialog
|
||||
expect(find.widgetWithIcon(IconButton, Icons.info_outline), findsOneWidget);
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.info_outline));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.widgetWithText(Center, 'About'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user