sync flutter widgets

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-02-12 11:01:35 +08:00
parent fbf7116edf
commit 483953cf56
31 changed files with 804 additions and 646 deletions

View File

@@ -273,8 +273,8 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
});
case TargetPlatform.fuchsia:
case TargetPlatform.android:
final List<Widget> buttons = <Widget>[];
for (int i = 0; i < buttonItems.length; i++) {
final buttons = <Widget>[];
for (var i = 0; i < buttonItems.length; i++) {
final ContextMenuButtonItem buttonItem = buttonItems[i];
buttons.add(
TextSelectionToolbarTextButton(

View File

@@ -90,7 +90,7 @@ class CupertinoSpellCheckSuggestionsToolbar extends StatelessWidget {
];
}
final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
final buttonItems = <ContextMenuButtonItem>[];
// Build suggestion buttons.
for (final String suggestion in spanAtCursorIndex.suggestions.take(

View File

@@ -99,7 +99,7 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder
// this handler. If the clear button widget recognizes the up event,
// then do not handle it.
if (_state._clearGlobalKey.currentContext != null) {
final RenderBox renderBox =
final renderBox =
_state._clearGlobalKey.currentContext!.findRenderObject()!
as RenderBox;
final Offset localOffset = renderBox.globalToLocal(
@@ -1482,6 +1482,7 @@ class _CupertinoRichTextFieldState extends State<CupertinoRichTextField>
child: _BaselineAlignedStack(
placeholder: placeholder,
editableText: editableText,
textAlignVertical: _textAlignVertical,
editableTextBaseline:
textStyle.textBaseline ?? TextBaseline.alphabetic,
placeholderBaseline:
@@ -1555,11 +1556,11 @@ class _CupertinoRichTextFieldState extends State<CupertinoRichTextField>
}
final bool enabled = widget.enabled;
final Offset cursorOffset = Offset(
final cursorOffset = Offset(
_iOSHorizontalCursorOffsetPixels / MediaQuery.devicePixelRatioOf(context),
0,
);
final List<TextInputFormatter> formatters = <TextInputFormatter>[
final formatters = <TextInputFormatter>[
...?widget.inputFormatters,
if (widget.maxLength != null)
LengthLimitingTextInputFormatter(
@@ -1617,7 +1618,7 @@ class _CupertinoRichTextFieldState extends State<CupertinoRichTextField>
);
final BoxBorder? border = widget.decoration?.border;
Border? resolvedBorder = border as Border?;
var resolvedBorder = border as Border?;
if (border is Border) {
BorderSide resolveBorderSide(BorderSide side) {
return side == BorderSide.none
@@ -1828,14 +1829,16 @@ class _BaselineAlignedStack
const _BaselineAlignedStack({
required this.editableTextBaseline,
required this.placeholderBaseline,
required this.textAlignVertical,
required this.editableText,
this.placeholder,
});
final TextBaseline editableTextBaseline;
final TextBaseline placeholderBaseline;
final Widget? placeholder;
final TextAlignVertical textAlignVertical;
final Widget editableText;
final Widget? placeholder;
@override
Iterable<_BaselineAlignedStackSlot> get slots =>
@@ -1852,6 +1855,7 @@ class _BaselineAlignedStack
@override
_RenderBaselineAlignedStack createRenderObject(BuildContext context) {
return _RenderBaselineAlignedStack(
textAlignVertical: textAlignVertical,
editableTextBaseline: editableTextBaseline,
placeholderBaseline: placeholderBaseline,
);
@@ -1863,6 +1867,7 @@ class _BaselineAlignedStack
_RenderBaselineAlignedStack renderObject,
) {
renderObject
..textAlignVertical = textAlignVertical
..editableTextBaseline = editableTextBaseline
..placeholderBaseline = placeholderBaseline;
}
@@ -1878,11 +1883,23 @@ class _RenderBaselineAlignedStack extends RenderBox
RenderBox
> {
_RenderBaselineAlignedStack({
required TextAlignVertical textAlignVertical,
required TextBaseline editableTextBaseline,
required TextBaseline placeholderBaseline,
}) : _editableTextBaseline = editableTextBaseline,
}) : _textAlignVertical = textAlignVertical,
_editableTextBaseline = editableTextBaseline,
_placeholderBaseline = placeholderBaseline;
TextAlignVertical get textAlignVertical => _textAlignVertical;
TextAlignVertical _textAlignVertical;
set textAlignVertical(TextAlignVertical value) {
if (_textAlignVertical == value) {
return;
}
_textAlignVertical = value;
markNeedsLayout();
}
TextBaseline get editableTextBaseline => _editableTextBaseline;
TextBaseline _editableTextBaseline;
set editableTextBaseline(TextBaseline value) {
@@ -1960,9 +1977,9 @@ class _RenderBaselineAlignedStack extends RenderBox
final RenderBox? placeholder = _placeholderChild;
final RenderBox editableText = _editableTextChild;
final _BaselineAlignedStackParentData editableTextParentData =
final editableTextParentData =
editableText.parentData! as _BaselineAlignedStackParentData;
final _BaselineAlignedStackParentData? placeholderParentData =
final placeholderParentData =
placeholder?.parentData as _BaselineAlignedStackParentData?;
size = _computeSize(
@@ -1979,13 +1996,17 @@ class _RenderBaselineAlignedStack extends RenderBox
);
assert(placeholder != null || placeholderBaselineValue == null);
final double placeholderY = placeholderBaselineValue != null
? editableTextBaselineValue - placeholderBaselineValue
: 0.0;
final Offset baselineDiff = placeholderBaselineValue != null
? Offset(0.0, editableTextBaselineValue - placeholderBaselineValue)
: Offset.zero;
final verticalAlignment = Alignment(0.0, textAlignVertical.y);
final double offsetYAdjustment = math.max(0, placeholderY);
editableTextParentData.offset = Offset(0, offsetYAdjustment);
placeholderParentData?.offset = Offset(0, placeholderY + offsetYAdjustment);
editableTextParentData.offset = verticalAlignment.alongOffset(
size - editableText.size as Offset,
);
// Baseline-align the placeholder to the editable text.
placeholderParentData?.offset =
editableTextParentData.offset + baselineDiff;
}
@override
@@ -1994,12 +2015,12 @@ class _RenderBaselineAlignedStack extends RenderBox
final RenderBox editableText = _editableTextChild;
if (placeholder != null) {
final _BaselineAlignedStackParentData placeholderParentData =
final placeholderParentData =
placeholder.parentData! as _BaselineAlignedStackParentData;
context.paintChild(placeholder, offset + placeholderParentData.offset);
}
final _BaselineAlignedStackParentData editableTextParentData =
final editableTextParentData =
editableText.parentData! as _BaselineAlignedStackParentData;
context.paintChild(editableText, offset + editableTextParentData.offset);
}
@@ -2054,7 +2075,7 @@ class _RenderBaselineAlignedStack extends RenderBox
height = math.max(height, editableTextSize.height);
width = math.max(width, editableTextSize.width);
final Size size = Size(width, height);
final size = Size(width, height);
assert(size.isFinite);
return constraints.constrain(size);
}
@@ -2062,7 +2083,7 @@ class _RenderBaselineAlignedStack extends RenderBox
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
final RenderBox editableText = _editableTextChild;
final _BaselineAlignedStackParentData editableTextParentData =
final editableTextParentData =
editableText.parentData! as _BaselineAlignedStackParentData;
return result.addWithPaintOffset(

View File

@@ -145,17 +145,13 @@ class VerticalCaretMovementRun implements Iterator<TextPosition> {
}
assert(lineNumber != _currentLine);
final Offset newOffset = Offset(
final newOffset = Offset(
_currentOffset.dx,
_lineMetrics[lineNumber].baseline,
);
final TextPosition closestPosition = _editable._textPainter
.getPositionForOffset(newOffset);
final MapEntry<Offset, TextPosition> position =
MapEntry<Offset, TextPosition>(
newOffset,
closestPosition,
);
final position = MapEntry<Offset, TextPosition>(newOffset, closestPosition);
_positionCache[lineNumber] = position;
return position;
}
@@ -419,8 +415,9 @@ class RenderEditable extends RenderBox
);
if (_foregroundRenderObject == null) {
final _RenderEditableCustomPaint foregroundRenderObject =
_RenderEditableCustomPaint(painter: effectivePainter);
final foregroundRenderObject = _RenderEditableCustomPaint(
painter: effectivePainter,
);
adoptChild(foregroundRenderObject);
_foregroundRenderObject = foregroundRenderObject;
} else {
@@ -452,8 +449,9 @@ class RenderEditable extends RenderBox
);
if (_backgroundRenderObject == null) {
final _RenderEditableCustomPaint backgroundRenderObject =
_RenderEditableCustomPaint(painter: effectivePainter);
final backgroundRenderObject = _RenderEditableCustomPaint(
painter: effectivePainter,
);
adoptChild(backgroundRenderObject);
_backgroundRenderObject = backgroundRenderObject;
} else {
@@ -714,7 +712,7 @@ class RenderEditable extends RenderBox
// happens in paragraph.cc's layout and TextPainter's
// _applyFloatingPointHack. Ideally, the rounding mismatch will be fixed and
// this can be changed to be a strict check instead of an approximation.
const double visibleRegionSlop = 0.5;
const visibleRegionSlop = 0.5;
_selectionStartInViewport.value = visibleRegion
.inflate(visibleRegionSlop)
.contains(startOffset + effectiveOffset);
@@ -1356,9 +1354,9 @@ class RenderEditable extends RenderBox
obscuringCharacter * plainText.length,
);
} else {
final StringBuffer buffer = StringBuffer();
int offset = 0;
final List<StringAttribute> attributes = <StringAttribute>[];
final buffer = StringBuffer();
var offset = 0;
final attributes = <StringAttribute>[];
for (final InlineSpanSemanticsInformation info in _semanticsInfo!) {
final String label = info.semanticsLabel ?? info.text;
for (final StringAttribute infoAttribute in info.stringAttributes) {
@@ -1436,19 +1434,19 @@ class RenderEditable extends RenderBox
Iterable<SemanticsNode> children,
) {
assert(_semanticsInfo != null && _semanticsInfo!.isNotEmpty);
final List<SemanticsNode> newChildren = <SemanticsNode>[];
final newChildren = <SemanticsNode>[];
TextDirection currentDirection = textDirection;
Rect currentRect;
double ordinal = 0.0;
int start = 0;
int placeholderIndex = 0;
int childIndex = 0;
var ordinal = 0.0;
var start = 0;
var placeholderIndex = 0;
var childIndex = 0;
RenderBox? child = firstChild;
final Map<Key, SemanticsNode> newChildCache = <Key, SemanticsNode>{};
final newChildCache = <Key, SemanticsNode>{};
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
for (final InlineSpanSemanticsInformation info
in _cachedCombinedSemanticsInfos!) {
final TextSelection selection = TextSelection(
final selection = TextSelection(
baseOffset: start,
extentOffset: start + info.text.length,
);
@@ -1462,8 +1460,7 @@ class RenderEditable extends RenderBox
.elementAt(childIndex)
.isTagged(PlaceholderSpanIndexSemanticsTag(placeholderIndex))) {
final SemanticsNode childNode = children.elementAt(childIndex);
final TextParentData parentData =
child!.parentData! as TextParentData;
final parentData = child!.parentData! as TextParentData;
assert(parentData.offset != null);
newChildren.add(childNode);
childIndex += 1;
@@ -1471,7 +1468,7 @@ class RenderEditable extends RenderBox
child = childAfter(child!);
placeholderIndex += 1;
} else {
final TextDirection initialDirection = currentDirection;
final initialDirection = currentDirection;
final List<ui.TextBox> rects = _textPainter.getBoxesForSelection(
selection,
);
@@ -1500,7 +1497,7 @@ class RenderEditable extends RenderBox
rect.right.ceilToDouble() + 4.0,
rect.bottom.ceilToDouble() + 4.0,
);
final SemanticsConfiguration configuration = SemanticsConfiguration()
final configuration = SemanticsConfiguration()
..sortKey = OrdinalSortKey(ordinal++)
..textDirection = initialDirection
..attributedLabel = AttributedString(
@@ -1538,7 +1535,7 @@ class RenderEditable extends RenderBox
if (_cachedChildNodes?.isNotEmpty ?? false) {
newChild = _cachedChildNodes!.remove(_cachedChildNodes!.keys.first)!;
} else {
final UniqueKey key = UniqueKey();
final key = UniqueKey();
newChild = SemanticsNode(
key: key,
showOnScreen: _createShowOnScreenFor(key),
@@ -1980,8 +1977,8 @@ class RenderEditable extends RenderBox
if (cachedValue != null) {
return cachedValue;
}
int count = 0;
for (int index = 0; index < text.length; index += 1) {
var count = 0;
for (var index = 0; index < text.length; index += 1) {
switch (text.codeUnitAt(index)) {
case 0x000A: // LF
case 0x0085: // NEL
@@ -2242,7 +2239,7 @@ class RenderEditable extends RenderBox
extentOffset = isNormalized ? newOffset.endOffset : newOffset.startOffset;
}
final TextSelection newSelection = TextSelection(
final newSelection = TextSelection(
baseOffset: baseOffset,
extentOffset: extentOffset,
affinity: fromPosition.affinity,
@@ -2591,12 +2588,12 @@ class RenderEditable extends RenderBox
};
size = Size(width, constraints.constrainHeight(preferredHeight));
final Size contentSize = Size(
final contentSize = Size(
_textPainter.width + _caretMargin,
_textPainter.height,
);
final BoxConstraints painterConstraints = BoxConstraints.tight(contentSize);
final painterConstraints = BoxConstraints.tight(contentSize);
_foregroundRenderObject?.layout(painterConstraints);
_backgroundRenderObject?.layout(painterConstraints);
@@ -2656,7 +2653,7 @@ class RenderEditable extends RenderBox
final double rightBound =
math.min(size.width, _textPainter.width) +
floatingCursorAddedMargin.right;
final Rect boundingRects = Rect.fromLTRB(
final boundingRects = Rect.fromLTRB(
leftBound,
topBound,
rightBound,
@@ -2780,7 +2777,7 @@ class RenderEditable extends RenderBox
startPosition,
Rect.zero,
);
for (final ui.LineMetrics lineMetrics in metrics) {
for (final lineMetrics in metrics) {
if (lineMetrics.baseline > offset.dy) {
return MapEntry<int, Offset>(
lineMetrics.lineNumber,
@@ -3163,7 +3160,7 @@ class _TextHighlightPainter extends RenderEditablePainter {
)
.toSet();
for (final TextBox box in boxes) {
for (final box in boxes) {
canvas.drawRect(
box
.toRect()
@@ -3290,7 +3287,7 @@ class _CaretPainter extends RenderEditablePainter {
if (radius == null) {
canvas.drawRect(integralRect, caretPaint);
} else {
final RRect caretRRect = RRect.fromRectAndRadius(integralRect, radius);
final caretRRect = RRect.fromRectAndRadius(integralRect, radius);
canvas.drawRRect(caretRRect, caretPaint);
}
}

View File

@@ -146,7 +146,7 @@ class _DiscreteKeyFrameSimulation extends Simulation {
: assert(_keyFrames.isNotEmpty),
assert(_keyFrames.last.time <= maxDuration),
assert(() {
for (int i = 0; i < _keyFrames.length - 1; i += 1) {
for (var i = 0; i < _keyFrames.length - 1; i += 1) {
if (_keyFrames[i].time > _keyFrames[i + 1].time) {
return false;
}
@@ -407,7 +407,11 @@ class _DiscreteKeyFrameSimulation extends Simulation {
/// ```dart
/// onChanged: (String newText) {
/// if (newText.isNotEmpty) {
/// SemanticsService.announce('\$$newText', Directionality.of(context));
/// SemanticsService.sendAnnouncement(
/// View.of(context),
/// '\$$newText',
/// Directionality.of(context),
/// );
/// }
/// }
/// ```
@@ -692,6 +696,13 @@ class EditableText extends StatefulWidget {
final bool enableSuggestions;
/// The text style to use for the editable text.
///
/// The user or platform may override this [style]'s [TextStyle.fontWeight],
/// [TextStyle.height], [TextStyle.letterSpacing], and [TextStyle.wordSpacing]
/// via a [MediaQuery] ancestor's [MediaQueryData.boldText],
/// [MediaQueryData.lineHeightScaleFactorOverride],
/// [MediaQueryData.letterSpacingOverride], and [MediaQueryData.wordSpacingOverride]
/// regardless of its [TextStyle.inherit] value.
final TextStyle style;
/// {@template flutter.widgets.editableText.strutStyle}
@@ -718,6 +729,9 @@ class EditableText extends StatefulWidget {
/// Within editable text and text fields, [StrutStyle] will not use its standalone
/// default values, and will instead inherit omitted/null properties from the
/// [TextStyle] instead. See [StrutStyle.inheritFromTextStyle].
///
/// The user or platform may override this [strutStyle]'s [StrutStyle.height]
/// via a [MediaQuery] ancestor's [MediaQueryData.lineHeightScaleFactorOverride].
StrutStyle get strutStyle {
if (_strutStyle == null) {
return StrutStyle.fromTextStyle(style, forceStrutHeight: true);
@@ -1750,8 +1764,7 @@ class EditableText extends StatefulWidget {
required final VoidCallback? onShare,
required final VoidCallback? onLiveTextInput,
}) {
final List<ContextMenuButtonItem> resultButtonItem =
<ContextMenuButtonItem>[];
final resultButtonItem = <ContextMenuButtonItem>[];
// Configure button items with clipboard.
if (onPaste == null || clipboardStatus != ClipboardStatus.unknown) {
@@ -1760,7 +1773,7 @@ class EditableText extends StatefulWidget {
// shown.
// On Android, the share button is before the select all button.
final bool showShareBeforeSelectAll =
final showShareBeforeSelectAll =
defaultTargetPlatform == TargetPlatform.android;
resultButtonItem.addAll(<ContextMenuButtonItem>[
@@ -1875,8 +1888,7 @@ class EditableText extends StatefulWidget {
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
const Map<String, TextInputType>
iOSKeyboardType = <String, TextInputType>{
const iOSKeyboardType = <String, TextInputType>{
AutofillHints.addressCity: TextInputType.name,
AutofillHints.addressCityAndState:
TextInputType.name, // Autofill not working.
@@ -1931,77 +1943,76 @@ class EditableText extends StatefulWidget {
return TextInputType.multiline;
}
const Map<String, TextInputType> inferKeyboardType =
<String, TextInputType>{
AutofillHints.addressCity: TextInputType.streetAddress,
AutofillHints.addressCityAndState: TextInputType.streetAddress,
AutofillHints.addressState: TextInputType.streetAddress,
AutofillHints.birthday: TextInputType.datetime,
AutofillHints.birthdayDay: TextInputType.datetime,
AutofillHints.birthdayMonth: TextInputType.datetime,
AutofillHints.birthdayYear: TextInputType.datetime,
AutofillHints.countryCode: TextInputType.number,
AutofillHints.countryName: TextInputType.text,
AutofillHints.creditCardExpirationDate: TextInputType.datetime,
AutofillHints.creditCardExpirationDay: TextInputType.datetime,
AutofillHints.creditCardExpirationMonth: TextInputType.datetime,
AutofillHints.creditCardExpirationYear: TextInputType.datetime,
AutofillHints.creditCardFamilyName: TextInputType.name,
AutofillHints.creditCardGivenName: TextInputType.name,
AutofillHints.creditCardMiddleName: TextInputType.name,
AutofillHints.creditCardName: TextInputType.name,
AutofillHints.creditCardNumber: TextInputType.number,
AutofillHints.creditCardSecurityCode: TextInputType.number,
AutofillHints.creditCardType: TextInputType.text,
AutofillHints.email: TextInputType.emailAddress,
AutofillHints.familyName: TextInputType.name,
AutofillHints.fullStreetAddress: TextInputType.streetAddress,
AutofillHints.gender: TextInputType.text,
AutofillHints.givenName: TextInputType.name,
AutofillHints.impp: TextInputType.url,
AutofillHints.jobTitle: TextInputType.text,
AutofillHints.language: TextInputType.text,
AutofillHints.location: TextInputType.streetAddress,
AutofillHints.middleInitial: TextInputType.name,
AutofillHints.middleName: TextInputType.name,
AutofillHints.name: TextInputType.name,
AutofillHints.namePrefix: TextInputType.name,
AutofillHints.nameSuffix: TextInputType.name,
AutofillHints.newPassword: TextInputType.text,
AutofillHints.newUsername: TextInputType.text,
AutofillHints.nickname: TextInputType.text,
AutofillHints.oneTimeCode: TextInputType.text,
AutofillHints.organizationName: TextInputType.text,
AutofillHints.password: TextInputType.text,
AutofillHints.photo: TextInputType.text,
AutofillHints.postalAddress: TextInputType.streetAddress,
AutofillHints.postalAddressExtended: TextInputType.streetAddress,
AutofillHints.postalAddressExtendedPostalCode: TextInputType.number,
AutofillHints.postalCode: TextInputType.number,
AutofillHints.streetAddressLevel1: TextInputType.streetAddress,
AutofillHints.streetAddressLevel2: TextInputType.streetAddress,
AutofillHints.streetAddressLevel3: TextInputType.streetAddress,
AutofillHints.streetAddressLevel4: TextInputType.streetAddress,
AutofillHints.streetAddressLine1: TextInputType.streetAddress,
AutofillHints.streetAddressLine2: TextInputType.streetAddress,
AutofillHints.streetAddressLine3: TextInputType.streetAddress,
AutofillHints.sublocality: TextInputType.streetAddress,
AutofillHints.telephoneNumber: TextInputType.phone,
AutofillHints.telephoneNumberAreaCode: TextInputType.phone,
AutofillHints.telephoneNumberCountryCode: TextInputType.phone,
AutofillHints.telephoneNumberDevice: TextInputType.phone,
AutofillHints.telephoneNumberExtension: TextInputType.phone,
AutofillHints.telephoneNumberLocal: TextInputType.phone,
AutofillHints.telephoneNumberLocalPrefix: TextInputType.phone,
AutofillHints.telephoneNumberLocalSuffix: TextInputType.phone,
AutofillHints.telephoneNumberNational: TextInputType.phone,
AutofillHints.transactionAmount: TextInputType.numberWithOptions(
decimal: true,
),
AutofillHints.transactionCurrency: TextInputType.text,
AutofillHints.url: TextInputType.url,
AutofillHints.username: TextInputType.text,
};
const inferKeyboardType = <String, TextInputType>{
AutofillHints.addressCity: TextInputType.streetAddress,
AutofillHints.addressCityAndState: TextInputType.streetAddress,
AutofillHints.addressState: TextInputType.streetAddress,
AutofillHints.birthday: TextInputType.datetime,
AutofillHints.birthdayDay: TextInputType.datetime,
AutofillHints.birthdayMonth: TextInputType.datetime,
AutofillHints.birthdayYear: TextInputType.datetime,
AutofillHints.countryCode: TextInputType.number,
AutofillHints.countryName: TextInputType.text,
AutofillHints.creditCardExpirationDate: TextInputType.datetime,
AutofillHints.creditCardExpirationDay: TextInputType.datetime,
AutofillHints.creditCardExpirationMonth: TextInputType.datetime,
AutofillHints.creditCardExpirationYear: TextInputType.datetime,
AutofillHints.creditCardFamilyName: TextInputType.name,
AutofillHints.creditCardGivenName: TextInputType.name,
AutofillHints.creditCardMiddleName: TextInputType.name,
AutofillHints.creditCardName: TextInputType.name,
AutofillHints.creditCardNumber: TextInputType.number,
AutofillHints.creditCardSecurityCode: TextInputType.number,
AutofillHints.creditCardType: TextInputType.text,
AutofillHints.email: TextInputType.emailAddress,
AutofillHints.familyName: TextInputType.name,
AutofillHints.fullStreetAddress: TextInputType.streetAddress,
AutofillHints.gender: TextInputType.text,
AutofillHints.givenName: TextInputType.name,
AutofillHints.impp: TextInputType.url,
AutofillHints.jobTitle: TextInputType.text,
AutofillHints.language: TextInputType.text,
AutofillHints.location: TextInputType.streetAddress,
AutofillHints.middleInitial: TextInputType.name,
AutofillHints.middleName: TextInputType.name,
AutofillHints.name: TextInputType.name,
AutofillHints.namePrefix: TextInputType.name,
AutofillHints.nameSuffix: TextInputType.name,
AutofillHints.newPassword: TextInputType.text,
AutofillHints.newUsername: TextInputType.text,
AutofillHints.nickname: TextInputType.text,
AutofillHints.oneTimeCode: TextInputType.text,
AutofillHints.organizationName: TextInputType.text,
AutofillHints.password: TextInputType.text,
AutofillHints.photo: TextInputType.text,
AutofillHints.postalAddress: TextInputType.streetAddress,
AutofillHints.postalAddressExtended: TextInputType.streetAddress,
AutofillHints.postalAddressExtendedPostalCode: TextInputType.number,
AutofillHints.postalCode: TextInputType.number,
AutofillHints.streetAddressLevel1: TextInputType.streetAddress,
AutofillHints.streetAddressLevel2: TextInputType.streetAddress,
AutofillHints.streetAddressLevel3: TextInputType.streetAddress,
AutofillHints.streetAddressLevel4: TextInputType.streetAddress,
AutofillHints.streetAddressLine1: TextInputType.streetAddress,
AutofillHints.streetAddressLine2: TextInputType.streetAddress,
AutofillHints.streetAddressLine3: TextInputType.streetAddress,
AutofillHints.sublocality: TextInputType.streetAddress,
AutofillHints.telephoneNumber: TextInputType.phone,
AutofillHints.telephoneNumberAreaCode: TextInputType.phone,
AutofillHints.telephoneNumberCountryCode: TextInputType.phone,
AutofillHints.telephoneNumberDevice: TextInputType.phone,
AutofillHints.telephoneNumberExtension: TextInputType.phone,
AutofillHints.telephoneNumberLocal: TextInputType.phone,
AutofillHints.telephoneNumberLocalPrefix: TextInputType.phone,
AutofillHints.telephoneNumberLocalSuffix: TextInputType.phone,
AutofillHints.telephoneNumberNational: TextInputType.phone,
AutofillHints.transactionAmount: TextInputType.numberWithOptions(
decimal: true,
),
AutofillHints.transactionCurrency: TextInputType.text,
AutofillHints.url: TextInputType.url,
AutofillHints.username: TextInputType.text,
};
return inferKeyboardType[effectiveHint] ?? TextInputType.text;
}
@@ -2341,7 +2352,7 @@ class EditableTextState extends State<EditableText>
widget.cursorColor.alpha / 255.0,
_cursorBlinkOpacityController.value,
);
return widget.cursorColor.withOpacity(effectiveOpacity);
return widget.cursorColor.withValues(alpha: effectiveOpacity);
}
@override
@@ -2737,9 +2748,9 @@ class EditableTextState extends State<EditableText>
final List<SuggestionSpan> suggestionSpans =
spellCheckResults!.suggestionSpans;
int leftIndex = 0;
var leftIndex = 0;
int rightIndex = suggestionSpans.length - 1;
int midIndex = 0;
var midIndex = 0;
while (leftIndex <= rightIndex) {
midIndex = ((leftIndex + rightIndex) / 2).floor();
@@ -2983,7 +2994,7 @@ class EditableTextState extends State<EditableText>
}
List<ContextMenuButtonItem> get _textProcessingActionButtonItems {
final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
final buttonItems = <ContextMenuButtonItem>[];
final TextSelection selection = textEditingValue.selection;
if (widget.obscureText || !selection.isValid || selection.isCollapsed) {
return buttonItems;
@@ -3033,17 +3044,29 @@ class EditableTextState extends State<EditableText>
_spellCheckConfiguration = _inferSpellCheckConfiguration(
widget.spellCheckConfiguration,
);
_appLifecycleListener = AppLifecycleListener(
onResume: () => _justResumed = true,
);
_appLifecycleListener = AppLifecycleListener(onResume: _onResume);
_initProcessTextActions();
}
void _onResume() {
_justResumed = true;
// To prevent adding multiple listeners, remove any existing one first.
FocusManager.instance.removeListener(_resetJustResumed);
// Reset _justResumed as soon as there is a focus change.
FocusManager.instance.addListener(_resetJustResumed);
}
void _resetJustResumed() {
_justResumed = false;
FocusManager.instance.removeListener(_resetJustResumed);
}
/// Query the engine to initialize the list of text processing actions to show
/// in the text selection toolbar.
Future<void> _initProcessTextActions() async {
_processTextActions.clear();
_processTextActions.addAll(await _processTextService.queryTextActions());
_processTextActions
..clear()
..addAll(await _processTextService.queryTextActions());
}
// Whether `TickerMode.of(context)` is true and animations (like blinking the
@@ -3269,11 +3292,13 @@ class EditableTextState extends State<EditableText>
WidgetsBinding.instance.removeObserver(this);
_liveTextInputStatus?.removeListener(_onChangedLiveTextInputStatus);
_liveTextInputStatus?.dispose();
clipboardStatus.removeListener(_onChangedClipboardStatus);
clipboardStatus.dispose();
clipboardStatus
..removeListener(_onChangedClipboardStatus)
..dispose();
_cursorVisibilityNotifier.dispose();
_appLifecycleListener.dispose();
FocusManager.instance.removeListener(_unflagInternalFocus);
FocusManager.instance.removeListener(_resetJustResumed);
_disposeScrollNotificationObserver();
super.dispose();
assert(_batchEditDepth <= 0, 'unfinished batch edits: $_batchEditDepth');
@@ -3803,7 +3828,7 @@ class EditableTextState extends State<EditableText>
// The caret is vertically centered within the line. Expand the caret's
// height so that it spans the line because we're going to ensure that the
// entire expanded caret is scrolled into view.
final Rect expandedRect = Rect.fromCenter(
final expandedRect = Rect.fromCenter(
center: rect.center,
width: rect.width,
height: math.max(rect.height, renderEditable.preferredLineHeight),
@@ -4088,7 +4113,7 @@ class EditableTextState extends State<EditableText>
view.devicePixelRatio;
final double obscuredHorizontal =
(view.padding.left + view.padding.right) / view.devicePixelRatio;
final Size visibleScreenSize = Size(
final visibleScreenSize = Size(
screenSize.width - obscuredHorizontal,
screenSize.height - obscuredVertical,
);
@@ -4160,7 +4185,13 @@ class EditableTextState extends State<EditableText>
_showToolbarOnScreenScheduled = true;
SchedulerBinding.instance.addPostFrameCallback((Duration _) {
_showToolbarOnScreenScheduled = false;
if (!mounted) {
if (!mounted || _dataWhenToolbarShowScheduled == null) {
return;
}
if (_dataWhenToolbarShowScheduled!.value != _value) {
// Value has changed so we should invalidate any toolbar scheduling.
_dataWhenToolbarShowScheduled = null;
_disposeScrollNotificationObserver();
return;
}
final Rect deviceRect = _calculateDeviceRect();
@@ -4210,7 +4241,7 @@ class EditableTextState extends State<EditableText>
TextSelectionOverlay _createSelectionOverlay() {
final EditableTextContextMenuBuilder? contextMenuBuilder =
widget.contextMenuBuilder;
final TextSelectionOverlay selectionOverlay = TextSelectionOverlay(
final selectionOverlay = TextSelectionOverlay(
controller: widget.controller,
clipboardStatus: clipboardStatus,
context: context,
@@ -4319,7 +4350,7 @@ class EditableTextState extends State<EditableText>
_showCaretOnScreenScheduled = false;
// Since we are in a post frame callback, check currentContext in case
// RenderEditable has been disposed (in which case it will be null).
final RenderEditable? renderEditable =
final renderEditable =
_editableKey.currentContext?.findRenderObject() as RenderEditable?;
if (renderEditable == null ||
!(renderEditable.selection?.isValid ?? false) ||
@@ -4435,13 +4466,30 @@ class EditableTextState extends State<EditableText>
.spellCheckService!
.fetchSpellCheckSuggestions(localeForSpellChecking!, text);
if (suggestions == null) {
// The request to fetch spell check suggestions was canceled due to ongoing request.
if (suggestions == null || !mounted) {
// The request to fetch spell check suggestions was canceled due to ongoing request,
// or the widget was unmounted.
return;
}
spellCheckResults = SpellCheckResults(text, suggestions);
renderEditable.text = buildTextSpan();
final double? lineHeightScaleFactor =
MediaQuery.maybeLineHeightScaleFactorOverrideOf(
context,
);
final double? letterSpacing = MediaQuery.maybeLetterSpacingOverrideOf(
context,
);
final double? wordSpacing = MediaQuery.maybeWordSpacingOverrideOf(
context,
);
renderEditable.text =
_OverridingTextStyleTextSpanUtils.applyTextSpacingOverrides(
lineHeightScaleFactor: lineHeightScaleFactor,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
textSpan: buildTextSpan(),
);
} catch (exception, stack) {
FlutterError.reportError(
FlutterErrorDetails(
@@ -4461,10 +4509,10 @@ class EditableTextState extends State<EditableText>
bool userInteraction = false,
}) {
final TextEditingValue oldValue = _value;
final bool textChanged = oldValue.text != value.text;
final textChanged = oldValue.text != value.text;
final bool textCommitted =
!oldValue.composing.isCollapsed && value.composing.isCollapsed;
final bool selectionChanged = oldValue.selection != value.selection;
final selectionChanged = oldValue.selection != value.selection;
if (textChanged || textCommitted) {
// Only apply input formatters if the text has changed (including uncommitted
@@ -4567,8 +4615,8 @@ class EditableTextState extends State<EditableText>
widget.cursorColor.alpha / 255.0,
_cursorBlinkOpacityController.value,
);
renderEditable.cursorColor = widget.cursorColor.withOpacity(
effectiveOpacity,
renderEditable.cursorColor = widget.cursorColor.withValues(
alpha: effectiveOpacity,
);
_cursorVisibilityNotifier.value =
widget.showCursor &&
@@ -4797,6 +4845,8 @@ class EditableTextState extends State<EditableText>
}
final InlineSpan inlineSpan = renderEditable.text!;
final double? lineHeightScaleFactor =
MediaQuery.maybeLineHeightScaleFactorOverrideOf(context);
final TextScaler effectiveTextScaler = switch ((
widget.textScaler,
widget.textScaleFactor,
@@ -4808,7 +4858,7 @@ class EditableTextState extends State<EditableText>
(null, null) => MediaQuery.textScalerOf(context),
};
final _ScribbleCacheKey newCacheKey = _ScribbleCacheKey(
final newCacheKey = _ScribbleCacheKey(
inlineSpan: inlineSpan,
textAlign: widget.textAlign,
textDirection: _textDirection,
@@ -4817,7 +4867,9 @@ class EditableTextState extends State<EditableText>
widget.textHeightBehavior ??
DefaultTextHeightBehavior.maybeOf(context),
locale: widget.locale,
structStyle: widget.strutStyle,
structStyle: widget.strutStyle.merge(
StrutStyle(height: lineHeightScaleFactor),
),
placeholder: _placeholderLocation,
size: renderEditable.size,
);
@@ -4830,14 +4882,14 @@ class EditableTextState extends State<EditableText>
}
_scribbleCacheKey = newCacheKey;
final List<SelectionRect> rects = <SelectionRect>[];
int graphemeStart = 0;
final rects = <SelectionRect>[];
var graphemeStart = 0;
// Can't use _value.text here: the controller value could change between
// frames.
final String plainText = inlineSpan.toPlainText(
includeSemanticsLabels: false,
);
final CharacterRange characterRange = CharacterRange(plainText);
final characterRange = CharacterRange(plainText);
while (characterRange.moveNext()) {
final int graphemeEnd = graphemeStart + characterRange.current.length;
final List<TextBox> boxes = renderEditable.getBoxesForSelection(
@@ -4911,9 +4963,7 @@ class EditableTextState extends State<EditableText>
if (selection == null || !selection.isValid) {
return;
}
final TextPosition currentTextPosition = TextPosition(
offset: selection.start,
);
final currentTextPosition = TextPosition(offset: selection.start);
final Rect caretRect = renderEditable.getLocalRectForCaret(
currentTextPosition,
);
@@ -4942,7 +4992,7 @@ class EditableTextState extends State<EditableText>
) {
// Compare the current TextEditingValue with the pre-format new
// TextEditingValue value, in case the formatter would reject the change.
final bool shouldShowCaret = widget.readOnly
final shouldShowCaret = widget.readOnly
? _value.selection != value.selection
: _value != value;
if (shouldShowCaret) {
@@ -5353,11 +5403,8 @@ class EditableTextState extends State<EditableText>
final String text = _value.text;
final TextSelection selection = _value.selection;
final bool atEnd = selection.baseOffset == text.length;
final CharacterRange transposing = CharacterRange.at(
text,
selection.baseOffset,
);
final atEnd = selection.baseOffset == text.length;
final transposing = CharacterRange.at(text, selection.baseOffset);
if (atEnd) {
transposing.moveBack(2);
} else {
@@ -5459,8 +5506,7 @@ class EditableTextState extends State<EditableText>
return;
}
final ScrollableState? state =
_scrollableKey.currentState as ScrollableState?;
final state = _scrollableKey.currentState as ScrollableState?;
final double increment = ScrollAction.getDirectionalIncrement(
state!,
intent,
@@ -5487,8 +5533,7 @@ class EditableTextState extends State<EditableText>
final Rect extentRect = renderEditable.getLocalRectForCaret(
_value.selection.extent,
);
final ScrollableState? state =
_scrollableKey.currentState as ScrollableState?;
final state = _scrollableKey.currentState as ScrollableState?;
final double increment = ScrollAction.getDirectionalIncrement(
state!,
ScrollIntent(
@@ -5501,7 +5546,7 @@ class EditableTextState extends State<EditableText>
if (_value.selection.extentOffset >= _value.text.length) {
return;
}
final Offset nextExtentOffset = Offset(
final nextExtentOffset = Offset(
extentRect.left,
extentRect.top + increment,
);
@@ -5520,7 +5565,7 @@ class EditableTextState extends State<EditableText>
if (_value.selection.extentOffset <= 0) {
return;
}
final Offset nextExtentOffset = Offset(
final nextExtentOffset = Offset(
extentRect.left,
extentRect.top + increment,
);
@@ -5802,6 +5847,12 @@ class EditableTextState extends State<EditableText>
),
(null, null) => MediaQuery.textScalerOf(context),
};
final double? lineHeightScaleFactor =
MediaQuery.maybeLineHeightScaleFactorOverrideOf(context);
final double? letterSpacing = MediaQuery.maybeLetterSpacingOverrideOf(
context,
);
final double? wordSpacing = MediaQuery.maybeWordSpacingOverrideOf(context);
final ui.SemanticsInputType inputType;
switch (widget.keyboardType) {
case TextInputType.phone:
@@ -5892,7 +5943,14 @@ class EditableTextState extends State<EditableText>
startHandleLayerLink:
_startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink,
inlineSpan: buildTextSpan(),
inlineSpan:
_OverridingTextStyleTextSpanUtils.applyTextSpacingOverrides(
lineHeightScaleFactor:
lineHeightScaleFactor,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
textSpan: buildTextSpan(),
),
value: _value,
cursorColor: _cursorColor,
backgroundCursorColor:
@@ -5904,7 +5962,11 @@ class EditableTextState extends State<EditableText>
maxLines: widget.maxLines,
minLines: widget.minLines,
expands: widget.expands,
strutStyle: widget.strutStyle,
strutStyle: widget.strutStyle.merge(
StrutStyle(
height: lineHeightScaleFactor,
),
),
selectionColor:
_selectionOverlay
?.spellCheckToolbarIsVisible ??
@@ -5974,7 +6036,7 @@ class EditableTextState extends State<EditableText>
String text = _value.text;
text = widget.obscuringCharacter * text.length;
// Reveal the latest character in an obscured field only on mobile.
const Set<TargetPlatform> mobilePlatforms = <TargetPlatform>{
const mobilePlatforms = <TargetPlatform>{
TargetPlatform.android,
TargetPlatform.fuchsia,
TargetPlatform.iOS,
@@ -5994,7 +6056,7 @@ class EditableTextState extends State<EditableText>
}
if (_placeholderLocation >= 0 &&
_placeholderLocation <= _value.text.length) {
final List<_ScribblePlaceholder> placeholders = <_ScribblePlaceholder>[];
final placeholders = <_ScribblePlaceholder>[];
final int placeholderLocation = _value.text.length - _placeholderLocation;
if (_isMultiline) {
// The zero size placeholder here allows the line to break and keep the caret on the first line.
@@ -6379,7 +6441,7 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable>
return false;
}
final Rect intersection = calculatedBounds.intersect(rect);
final HitTestResult result = HitTestResult();
final result = HitTestResult();
WidgetsBinding.instance.hitTestInView(
result,
intersection.center,
@@ -6392,7 +6454,7 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable>
@override
Rect get bounds {
final RenderBox? box = context.findRenderObject() as RenderBox?;
final box = context.findRenderObject() as RenderBox?;
if (box == null || !mounted || !box.attached) {
return Rect.zero;
}
@@ -6422,7 +6484,7 @@ class _ScribblePlaceholder extends WidgetSpan {
List<PlaceholderDimensions>? dimensions,
}) {
assert(debugAssertIsValid());
final bool hasStyle = style != null;
final hasStyle = style != null;
if (hasStyle) {
builder.pushStyle(style!.getTextStyle(textScaler: textScaler));
}
@@ -6539,13 +6601,13 @@ class _DeleteTextAction<T extends DirectionalTextEditingIntent>
final TextBoundary atomicBoundary = state._characterBoundary();
if (!selection.isCollapsed) {
// Expands the selection to ensure the range covers full graphemes.
final TextRange range = TextRange(
final range = TextRange(
start:
atomicBoundary.getLeadingTextBoundaryAt(selection.start) ??
state._value.text.length,
end: atomicBoundary.getTrailingTextBoundaryAt(selection.end - 1) ?? 0,
);
final ReplaceTextIntent replaceTextIntent = ReplaceTextIntent(
final replaceTextIntent = ReplaceTextIntent(
state._value,
'',
range,
@@ -6571,7 +6633,7 @@ class _DeleteTextAction<T extends DirectionalTextEditingIntent>
0,
extentOffset: target,
);
final ReplaceTextIntent replaceTextIntent = ReplaceTextIntent(
final replaceTextIntent = ReplaceTextIntent(
state._value,
'',
rangeToDelete,
@@ -6609,7 +6671,7 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent>
// Returns true iff the given position is at a wordwrap boundary in the
// upstream position.
bool _isAtWordwrapUpstream(TextPosition position) {
final TextPosition end = TextPosition(
final end = TextPosition(
offset: state.renderEditable.getLineAtOffset(position).end,
affinity: TextAffinity.upstream,
);
@@ -6622,7 +6684,7 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent>
// Returns true if the given position at a wordwrap boundary in the
// downstream position.
bool _isAtWordwrapDownstream(TextPosition position) {
final TextPosition start = TextPosition(
final start = TextPosition(
offset: state.renderEditable.getLineAtOffset(position).start,
);
return start == position &&
@@ -6690,7 +6752,7 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent>
(selection.baseOffset - selection.extentOffset) *
(selection.baseOffset - newSelection.extentOffset) <
0;
final TextSelection newRange = shouldCollapseToBase
final newRange = shouldCollapseToBase
? TextSelection.fromPosition(selection.base)
: newSelection;
return Actions.invoke(
@@ -6948,3 +7010,55 @@ class _EditableTextTapUpOutsideAction
// The default action is a no-op.
}
}
/// A utility class for overriding the text styles of a [TextSpan] tree.
// When changes are made to this class, the equivalent API in text.dart
// must also be updated.
// TODO(Renzo-Olivares): Remove after investigating a solution for overriding all
// styles for children in an [InlineSpan] tree, see: https://github.com/flutter/flutter/issues/177952.
class _OverridingTextStyleTextSpanUtils {
static TextSpan applyTextSpacingOverrides({
double? lineHeightScaleFactor,
double? letterSpacing,
double? wordSpacing,
required TextSpan textSpan,
}) {
if (lineHeightScaleFactor == null &&
letterSpacing == null &&
wordSpacing == null) {
return textSpan;
}
return _applyTextStyleOverrides(
TextStyle(
height: lineHeightScaleFactor,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
),
textSpan,
);
}
static TextSpan _applyTextStyleOverrides(
TextStyle overrideTextStyle,
TextSpan textSpan,
) {
return TextSpan(
text: textSpan.text,
children: textSpan.children?.map((InlineSpan child) {
if (child is TextSpan && child.runtimeType == TextSpan) {
return _applyTextStyleOverrides(overrideTextStyle, child);
}
return child;
}).toList(),
style: textSpan.style?.merge(overrideTextStyle) ?? overrideTextStyle,
recognizer: textSpan.recognizer,
mouseCursor: textSpan.mouseCursor,
onEnter: textSpan.onEnter,
onExit: textSpan.onExit,
semanticsLabel: textSpan.semanticsLabel,
semanticsIdentifier: textSpan.semanticsIdentifier,
locale: textSpan.locale,
spellOut: textSpan.spellOut,
);
}
}

View File

@@ -88,7 +88,7 @@ class SpellCheckSuggestionsToolbar extends StatelessWidget {
return null;
}
final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
final buttonItems = <ContextMenuButtonItem>[];
// Build suggestion buttons.
for (final String suggestion in spanAtCursorIndex.suggestions.take(
@@ -112,7 +112,7 @@ class SpellCheckSuggestionsToolbar extends StatelessWidget {
}
// Build delete button.
final ContextMenuButtonItem deleteButton = ContextMenuButtonItem(
final deleteButton = ContextMenuButtonItem(
onPressed: () {
if (!editableTextState.mounted) {
return;
@@ -174,18 +174,17 @@ class SpellCheckSuggestionsToolbar extends StatelessWidget {
/// Builds the toolbar buttons based on the [buttonItems].
List<Widget> _buildToolbarButtons(BuildContext context) {
return buttonItems.map((ContextMenuButtonItem buttonItem) {
final TextSelectionToolbarTextButton button =
TextSelectionToolbarTextButton(
padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
onPressed: buttonItem.onPressed,
alignment: Alignment.centerLeft,
child: Text(
AdaptiveTextSelectionToolbar.getButtonLabel(context, buttonItem),
style: buttonItem.type == ContextMenuButtonType.delete
? const TextStyle(color: Colors.blue)
: null,
),
);
final button = TextSelectionToolbarTextButton(
padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
onPressed: buttonItem.onPressed,
alignment: Alignment.centerLeft,
child: Text(
AdaptiveTextSelectionToolbar.getButtonLabel(context, buttonItem),
style: buttonItem.type == ContextMenuButtonType.delete
? const TextStyle(color: Colors.blue)
: null,
),
);
if (buttonItem.type != ContextMenuButtonType.delete) {
return button;
@@ -216,7 +215,7 @@ class SpellCheckSuggestionsToolbar extends StatelessWidget {
mediaQueryData.padding.top +
CupertinoTextSelectionToolbar.kToolbarScreenPadding;
// Makes up for the Padding.
final Offset localAdjustment = Offset(
final localAdjustment = Offset(
CupertinoTextSelectionToolbar.kToolbarScreenPadding,
paddingAbove,
);

View File

@@ -156,19 +156,37 @@ class SystemContextMenu extends StatefulWidget {
static List<IOSSystemContextMenuItem> getDefaultItems(
EditableTextState editableTextState,
) {
return <IOSSystemContextMenuItem>[
if (editableTextState.copyEnabled) const IOSSystemContextMenuItemCopy(),
if (editableTextState.cutEnabled) const IOSSystemContextMenuItemCut(),
if (editableTextState.pasteEnabled) const IOSSystemContextMenuItemPaste(),
if (editableTextState.selectAllEnabled)
const IOSSystemContextMenuItemSelectAll(),
if (editableTextState.lookUpEnabled)
const IOSSystemContextMenuItemLookUp(),
if (editableTextState.searchWebEnabled)
const IOSSystemContextMenuItemSearchWeb(),
if (editableTextState.liveTextInputEnabled)
const IOSSystemContextMenuItemLiveText(),
];
final items = <IOSSystemContextMenuItem>[];
// Use the generic Flutter-rendered context menu model as the single source of truth.
for (final ContextMenuButtonItem button
in editableTextState.contextMenuButtonItems) {
switch (button.type) {
case ContextMenuButtonType.copy:
items.add(const IOSSystemContextMenuItemCopy());
case ContextMenuButtonType.cut:
items.add(const IOSSystemContextMenuItemCut());
case ContextMenuButtonType.paste:
items.add(const IOSSystemContextMenuItemPaste());
case ContextMenuButtonType.selectAll:
items.add(const IOSSystemContextMenuItemSelectAll());
case ContextMenuButtonType.lookUp:
items.add(const IOSSystemContextMenuItemLookUp());
case ContextMenuButtonType.searchWeb:
items.add(const IOSSystemContextMenuItemSearchWeb());
case ContextMenuButtonType.share:
items.add(const IOSSystemContextMenuItemShare());
case ContextMenuButtonType.liveTextInput:
items.add(const IOSSystemContextMenuItemLiveText());
case ContextMenuButtonType.delete:
// No native iOS system menu button for Delete — intentionally ignored.
case ContextMenuButtonType.custom:
// Custom items are provided explicitly via SystemContextMenu.items,
// not via defaults. Intentionally ignore in default mapping.
}
}
return items;
}
@override

View File

@@ -1302,14 +1302,17 @@ class RichTextFieldState extends State<RichTextField>
context,
);
final ThemeData themeData = Theme.of(context);
final InputDecorationThemeData decorationTheme = InputDecorationTheme.of(
context,
);
final InputDecoration effectiveDecoration =
(widget.decoration ?? const InputDecoration())
.applyDefaults(themeData.inputDecorationTheme)
.applyDefaults(decorationTheme)
.copyWith(
enabled: _isEnabled,
hintMaxLines:
widget.decoration?.hintMaxLines ??
themeData.inputDecorationTheme.hintMaxLines ??
decorationTheme.hintMaxLines ??
widget.maxLines,
);
@@ -1347,8 +1350,8 @@ class RichTextFieldState extends State<RichTextField>
return effectiveDecoration;
} // No counter widget
String counterText = '$currentLength';
String semanticCounterText = '';
var counterText = '$currentLength';
var semanticCounterText = '';
// Handle a real maxLength (positive number)
if (widget.maxLength! > 0) {
@@ -1655,7 +1658,7 @@ class RichTextFieldState extends State<RichTextField>
widget.keyboardAppearance ?? theme.brightness;
final RichTextEditingController controller = _effectiveController;
final FocusNode focusNode = _effectiveFocusNode;
final List<TextInputFormatter> formatters = <TextInputFormatter>[
final formatters = <TextInputFormatter>[
...?widget.inputFormatters,
if (widget.maxLength != null)
LengthLimitingTextInputFormatter(